187,064 Instructions for One Flag: Reverse Engineering HTB Callfuscated Insane Challenge

A detailed technical breakdown of the HackTheBox Callfuscated reversing challenge reveals an extremely heavy obfuscation scheme built around a custom virtual machine, mixed Boolean-arithmetic transformations, opaque predicates, and call-based junk code. The binary implements a password checker that executes 187,064 instructions even on a short input because every real operation is wrapped inside thousands of call/pop gadgets. The author bypassed static analysis by building a ptrace-based tracer, dumping the 586-cell VM program array, and writing a Python emulator that faithfully replays the recorded execution trace. After fixing several edge cases involving rand() return addresses and operand-size detection, the emulator reproduced the exact register state of the original binary. Dynamic analysis exposed that the VM performs simple big-endian word construction followed by XOR operations with eight constant pairs, allowing the flag to be recovered directly without further symbolic execution.

HabrTranslated from Russian

Read at source

The HackTheBox challenge Callfuscated presents a stripped 64-bit ELF binary that requests a password and prints either Correct or Incorrect flag. Inside the binary lies a heavily obfuscated verification routine built from four complementary techniques that survive static analysis but collapse under concrete execution.

The Obfuscation Arsenal

The protection consists of a custom VM whose bytecode resides in a 586-element array called program[]. A dispatcher reads the current program counter, fetches an opcode, and dispatches to handlers. Every arithmetic primitive is further expanded with Mixed Boolean-Arithmetic expressions. Opaque predicates double the control-flow graph with branches whose outcome is known at compile time. Finally, genuine instructions are wrapped inside thousands of call gadgets of the form pop r8; <insn>; call next, producing the 187,064-instruction count mentioned in the title.

Dynamic Reconnaissance

Because all four layers are execution-dependent, the author recorded a full instruction trace using ptrace with PTRACE_SINGLESTEP on a known input. The resulting log contained 187,064 lines together with register values after each instruction inside the image range 0x401000–0x40d000. A separate memory dumper extracted the VM program array and stack frames directly from the running process.

Building a Faithful Emulator

The trace was replayed inside a custom x86-64 emulator written in Python that parsed objdump output and implemented handlers for the thirty opcodes actually encountered. Two notable bugs were corrected during validation:

  • rand() is invoked from four distinct call sites (0x409235, 0x40ad5b, 0x40af37, 0x40b10b) for a total of 192 calls; each return address must be calculated as call-site + 5 rather than assuming a single fixed location.
  • Operand-size detection used a naïve substring check that incorrectly treated “DWORD PTR” as containing “WORD”, truncating 32- and 64-bit memory accesses.

After these fixes the emulator matched the original trace line-by-line and reached the final instruction at address 0x40b537 with eax equal to 0xffffffff, exactly as the real binary behaves on an incorrect password.

Recovering the VM Semantics

With a working emulator the author dumped the data stack after every dispatch and decoded the 586 VM instructions. The bytecode implements a simple stack machine whose relevant operations are:

  • PUSH imm – push constant
  • H2 – addition (used to compute input buffer address 0x40f080)
  • DEREF – read input byte
  • H5 – multiply accumulator by 256
  • H7 – add next byte
  • G8 / G3 – XOR with per-group constants

Consequently every four input characters are accumulated into a 32-bit big-endian word. Eight such words are XORed with the following constant pairs:

  • (0x0915033a, 0x41414141)
  • (0x427d7872, 0x11111111)
  • (0x30310a00, 0x55555555)
  • (0x2a052e32, 0x5a5a5a5a)
  • (0xcff5ecdf, 0xaaaaaaaa)
  • (0x1914031e, 0x77777777)
  • (0xf6f7c6ad, 0x99999999)
  • (0x6c6a524e, 0x33333333)

The resulting 32-bit values must all be zero for the password to be accepted. Solving each equation yields the flag bytes directly: HTB{******_**_***_********_*_**}.

Conclusion

The exercise demonstrates that even an extreme combination of VM-based dispatch, MBA, opaque predicates and call obfuscation remains vulnerable once execution is recorded and replayed. All four techniques ultimately depend on concrete runtime values that a faithful emulator can capture and simplify.

Meridian digestThe week's most consequential security stories, distilled into one briefing.See the digest

More in Malware & Botnets

Yoba Campaign Uses Signed ChatGPT and Claude Binaries for DLL Sideloading and XMRig Deployment
uCreator Spyware Infiltrates Modified Telegram, WeChat and GCash Builds on Android
Researchers Deploy Cowrie Honeypot Disguised as NVIDIA H100 Server to Study SSH Botnet Tactics