Hack The Boo CTF 2025 | Reversing
All Reversing challenges from HackTheBoo 2025
Hack The Boo CTF 2025 was a 2-day CTF hosted by HackTheBox. I competed solo and finished in the top 5 out of 2,893 players. This post covers the Reversing challenges.
Digital Alchemy [medium]
Morvidus the alchemist claims to have perfected the art of digital alchemy. Being paranoid, he secured his incantation with a complex algorithm, but left the code rushed and broken. Fix his amateur mistakes and claim the digital gold for yourself!
Step 1: Explore the Challenge Files
List the contents and identify file types.
1ls -l
2file athanor
3cat lead.txt
4xxd lead.txtFindings:
athanor: 64-bit PIE ELF, stripped.lead.txt: begins with magicMTRLLEAD, then 4 bytes, then a payload.
Header breakdown (from xxd):
100000000: 4d54 524c 4c45 4144 972c ffbc ... MTRLLEAD.,..- Magic:
MTRLLEAD - Seed (big-endian):
0x97 0x2c 0xff 0xbc→0x972cffbc
Step 2: Baseline Runtime Behavior
Run the binary to observe side effects.
1./athanor
2ls -l
3cat gold.txtOutput snippet:
1Initializing the Athanor...
2The Athanor glows brightly, revealing a secret...The program writes gold.txt with 7 bytes: J^Mw_~<.
Step 3: Static Recon of the Binary
Pull strings and inspect .rodata.
1strings -a athanor | head -n 50
2objdump -s -j .rodata athanorInteresting data in .rodata:
USMWO[]\iN[QWRYdqXle[i_bm^aoc(29 bytes)- Filenames:
lead.txt,gold.txt - Messages, and the magic
MTRLLEAD
Disassemble to locate the main logic.
1objdump -M intel -d athanor | sed -n '300,420p'Key observations (addresses approximate):
- 0x1251–0x1310: reads
lead.txt, checks header, loads 4-byte seed big-endian. - 0x13bb–0x148f: Stage 1 loop processes 29 bytes, accumulates a signed sum, and reconstructs the 29-byte key above (verification path via
strcmp). - 0x14d4–0x15c5: Stage 2 allocates a 0x28 buffer, copies 7 bytes from the remaining payload, then for each byte computes:
state = (0x214f*state + sum) mod 0x26688d; out[i] = in[i] ^ (state & 0xf)and writes 7 bytes togold.txt.
Attempts to use tracing were sandbox-blocked, so analysis stayed static:
1gdb -q ./athanor # no symbols; ptrace blocked here
2strace -s 80 ./athanor # ptrace blocked in sandboxOverview Diagram
High-level flow of the transformation:
1+---------------------+ +-----------------------------+
2| lead.txt | | athanor (ELF, stripped) |
3| MTRLLEAD | seed | -----> | read header + seed (BE) |
4| payload | | stage1: consume 29 bytes |
5| | | - derive 29B key |
6+---------------------+ | - signed sum S of bytes |
7 | stage2: for remaining tail |
8 | state = (0x214F*state+S) |
9 | mod 0x26688D |
10 | out[i] = in[i] ^ (state |
11 | & 0xF) |
12 +-------------+---------------+
13 |
14 v
15 (flag)Step 4: Model the Transform in Python
Recreate Stage 1 to confirm the embedded 29-byte key and compute the signed sum of the first 29 payload bytes (result: 2245).
1from pathlib import Path
2data = Path('lead.txt').read_bytes()
3payload = bytearray(data[12:])
4base = 0x40
5key_len = 29
6signed_sum = 0
7idx = 0
8for i in range(key_len):
9 b = payload[idx]; idx += 1
10 signed_sum += b if b < 0x80 else b - 0x100
11 # complex per-byte transform (matches key in .rodata)
12 t = base ^ ((base + i + b) & 0xff)
13 h = ((t*3) >> 8) & 0xff
14 t2 = (((t - h) & 0xff) >> 1) & 0xff
15 t2 = (t2 + h) & 0xff
16 t2 = (t2 >> 6) & 0xff
17 t3 = ((t2 << 7) - t2) & 0xff
18 out = (t - t3 + 1) & 0xff
19print('sum =', signed_sum)
20print('stage1 consumed =', idx)Stage 2 on the next 7 bytes reproduces gold.txt but we can also apply it to the entire remaining payload to get the flag.
1from pathlib import Path
2data = Path('lead.txt').read_bytes()
3seed = int.from_bytes(data[8:12], 'big')
4payload = bytearray(data[12:])
5base, key_len = 0x40, 29
6signed_sum = sum((b if b < 0x80 else b-0x100) for b in payload[:key_len])
7tail = payload[key_len:]
8state = seed
9res = bytearray()
10for b in tail:
11 state = (0x214f*state + (signed_sum & 0xffffffff)) & 0xffffffff
12 state %= 0x26688d
13 res.append(b ^ (state & 0xf))
14print(res.decode('latin1'))Output:
1HTB{Sp1r1t_0f_Th3_C0d3_Aw4k3n3d}\x0c- strip the
\x0c
Rusted Oracle [easy]
An ancient machine, a relic from a forgotten civilization, could be the key to defeating the Hollow King. However, the gears have ground almost to a halt. Can you restore the decrepit mechanism?
Step 1: Explore the Challenge Files
List contents and identify the target binary.
1file rusted_oracleFindings:
rusted_oracle: 64-bit PIE ELF, dynamically linked, not stripped.
Step 2: Baseline Runtime Behavior
Run the binary to see prompts and interaction.
1./rusted_oracle
2printf 'test\n' | ./rusted_oracleOutput snippet:
1A forgotten machine still ticks beneath the stones.
2Its gears grind against centuries of rust.
3
4[ a stranger approaches, and the machine asks for their name ]
5> [ the machine falls silent ]Observation: It asks for a name, then falls silent if the input is wrong.
Step 3: Static Recon for Hints
Look for embedded strings and constants.
1strings -a rusted_oracle | head -n 50
2objdump -s -j .rodata rusted_oracleInteresting .rodata excerpt:
1Contents of section .rodata:
2 2000 01000200 4f6e2061 20727573 74656420 ....On a rusted
3 2010 706c6174 652c2066 61696e74 206c6574 plate, faint let
4 2020 74657273 20726576 65616c20 7468656d ters reveal them
5 2030 73656c76 65733a20 25730a00 4120666f selves: %s..A fo
6 ...
7 20e0 00726561 6400436f 7277696e 2056656c .read.Corwin Vel
8 20f0 6c005b20 74686520 67656172 73206265 l.[ the gears be
9 2100 67696e20 746f2074 75726e2e 2e2e2073 gin to turn... s
10 2110 6c6f776c 792e2e2e 205d0a00 5b207468 lowly... ]..[ thThe name Corwin Vell appears near other UI text, suggesting a magic input.
Step 4: Disassemble Control Flow
Disassemble main to confirm the check and follow-on logic.
1gdb -batch -ex 'file rusted_oracle' -ex 'disassemble main'Key points from main:
- Reads up to 0x3f bytes into a 0x40 buffer, trims trailing newline.
strcmp(input, CONST_AT_0x20E6): if zero, prints “[ the gears begin to turn… ]” and callsmachine_decoding_sequence.- Else prints a failure message and exits.
Given the .rodata placement, the expected name is Corwin Vell.
Step 5: Analyze the Decoding Routine
Disassemble the function that prints the final message.
1gdb -batch -ex 'file rusted_oracle' -ex 'disassemble machine_decoding_sequence'Pseudo-logic reconstructed from the assembly (loop over 24 QWORDs at enc = 0x4050):
1for i in range(24):
2 v = enc[i]
3 v ^= 0x524e
4 v = ror64(v, 1)
5 v ^= 0x5648
6 v = rol64(v, 7)
7 v >>= 8
8 out[i] = v & 0xff
9print("On a rusted plate, faint letters reveal themselves: %s" % out)Dump the enc array from memory:
1gdb -batch -ex 'file rusted_oracle' -ex 'x/24gx 0x4050'Resulting QWORDs:
10x000000000000fffe
20x000000000000ff8e
30x000000000000ffd6
40x000000000000ff32
50x000000000000ff12
60x000000000000ff72
70x000000000000fe1a
80x000000000000ff1e
90x000000000000ff9e
100x000000000000fe1a
110x000000000000ff66
120x000000000000ffc2
130x000000000000fe6a
140x000000000000ffd2
150x000000000000fe0e
160x000000000000ff6e
170x000000000000ff6e
180x000000000000fe4e
190x000000000000fe5a
200x000000000000fe5a
210x000000000000fe1a
220x000000000000fe5a
230x000000000000ff2a
240x0000000000000000Step 6: Recreate the Transform and Recover the Flag
Implement the exact bitwise pipeline in Python and run it over the constants. A trailing padding byte is discarded.
Create the decoder:
1ENC_VALUES = [
2 0x000000000000fffe,
3 0x000000000000ff8e,
4 0x000000000000ffd6,
5 0x000000000000ff32,
6 0x000000000000ff12,
7 0x000000000000ff72,
8 0x000000000000fe1a,
9 0x000000000000ff1e,
10 0x000000000000ff9e,
11 0x000000000000fe1a,
12 0x000000000000ff66,
13 0x000000000000ffc2,
14 0x000000000000fe6a,
15 0x000000000000ffd2,
16 0x000000000000fe0e,
17 0x000000000000ff6e,
18 0x000000000000ff6e,
19 0x000000000000fe4e,
20 0x000000000000fe5a,
21 0x000000000000fe5a,
22 0x000000000000fe1a,
23 0x000000000000fe5a,
24 0x000000000000ff2a,
25 0x0000000000000000,
26]
27
28MASK64 = 0xFFFFFFFFFFFFFFFF
29
30def decode(values):
31 out = []
32 for v in values:
33 v ^= 0x524E
34 v = ((v >> 1) | ((v & 1) << 63)) & MASK64 # ror 1
35 v ^= 0x5648
36 v = ((v << 7) & MASK64) | (v >> (64 - 7)) # rol 7
37 v >>= 8
38 out.append(v & 0xFF)
39 return bytes(out[:-1])
40
41if __name__ == '__main__':
42 print(decode(ENC_VALUES).decode('ascii'))
43PY1python3 exploit.pyOutput:
1HTB{sk1pP1nG-C4ll$!!1!}