Reverse Engineering: License Key Crackme - CTF Writeup
| Category: REVERSING | Flags: 2 (User / Root) |
Challenge: https://hackerdna.com/labs/reverse-engineering-crackme
Contents
- Reconnaissance
- Static Analysis: The ARM64 Binary
- Recovering the User Flag
- Reverse-Engineering derive_key
- Forging the Admin License
Reconnaissance
The web service is Sentinel License Manager. It provides a download of the offset activation client and documents the CLI contract:
./sentinel-activate --account <name> --key <SENT-XXXX-XXXX-XXXX>
A demo trial:
- Account:
trial - License key:
SENT-3B00-1C47-EF00
The binary is an aarch64 (ARM64) ELF, not stripped, with useful symbols:
decode_token 4140
derive_key 4140
validate 4140
main 4140
g_token_enc 36-byte object in .rodata
Static Analysis: The ARM64 Binary
Load it with a disassembler that understands ARM64. The relevant functions:
decode_token(buf)— XORs each byte ofg_token_encwith0x5A.derive_key(account, out)— builds theSENT-XXXX-XXXX-XXXXkey from an account name.validate(account, key)— derives the expected key and compares it (case-insensitively) with the user-provided key.main— parses--account,--key, and a hidden--diag.
Recovering the User Flag
main’s --diag branch calls decode_token then prints straight from
g_token_enc:
"User Flag: %s\n" // 0x400d60
g_token_enc (36 bytes at 0x400cc0):
62 63 3b 3c 6c 6e 3c 6d 77 38 39 38 3e 77 6e 63
6a 3b 77 62 6f 6e 68 77 3f 38 6f 3e 3f 6a 3f 3e
6e 6c 39 3f
XOR each byte with 0x5A:
python3 -c "
tok=bytes.fromhex('62633b3c6c6e3c6d773839383e776e636a3b77626f6e68773f386f3e3f6a3f3e6e6c393f')
print(bytes(b^0x5a for b in tok).decode())
"
USER FLAG: 89af64f7-bcbd-490a-8542-eb5de0ed46ce
Reverse-Engineering derive_key
derive_key initializes a 6-byte accumulator to zero, then for each
character of the account name computes:
n = len(account)
out = [0]*6
j = 0
for i, ch in enumerate(account_bytes):
b = ch
t = ((b & 0x7f) << 1) + b # ubfiz + add
b = (t + ((t & 0x1f) << 3)) & 0xff
b = (b + 0x3d) & 0xff
b ^= 0x47
b = (b + i) & 0xff
b ^= n
out[j] ^= b
j = (j + 1) % 6
The 6 accumulator bytes are rendered as 12 hex nibbles (using the
0123456789ABCDEF table) and formatted with
SENT-%c%c%c%c-%c%c%c%c-%c%c%c%c.
Implementing exactly that and checking against the documented demo key is the sanity check:
'trial' -> SENT-3B00-1C47-EF00 (matches the site's trial license!)
Forging the Admin License
The site says premium modules are reserved for the admin account. Compute the
admin key the same way:
'admin' -> SENT-3A8A-F81F-9100
Submitting it to the web activation endpoint:
curl -s "http://TARGET/activate" -X POST \
-d "account=admin&license_key=SENT-3A8A-F81F-9100"
{
"account": "admin",
"message": "Premium license activated. All modules unlocked.",
"root_flag": "f8694e5c-efef-4272-83c2-1101cfad5fd3",
"status": "success",
"tier": "premium"
}
Root Flag: f8694e5c-efef-4272-83c2-1101cfad5fd3
How the Attack Works
- Hardcoded secrets — an encrypted user flag (
g_token_enc) is embedded in the binary under a trivial XOR. - Deterministic key generation —
derive_keyis a pure function of the account name with no server randomness, so any account’s license can be computed offline. - Trusted client = trusted gate — the web backend accepts whatever key the client says is valid and unlocks “admin” with a handful of AArch64 instructions reversed.
Key Takeaways
- Treat client-side logic as public. Any secret in a binary is recoverable; XOR obfuscation is security theater.
- License keys with no HSM/offline signature derived from a static algorithm can always be forged. Use asymmetric signatures validated server-side.
- ARM64 reversing is just like x86 once the calling convention (x0-x7,
w0-w31, x29/x30) is clear — include the little-endian
.rodataandUBFIZ/UBFXbit tricks. - Always validate a reverse engineered algorithm against a known-good example
(here the
trialkey) before trusting it.