Skattkistan bot
This write-up covers the solve from first contact with the file, through reversing the Amiga executable, recovering the correct movement path, and extracting the final flag.
skattkistan
undut{...}
1. Initial Triage
The first step was to locate the file the challenge referred to. In the
workspace, the likely target was skattkistan.
rg --files | rg 'skat'
That identified two useful artifacts in the directory: the executable itself and a Windows zone marker:
/home/userhonest/undutmaning/skattkistan
/home/userhonest/undutmaning/skattkistan:Zone.Identifier
Running file on it showed that this was not a text file or a
normal Linux ELF binary:
file skattkistan
skattkistan: AmigaOS loadseg()ble executable/binary
That immediately changed the approach. Instead of treating it as a script or archive, this had to be reversed as an old Amiga executable.
A quick direct flag search also failed, which was useful because it ruled out the simplest case where the flag was embedded as a visible string:
strings -n 4 skattkistan | rg 'undut\{|flag|FLAG|Undut'
2. Pulling Strings
The next step was to inspect the binary for visible strings. Even when a program is compiled, user-facing messages usually survive.
strings -n 3 skattkistan
This revealed Swedish text such as:
Du är vid ett vägskälVilken väg [h=höger, v=vänster]?Du hittade en pärla!Du hittade ett silvermynt!Du hittade ett halsband!Du hittade skattkistan!Du är vilse
From that alone, the high-level behavior was already clear: the binary was
implementing a left/right path game, where the user enters h
or v and eventually reaches different treasure events.
3. Disassembly and Structure
Since the file was an Amiga executable, I used radare2 with
the m68k architecture. The binary was small and mostly
stripped, but still easy enough to inspect manually.
which r2
/usr/bin/r2
r2 -q -a m68k -b 32 skattkistan
aaa
pd
The important findings were:
- The program reads one byte at a time and accepts
horv. - It compares the user path against several hardcoded 32-byte values.
- If the correct path is reached, it prints one of the treasure messages.
- The chest path triggers a hidden decode routine instead of only printing a message.
4. Understanding the Hash Check
At first glance the 32-byte constants looked like plain SHA-256 digests,
and they were. The subtle part was that the binary was not checking
SHA256(path). It was checking:
SHA256(SHA256(path))
That mattered because an early brute-force using only one SHA-256 layer gave no useful hits. After re-reading the control flow, it became clear that the program hashed the move string once, then hashed the 32-byte result again before comparing to the embedded constants.
5. Recovering the Paths
Once the double-hash behavior was understood, I wrote a small native
brute-forcer in C using OpenSSL to test candidate strings over the alphabet
{h, v}.
digest = SHA256(SHA256(candidate))
That quickly recovered the known treasure checkpoints:
- Pearl:
h - Silver coin:
hhv - Necklace:
hhvvhvvhhhv
The sequence of hits was useful beyond just confirming that the brute-force worked. Each success produced a later treasure message than the previous one, which strongly suggested the program was walking one persistent route state forward rather than validating unrelated standalone inputs.
h, hhv, and
hhvvhvvhhhv behave like checkpoints on the same maze path.
That is why extending the necklace route was a better search strategy than
restarting from scratch over the full input space.
Those hits strongly suggested the challenge was a single consistent route where each treasure is an intermediate checkpoint. So instead of brute-forcing every possible string for the chest, I restricted the search to strings that extended the necklace path.
That produced the chest route:
hhvvhvvhhhvvvhhvhvvvhhvhvhhhvvvhhvhhhv
That route is important for two separate reasons: it satisfies the final treasure check, and it also becomes the material for the decode key used in the hidden payload routine.
6. Hidden Decode Routine
Reaching the chest was not the final step. The binary then used a hidden decode routine. The important detail here was that the XOR key was not the double hash used for verification. It used the first hash:
key = SHA256(chest_path)
With the chest route recovered, the next step was:
- Compute
SHA256(path)for the recovered chest path. - Use that 32-byte digest as the XOR key.
- Scan the binary for a 32-byte block that decodes into printable text.
That led directly to the hidden payload inside the binary.
python3 - <<'PY'
import hashlib
from pathlib import Path
path=b'hhvvhvvhhhvvvhhvhvvvhhvhvhhhvvvhhvhhhv'
p=Path('skattkistan').read_bytes()
key=hashlib.sha256(path).digest()
print('key', key.hex())
for off in range(0, len(p)-32):
pt=bytes(p[off+i]^key[i] for i in range(32))
if b'undut{' in pt:
print('hit', hex(off), pt)
PY
key = 8facdb20194c3a1bdae1cf30f8877f1cf98af74fbf2a2a2788712bc8ef43193e
offset = 0x964
decoded = undut{Tr3a5ur3-PzH153KGlPFuZMR}
This verification step matters because it proves the final stage was not a guessed decryption. The correct path-derived hash cleanly reveals a printable flag at a single concrete offset in the binary.
7. Final Flag
After decoding the payload with the correct XOR key, the flag was:
undut{Tr3a5ur3-PzH153KGlPFuZMR}
8. Summary
The challenge looked simple at first because of the visible strings, but the actual protection was the path verification logic and the extra hidden decode step at the chest. The key solve steps were:
- Identify the file as an AmigaOS executable.
- Extract strings to understand the input model and treasure events.
- Reverse the hash logic and notice the binary checks
SHA256(SHA256(path)). - Brute-force the path over
handv. - Use
SHA256(chest_path)as the XOR key for the final payload. - Recover the flag from the binary.