HuntMe2
The trail doesn’t stay still. The forest shifts, and the signs no longer speak plainly. What was once hidden in plain sight is now layered behind changing patterns.
Author : N!L
Attack script written by AI agent:
#!/usr/bin/env python3
# Data from the binary
byte_402060 = [
0xf8, 0x98, 0x76, 0xfb, 0xc9, 0x0a, 0x03, 0x0d,
0x44, 0x3d, 0x6b, 0xa6, 0xc3, 0x25, 0xa8, 0x60,
0xfb, 0x57, 0x6c, 0xf3, 0xa1, 0xf0, 0xcf, 0x61,
0xe6, 0xe4, 0x45, 0x16, 0x0e, 0x18, 0x3e, 0x27
]
# The arrays at unk_402020 (5 arrays of 7 bytes each based on the code)
# From address 0x402020, we have data. Looking at the code, v2[0] = &unk_402020,
# v2[1] = &unk_402027, v2[2] = &unk_40202E, v2[3] = &unk_402035, v2[4] = &unk_40203C
# So each is 7 bytes apart (0x402027 - 0x402020 = 7)
data_at_402020 = [
0xa8, 0xc5, 0x83, 0xa0, 0x42, 0x2c, 0x01, # array 0
0xcb, 0x32, 0x20, 0xf3, 0xcf, 0x65, 0xbc, # array 1
0x13, 0x79, 0xb2, 0x29, 0x74, 0x61, 0xe7, # array 2
0xa7, 0x68, 0x76, 0x0a, 0x4e, 0x39, 0x43, # array 3
0xf1, 0xcd, 0x12, 0xb2, 0x7d, 0x0b, 0x2d # array 4
]
def sub_401201(a1, a2):
# return (unsigned __int8)((61 * a2) ^ ((unsigned __int8)((8 * a1) ^ a1) >> 5) ^ (8 * a1) ^ a1);
temp = ((8 * a1) ^ a1) & 0xFF
return ((61 * a2) ^ (temp >> 5) ^ temp) & 0xFF
def sub_401239(a1):
# a1 is the index (0-31)
v6 = 0
arrays = []
for i in range(5):
arrays.append(data_at_402020[i*7:(i+1)*7])
for i in range(5):
v4 = arrays[i] # pointer to array
v3 = (a1 * (i + 1) + i * i + 3) % 7
v6 ^= v4[v3]
v6 = ((2 * v6) | (v6 >> 7)) & 0xFF
return sub_401201(v6, a1)
def compute_target():
# We need to find input[i] such that: sub_401239(i) ^ input[i] == byte_402060[i]
# So input[i] = sub_401239(i) ^ byte_402060[i]
flag = []
for i in range(32):
val = sub_401239(i)
flag_char = val ^ byte_402060[i]
flag.append(flag_char)
return bytes(flag)
if __name__ == "__main__":
flag = compute_target()
print(f"Flag: {flag}")
print(f"Flag (string): {flag.decode('ascii', errors='ignore')}")
# Test by running through the algorithm
print("\nTesting...")
for i in range(32):
val = sub_401239(i)
computed = val ^ flag[i]
if computed != byte_402060[i]:
print(f"Mismatch at index {i}: {computed:02x} != {byte_402060[i]:02x}")
else:
print(f"Index {i}: OK")
Writeup written by AI agent:
HuntMe2 CTF Challenge Solution
Challenge Analysis
The binary HuntMe2 is a 64-bit ELF executable that validates a 32-character input flag. The validation algorithm involves:
- Input Length Check: The input must be exactly 32 characters.
- Complex Transformation: Each character at position
i(0-31) undergoes a transformation throughsub_401239(i). - XOR Comparison: The transformed value is XORed with the input character and compared against a hardcoded byte array
byte_402060.
Algorithm Details
Main Validation (sub_40132A)
- Checks input length == 32
- Calls
sub_401176()(doesn't affect validation, just computes a value) - For each position
i(0-31):- Computes
sub_401239(i) - Requires:
sub_401239(i) ^ input[i] == byte_402060[i] - Therefore:
input[i] = sub_401239(i) ^ byte_402060[i]
- Computes
Transformation Function (sub_401239)
Takes index i and returns a byte:
- Uses 5 arrays of 7 bytes each starting at
0x402020 - For each array
j(0-4):- Computes index:
(i * (j+1) + j*j + 3) % 7 - XORs the byte at that index into accumulator
v6 - Rotates
v6left by 1 bit:v6 = (2 * v6) | (v6 >> 7)
- Computes index:
- Passes result through
sub_401201(v6, i)
Final Transformation (sub_401201)
return (61 * i) ^ (((8 * v6) ^ v6) >> 5) ^ ((8 * v6) ^ v6)
Solution Approach
The solution involves:
- Extracting the hardcoded data from the binary:
byte_402060(32 bytes): target XOR results- Arrays at
0x402020(35 bytes, organized as 5x7 arrays)
- Implementing the transformation algorithm in Python
- Computing the required input:
input[i] = sub_401239(i) ^ byte_402060[i]
Flag
nexus{f0ll0w_7h3_ch4ng1ng_7r41l}
How to Test
echo -n "nexus{f0ll0w_7h3_ch4ng1ng_7r41l}" | ./HuntMe2
Output: "You adapt. The hunt continues."
Tools Used
- Python for implementing the reverse algorithm
- Standard Linux tools (file, strings, etc.)