pwnfreemedium

Magic Scrolls

hackthebox

Task: glibc 2.37 menu-based heap pwn where a global magic_numbers[] array overlaps the spells[] pointer table, giving an arbitrary 8-byte write into two heap-pointer slots. Solution: build arbitrary read/free primitives from the overlap, leak heap (safe-linking) and libc (unsorted bin), perform a double-free-key-safe overlap-poison tcache write of _IO_list_all, and trigger House of Apple 2 FSOP via exit() for a shell.

$ ls tags/ techniques/
tcache_poisoningunsorted_bin_libc_leakhouse_of_apple_2array_overlap_bugoverlap_poison_arbitrary_writesafe_linking_recoveryself_introspection_via_arbitrary_readfsop_exit_trigger

$ cat /etc/rate-limit

Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.

Magic Scrolls — HackTheBox

Description

Legends say that if magical numbers align in the right sequence, magic will happen.

A medium-difficulty heap-exploitation challenge. We are given a PIE ELF64 binary magic (not stripped) bundled with its own loader and libc (Debian GLIBC 2.37-7), plus a Dockerfile (ubuntu:22.04 + socat). The remote service runs:

socat tcp-l:1337,reuseaddr,fork EXEC:./magic

Goal: pop a shell on the remote and read flag.txt.

Analysis

Protections

Full RELRO | Stack Canary | NX | PIE

Crucially the libc is glibc 2.37, where __free_hook and __malloc_hook have been removed (the symbols may still exist but are never dereferenced by the allocator). That kills the classic hook-overwrite RCE path — code execution must come from FSOP (we use House of Apple 2).

Program behavior

main first reads a "magic charm":

read(0, buf, 0x40); if (strcmp(buf, "Alohomora") == 0) power = 4; // unlocks the bug

Then a 6-option menu loops:

OptNameEffect
1update_magic_numberswrite a magic number, and (if power!=0) project two of them onto spells[0]/[1]
2create_spellsize=read(0,buf,0x1ff); spells[c]=malloc(size); memcpy; c++
3remove_spellfree(spells[idx]); spells[idx]=0; spell_len[idx]=0
4read_spellprint super_spell_len-1 bytes from super_spell (arbitrary read)
5set_favorite_spellfirst call selects a spell; later calls only refresh the pointer
6exitexit()

Global memory map (offsets from PIE base)

...

$ grep --similar

Similar writeups