## Overview

Callfuscated looked at first like a normal Linux crackme, but normal reverse-engineering workflows quickly broke down. The binary was stripped, the real validation routine was not represented cleanly as a function, and Ghidra could not produce a trustworthy high-level view of the critical code path.

The final solve came from changing abstraction levels: instead of trying to read the decompiler output, I traced the executed code, recognized a call-threaded VM/stack-machine, recovered its bytecode semantics, and reduced the final password check to simple 32-bit XOR/subtraction constraints.

## Initial Triage

The archive contained a single target binary:

    rev_callfuscated/crackme

Basic properties:

Property | Value
--- | ---
Format | ELF 64-bit LSB executable
Architecture | x86-64
Linking | Dynamically linked
Symbols | Stripped
SHA-256 | `2389a35ac38bff9546a4c356c7d22633b22e8a1616b61adb85991afda40a3e36`

Runtime behavior:

    Welcome to callfuscated crackme.
    To register enter your password:
    Incorrect flag. Try again

Important strings and addresses discovered during analysis:

    entry:                 0x401080
    real main-ish target:  0x409002
    input buffer:          0x40f080
    prompt string:         0x40d008
    scanf format:          0x40d04b  "%63s"
    success format:        0x40d050  "Correct. Validate the challenge using the flag: %s"
    failure string:        0x40d084

## Callfuscation Model

The binary used a repeated pattern like:

    call target
    pop r8
    ...
    call next_block
    pop r8

This destroys the assumptions normal disassemblers and decompilers make about calls, returns, and function boundaries. Ghidra could identify many pieces of code, but the apparent functions were not the semantic units that mattered.

Key pivot: the problem was not “find the right function in Ghidra.” The problem was “recover the executed instruction stream and infer the higher-level machine being interpreted.”

## Dynamic Analysis

I used controlled dynamic analysis to locate the actual validation flow. The input was stored globally at:

    0x40f080

Watchpoints and traces showed that the program consumed exactly 32 bytes of input. A simple prefix oracle did not exist; wrong inputs produced similar late failure states, so the validator was not a straight character-by-character compare.

The decisive late predicate was observed around:

    0x40b537: test eax, eax
    0x40bcc2: jne  0x4096e1

For bad inputs, `eax` ended as `0xffffffff` and execution reached the failure path.

## VM Reduction

Tracing the semantic instruction stream revealed that the validator behaved like a small stack machine. The clean bytecode semantics were recovered as:

Opcode | Meaning
--- | ---
`op0` | push immediate
`op2` | add
`op3` | subtract
`op5` | multiply
`op7` | bitwise OR
`op8` | XOR
`op10` | load byte from computed address

The VM built eight big-endian 32-bit words from the 32-byte input and checked each chunk with:

    ((word ^ const_a) - const_b) == 0

Therefore each required 32-bit input word was simply:

    word = const_a ^ const_b

## Recovering the Flag

The extracted constant pairs produced the full 32-byte input:

Expression | Result | ASCII
--- | --- | ---
`0x0915033a ^ 0x41414141` | `0x4854427b` | `HTB{`
`0x427d7872 ^ 0x11111111` | `0x536c6963` | `Slic`
`0x30310a00 ^ 0x55555555` | `0x65645f55` | `ed_U`
`0x2a052e32 ^ 0x5a5a5a5a` | `0x705f7468` | `p_th`
`0xcff5ecdf ^ 0xaaaaaaaa` | `0x655f4675` | `e_Fu`
`0x1914031e ^ 0x77777777` | `0x6e637469` | `ncti`
`0xf6f7c6ad ^ 0x99999999` | `0x6f6e5f34` | `on_4`
`0x6c6a524e ^ 0x33333333` | `0x5f59617d` | `_Ya}`

## Verification

Combining the chunks gives:

    HTB{Sliced_Up_the_Function_4_Ya}

Running the binary with that value reached the success path and printed the expected validation string.

## Lessons Learned

- Decompiler failure was a signal, not a dead end.
- The right abstraction was an executed VM trace, not static function recovery.
- Call-threading can make ordinary control-flow graphs actively misleading.
- The obfuscated arithmetic ultimately collapsed to simple XOR/subtraction equations.