Scenario
The room shouldn't exist. No record in Crownspire's registry places a private library on this floor, no watch schedule accounts for the hour it takes to reach it, and that's exactly why Rin is standing in it now. Dust holds the shape of a desk nobody's touched since before the white fire. Shelves half burned, half intact. In the far wall sits an old strongbox with a hatch built into its face, sealed the way a man seals something he never wants opened again, not by anyone, not even by himself on a better day. Nobody sent Rin here. She came because the under-levels are hers to answer for, and lately every runner who comes back from the southern roads brings the same story: the Quiet Marches are moving, and whatever Alyss intends next, it isn't going to stop at a border. Maelor kept files on things he feared enough to bury, and Rin is betting that a girl who should have died in one of his purges made that list. If there's a way to know what's coming before it reaches her people, it's behind this hatch. One lock stands between her and an answer, and she means to have it before the watch changes.
Enumeration
I start by examining the binary type and compiled security protection using file and checksec.
- Architecture: 64-bit x86_64 Little-Endian LSB.
- Symbols: Not stripped (function names like
main,service_hatch, andbannerremain intact).
Key Security Implications:
- Executable Stack (
Stack: Executable/ No NX Protection): Due to a missing GNU_STACK ELF program header, stack memory is marked with Read/Write/Execute (RWX) permissions. If we can divert control flow to data we provide on the stack, the CPU will execute our input directly as shellcode. - No Stack Canary: There are no stack protector cookies (canaries) verifying stack integrity before function returns. We can continuously write past local buffer boundaries to overwrite saved pointers without triggering an abort.
- PIE Enabled: Position Independent Executable is active, meaning binary code locations and libc addresses are randomized by ASLR. However, as demonstrated in our analysis below, a deliberate address leak inside the binary renders PIE harmless to our attack path.
Reverse Engineering & Code Analysis
Using objdump, readelf, and static analysis techniques, we examine the behavior of the challenge symbols.
Symbol Tree Overview
Using readelf -Ws the_hinge_whisper reveals three custom application functions:
bannerat offset0x11c9service_hatchat offset0x11e3mainat offset0x1246
Disassembly & Pseudocode Reconstruction
main Function (0x1246)
main configures unbuffered I/O streams (stdin and stdout), calls banner() to display lore ASCII art, invokes service_hatch(), and finally prints an ending string if the hatch finishes without intervention.
service_hatch Function (0x11e3)
Here is the core disassembly of the vulnerable procedure:
Translated into high-level C code:
Vulnerability Analysis
The function exhibits two interconnected vulnerabilities that make achieving arbitrary shell code execution straightforward:
- Information Disclosure (Stack Pointer Leak):
By printing
%ptargeting&buffer(rbp - 0x40), the binary explicitly tells us the exact randomized virtual memory address of the stack buffer for the current execution instance. This breaks Address Space Layout Randomization (ASLR). - Stack Buffer Overflow:
The allocated array size is only
0x40(64 bytes), but the subsequent call toread()accepts up to0x50(80 bytes). Because the stack lacks canaries, writing past byte 64 immediately overwrites stack maintenance registers.
Stack Frame Layout
By providing a payload of exactly 64 + 8 + 8 = 80 bytes, we gain full control over the saved instruction pointer (RIP).
Exploitation
Why This Attack Vector Works
- When
service_hatch()finishes execution and callsret(1245: c3), the CPU pops the top 8 bytes from the stack (at offset 72) and redirects instruction execution to that memory address. - We replace this return address with the leaked stack address captured from
printf. - Because the ELF lacks NX bit enforcement (
Stack: Executable), when the CPU jumps into the stack buffer, it treats our supplied bytes as instructions instead of data, immediately executing our shellcode.
Shellcode Design: Avoiding Self-Corruption on Return
A subtle consideration when deploying shellcode on small stack frames is the interaction between stack pointer pushes and instruction execution:
- At the exact moment
retredirects control flow into our buffer (offset0), the stack pointer (RSP) has just popped the return address and sits at offset80(rbp + 0x10). - Because x86_64 stacks grow downward (from higher memory addresses toward lower ones), execution of assembly instruction patterns that push arguments (e.g., standard
push 0x6873...strings for/bin/sh) decrementsRSPback down toward our active payload. - Standard long generator shellcodes (such as
shellcraft.sh()at 48 bytes) perform multiple pushes that can dip down below offset 48, causing instructions to overwrite parts of themselves before execution finishes!
Solution: Compact 24-byte x64 Shellcode
To ensure zero possibility of stack collision and self-corruption, we implement a concise 24-byte custom x86_64 assembly shellcode invoking execve("/bin//sh", NULL, NULL):
Why this failsafe works: The 24-byte payload occupies bytes 0 to 24 of our buffer. The two push operations only drop RSP from byte 80 down to 64. That leaves a clean 40-byte separation gap (padded with NOP instructions 0x90) ensuring our executing instructions remain completely isolated from stack writes.
Exploit Script
Below is the annotated Python exploit script exploit.py utilizing pwntools:
Execution
Running the exploit against the challenge server successfully abuses the executable stack and retrieves an interactive system shell to capture the flag:
Conclusion
"The Hinge Whisper" serves as an excellent foundational exercise in classic stack-based binary exploitation. While the binary was protected by Position Independent Executable (PIE), the intentional memory leak of the buffer's address completely negated this mitigation, providing a reliable and direct target for our hijacked instruction pointer.
Key Takeaways:
- Information Disclosure is Fatal: The deliberate
%pformat string leak of the stack pointer demonstrated how a single verbosity error can entirely compromise ASLR and PIE protections. - Stack Dynamics Matter: The core hurdle was not just hijacking control flow, but understanding exactly how the stack pointer (
RSP) behaves during and immediately after aretinstruction. Recognizing the potential for self-corrupting shellcode on a small stack frame was critical to a successful exploit. - Payload Optimization: Bypassing the self-corruption issue required discarding standard, bulky shellcode generators in favor of a tightly written, 24-byte custom x86_64 assembly payload that maintained a safe distance from active stack operations.
By successfully forging the digital latch-key and exploiting the memory layout, we gained an interactive shell, bypassed the unique hatch lock, and secured the flag. With this obstacle cleared, Rin is now one step closer to uncovering the rest of Maelor's hidden secrets within the undocumented room.