reversefreemedium

Hexecution

HackTheBox

Two files: `cook` — ELF 64-bit LSB PIE executable, x86-64, stripped, dynamically linked (custom VM interpreter) and `recipe.asm` — 289 lines of cooking-themed esoteric assembly. The binary reads and executes the recipe file.

$ ls tags/ techniques/
custom_vm_emulationopcode_reverse_engineeringpermutation_inversiondouble_permutation_reversalblock_shuffle_reversal

Hexecution — HackTheBox

Description

My friend is always tampering with low-level things. So created something different, and going to challenge him. Before challenge him, can you try it and see if it works or not?

Two files: cook — ELF 64-bit LSB PIE executable, x86-64, stripped, dynamically linked (custom VM interpreter) and recipe.asm — 289 lines of cooking-themed esoteric assembly. The binary reads and executes the recipe file.

Analysis

Initial Reconnaissance

$ file cook cook: ELF 64-bit LSB pie executable, x86-64, dynamically linked, stripped $ file recipe.asm recipe.asm: ASCII text $ wc -l recipe.asm 289 recipe.asm

The binary is stripped — no symbols, full reverse engineering through disassembler required. The recipe.asm file contains instructions with cooking-themed names: BOIL, AES256, SPELL, ROAST, QUICKMAFFS, GRIND, GOODBYE, WINDOW, LADDER, PEPEFROG, CHAIR.

VM Architecture

The VM has 6 16-bit registers and a 256-byte memory array:

RegisterPurpose
VEGETABLEGeneral purpose
FRUITGeneral purpose
MEATGeneral purpose
DAIRYGeneral purpose
PROTEINAccumulator / pointer
CARBOMemory base pointer

Additionally: internal counter (auto-increment for AES256 and SPELL 0).

Opcode Mapping

Full reverse engineering of the cook binary through Ghidra revealed the following mapping:

Cooking OpcodeActual OperationDescription
BOIL REG, immMOV REG, immLoad immediate value
AES256 immmem[CARBO + counter++] = immSequential byte write to memory
SPELL 1write(stdout)Print mem[CARBO..PROTEIN]
SPELL 0read(stdin)Read input into mem[CARBO..]
ROAST R1, R2XOR R1, R2Bitwise XOR
QUICKMAFFS addrPROTEIN = mem[addr]Load from memory into PROTEIN
GOODBYE R1, R2MOV R1, R2Register copy
WINDOW REGmem[CARBO] = REGWrite register to memory at CARBO address
LADDER REGINC REGIncrement register
GRIND R1, R2assert R1 == R2Compare, exit on mismatch
PEPEFROG R1, R2memcmp 32 bytesCompare 32 bytes of memory, output success
CHAIRDebug dump ASCIIDump memory as ASCII

Analyzing recipe.asm

The program executes the following logic:

  1. Lines 1-20: Writes and outputs the prompt "Enter the flag: "
  2. Line 24: Reads 32-byte user input via SPELL 0 into memory starting at offset 0x14
  3. Lines 25-57: Writes 32-byte reference string to memory at offset 0x70:
    Hex: 35 6d 61 4e 63 49 34 5f 5f 55 31 30 5f 64 65 35
         4c 31 33 5f 4d 6e 34 55 30 75 34 74 72 66 6e 5f
    ASCII: 5maNcI4__U10_de5L13_Mn4U0u4trfn_
    
  4. Line 60: Input length check == 32 via GRIND PROTEIN, VEGETABLE
  5. Lines 61-188 (Phase 1): In-place permutation within 4-byte blocks: [a,b,c,d] → [c,b,d,a]
  6. Lines 189-285 (Phase 2): Inter-block permutation — reading from shuffled input at specific addresses and sequential writing to new memory area (0x42+)
  7. Lines 287-289: Compare result with reference string via PEPEFROG

Solution

Step 1: Building VM Emulator

To understand the recipe.asm logic, a full VM emulator was written in Python implementing all 12 opcodes. Tracing with symbolic input allowed precise identification of both permutation phases.

Step 2: Extracting Permutations

Phase 1 (intra-block): For each 4-byte block [a,b,c,d][c,b,d,a]

Phase 2 (inter-block): Read addresses from input (base 0x14):

[0x14, 0x19, 0x1e, 0x23, 0x17, 0x1a, 0x1d, 0x20,
 0x18, 0x15, 0x16, 0x1b, 0x1c, 0x21, 0x22, 0x1f,
 0x24, 0x29, 0x2e, 0x33, 0x27, 0x2a, 0x2d, 0x30,
 0x28, 0x25, 0x26, 0x2b, 0x2c, 0x31, 0x32, 0x2f]

Indices (from 0): [0, 5, 10, 15, 3, 6, 9, 12, 4, 1, 2, 7, 8, 13, 14, 11, 16, 21, 26, 31, 19, 22, 25, 28, 20, 17, 18, 23, 24, 29, 30, 27]

Step 3: Inverting Permutations

Working backwards from the reference string:

  1. Invert Phase 2: Compute inverse permutation, apply to reference string → get Phase 1 output: 5U1c_mI0_4a5_deNLu4M01ntr43_Ufn_
  2. Invert Phase 1: For each 4-byte block reverse [c,b,d,a] → [a,b,c,d] (i.e., apply [d,b,a,c]) → get original input: cU510m_I54_aNd_eMuL4t10n_4r3_fUn

Step 4: Verification

Direct check: apply both permutations to cU510m_I54_aNd_eMuL4t10n_4r3_fUn:

  • After Phase 1: 5U1c_mI0_4a5_deNLu4M01ntr43_Ufn_
  • After Phase 2: 5maNcI4__U10_de5L13_Mn4U0u4trfn_ ✓ (matches reference)

Full Solve Script

#!/usr/bin/env python3 """Hexecution solver — inverts double permutation to recover flag.""" # Reference string from AES256 instructions in recipe.asm ref_bytes = [0x35, 0x6d, 0x61, 0x4e, 0x63, 0x49, 0x34, 0x5f, 0x5f, 0x55, 0x31, 0x30, 0x5f, 0x64, 0x65, 0x35, 0x4c, 0x31, 0x33, 0x5f, 0x4d, 0x6e, 0x34, 0x55, 0x30, 0x75, 0x34, 0x74, 0x72, 0x66, 0x6e, 0x5f] ref_str = ''.join(chr(b) for b in ref_bytes) # Phase 2 permutation: addresses read from input (base 0x14) phase2_addrs = [0x14, 0x19, 0x1e, 0x23, 0x17, 0x1a, 0x1d, 0x20, 0x18, 0x15, 0x16, 0x1b, 0x1c, 0x21, 0x22, 0x1f, 0x24, 0x29, 0x2e, 0x33, 0x27, 0x2a, 0x2d, 0x30, 0x28, 0x25, 0x26, 0x2b, 0x2c, 0x31, 0x32, 0x2f] perm2 = [addr - 0x14 for addr in phase2_addrs] # Invert Phase 2: output[perm2[i]] = ref[i] inv_perm2 = [0] * 32 for i in range(32): inv_perm2[perm2[i]] = i shuffled = [ref_str[inv_perm2[j]] for j in range(32)] shuffled_str = ''.join(shuffled) print(f"After inverting Phase 2: {shuffled_str}") # Invert Phase 1: reverse [c,b,d,a] -> [a,b,c,d] # Forward: [0,1,2,3] -> [2,1,3,0], so position 0 came from 3, etc. result = [''] * 32 for block in range(8): base = block * 4 a, b, c, d = shuffled_str[base], shuffled_str[base+1], shuffled_str[base+2], shuffled_str[base+3] # Inverse of [a,b,c,d] -> [c,b,d,a] is [d,b,a,c] result[base] = d # original[0] = shuffled[3] result[base + 1] = b # original[1] = shuffled[1] result[base + 2] = a # original[2] = shuffled[0] result[base + 3] = c # original[3] = shuffled[2] flag_inner = ''.join(result) print(f"Flag inner: {flag_inner}") print(f"HTB{{{flag_inner}}}") # HTB{cU510m_I54_aNd_eMuL4t10n_4r3_fUn}

$ cat /etc/motd

Liked this one?

Pro unlocks every writeup, every flag, and API access. $9/mo.

$ cat pricing.md

$ grep --similar

Similar writeups