//HuntMe1 & HuntMe2
Category: Beginner
Flag format: nexus{}
>Summary
-
HuntMe1: Simple static analysis; the flag is embedded in the binary as a plaintext string. -
HuntMe2: Slightly harder — a stripped ELF with a custom key-generation routine that XORs an expected array to reveal a 32-byte flag. I statically reverse-engineered the algorithm and wrote a Python script to compute the flag.
>Tools used
-
file,strings— basic reconnaissance -
objdump -d(orgdb) — disassembly/inspection -
readelf -S— section offsets, rodata address -
python3— scripted calculation using the derived algorithm
>Walkthrough (HuntMe1)
Recon & static analysis
- Inspect the file type:
file HuntMe1
- Extract strings to quickly search for content that looks like a flag:
strings -n 6 HuntMe1 | head -n 60
Results
- The
stringsoutput showsnexus{h1dd3n_1n_7h3_f0r357_4t_n1gh7}in plaintext.
Conclusion: The flag is nexus{h1dd3n_1n_7h3_f0r357_4t_n1gh7}.
>Walkthrough (HuntMe2)
Short: This binary is stripped. Running strings gives only flavor text and no flag. The program takes input and validates it. The flag is reconstructed by reversing a key-generator + expected-bytes XOR operation stored in .rodata.
Step 1 — Basic Recon:
file HuntMe2
strings -n 6 HuntMe2
chmod +x HuntMe2
./HuntMe2 # runs and shows a prompt
Step 2 — Load rodata to locate constants
Use readelf -S to find .rodata, then inspect bytes in the file using xxd or objdump -s -j .rodata:
readelf -S HuntMe2
objdump -s -j .rodata HuntMe2
xxd -g1 -s 0x2020 -l 0x40 HuntMe2
xxd -g1 -s 0x2060 -l 0x40 HuntMe2
- I found arrays starting around
0x402020and an expected 32-byte array at0x402060.
Step 3 — Disassemble the program
objdump -d -Mintel HuntMe2 > HuntMe2.disasm
Looked for functions that operate on the arrays and take an index to produce a key byte. I found functions that:
-
Use 5 small byte-arrays (at
0x402020,0x402027,0x40202e,0x402035,0x40203c). -
Compute an index into each small array using a simple linear formula and
mod 7. -
XOR selected bytes, apply rotations, and then apply a
transform(shifts, xors, and xor with(n * 0x3d)) to produce a single byte for each flag byte. -
The 32 expected bytes are XORed with the computed key bytes to produce the flag string.
Key code snippets (reimplemented in pseudocode):
- Rotation & transform portion (deduced from disassembly at 0x401201):
transform(byte b, int n):
b ^= (b << 3)
b ^= (b >> 5)
b ^= (n * 0x3d)
return b
- The per-index key routine (deduced by following loops in the disassembly):
key(n):
v = 0
for i in range(5):
t = ((i+1) * n + i*i + 3) % 7 # choose a byte from the 7-long array
v ^= arrs[i][t]
v = ((v << 1) | (v >> 7)) & 0xff # rotate left by 1 after every array step
v = transform(v, n)
return v
The function then uses expected[n] ^ key(n) to obtain the flag byte at offset n.
Step 4 — Reconstruct the flag with Python
After deriving the algorithm from the disassembly, I wrote a Python script to reproduce the exact steps and compute all 32 bytes. Here is a minimal reproduction script that works with the .rodata bytes already found in the binary:
#!/usr/bin/env python3
import sys
import pathlib
path = pathlib.Path('HuntMe2')
data = path.read_bytes()
base = 0x402000
def get(addr,length):
return data[addr - base + 0x2000: addr - base + 0x2000 + length]
expected = get(0x402060, 0x20)
arrs = [get(0x402020 + i*7, 7) for i in range(5)]
def transform(b, n):
b &= 0xff
b ^= (b << 3) & 0xff
b ^= (b >> 5) & 0xff
b ^= ((n * 0x3d) & 0xff)
return b & 0xff
def key(n):
v = 0
for i, arr in enumerate(arrs):
t = ((i+1) * n + i * i + 3) % 7
v ^= arr[t]
v = ((v << 1) | (v >> 7)) & 0xff
v = transform(v, n)
return v
flag_bytes = bytes([ expected[i] ^ key(i) for i in range(len(expected)) ])
print(flag_bytes.decode())
# Output should be: nexus{f0ll0w_7h3_ch4ng1ng_7r41l}
Running this script reproduced the flag: nexus{f0ll0w_7h3_ch4ng1ng_7r41l}
Note: If you want to replicate, ensure that the script reads the exact bytes from the binary as in the example.
>Final flags
-
HuntMe1:
nexus{h1dd3n_1n_7h3_f0r357_4t_n1gh7} -
HuntMe2:
nexus{f0ll0w_7h3_ch4ng1ng_7r41l}
>Notes
-
HuntMe2 used a fairly straightforward custom key derivation pattern (5 small arrays + rotate + transform) and then XORed with an array of expected bytes in
.rodata. The challenge was mostly to find the right offsets and translate the assembly into the equivalent Python operations. -
The binary is stripped; however,
readelfandobjdumpare enough to inspect the rodata and disassembly.gdbhelps to validate function results and variable sizes if unsure.
If you'd like, I can add the exact Python script into the repository as solve_huntme2.py and a small README.md guiding how to run both solves.
Good luck in the contest!