Reversing Write-up

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.

Challenge File skattkistan
Category Reversing
Flag Format 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:

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.

The Swedish strings gave the progression of the challenge almost for free: path choice prompt, minor treasure milestones, a treasure chest event, and a failure branch for getting lost.

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 key point was that the binary did not store the full path in plaintext. It stored hash values and compared the user input against those.

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:

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.

In practice, that means 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:

  1. Compute SHA256(path) for the recovered chest path.
  2. Use that 32-byte digest as the XOR key.
  3. 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:

Flag 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: