Daddy-Long-Arms
Daddy-Long-Arms
Platform: spbctf | Category: Reversing | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-06-24 | Status: Solved Techniques: ld_preload_hook, rand_sequence_forcing, static_aarch64_analysis, xor_decryption
Summary
Task: AArch64 Tigress-obfuscated ELF that XOR-decrypts a flag with a user-supplied key, but hides a backdoor lucky branch gated behind six fixed rand() return values. Solution: run the binary natively on Apple-Silicon arm64 in Docker and LD_PRELOAD a shim that makes rand() emit the magic sequence, forcing the lucky branch to decrypt the flag with the embedded correct key.
Daddy-Long-Arms — spbctf
Description
qemu-aarch64 -L /usr/aarch64-linux-gnu ./daddy_long_arms.elf
An ARM AArch64 binary (SPbCTF rev-kids 2020, rev/new7) decrypts a flag using a
key you pass on the command line. With a wrong key you get garbage. The title
"Daddy-Long-Arms" puns on the ARM architecture. Goal: recover the flag
without knowing the correct key.
Analysis
Recon
$ file daddy_long_arms.elf
ELF 64-bit LSB pie executable, ARM aarch64, dynamically linked,
interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 3.7.0,
GLIBC_2.17, with debug_info, not stripped, GCC (Debian 8.3.0)
- Not stripped; the source was processed by Tigress (the binary contains a
tigress.cmarker and the typical_1_obfuscated_*_$array/_$stringsdata tables of a Tigress stack-VM interpreter). - Imports:
srand,rand,time,malloc,printf,puts,__libc_start_main. - Symbols of interest:
main@0x8f4getflag@0xc70obfuscated_getflag@0x97cobfuscated_getkey@0xe44- data tables
_1_obfuscated_getflag_$array/$strings,_1_obfuscated_getkey_$array/$strings.
- Strings hint the logic:
"Key must be specified as a parameter","Flag decrypted with your key:","luck=1" .. "luck=5","You're lucky today!","You don't need to guess the correct key, we're giving you the flag for free".
Apple LLVM objdump disassembles elf64-littleaarch64 fine:
objdump -d daddy_long_arms.elf
Program logic
main:
malloc(35)for the output buffer.- Requires
argc > 1, else prints"Key must be specified as a parameter". - Loads
argv[1](the user key). - Calls
rand() & 0xffto get a random byte. - Calls
getflag(out_buf, argv[1], rand_byte). - Prints
"Flag decrypted with your key: %s".
getflag(out, key, randbyte):
-
Allocates
buf40andbuf38(35 bytes each). -
Calls
obfuscated_getflag(buf40)— fillsbuf40with the encrypted flag bytes. Importantly this function begins withsrand(time(0))(a Tigress stack-VM interpreter that rebuilds bytes from its$array/$stringsdata). -
Calls
rand()six times and compares the results against a fixed sequence: call value (dec) value (hex) 1 31337 0x7a69 2 12061 0x2f1d 3 13415 0x3467 4 23353 0x5b39 5 23817 0x5d09 6 3421 0x0d5d -
If all six match → the "lucky" branch: prints
luck=1..5, the two free-flag messages, callsobfuscated_getkey(buf38)to obtain the correct key, then computesout[i] = buf40[i] ^ buf38[i](encrypted_flag XOR correct_key) = the real flag, for free. -
Otherwise (
b.ne -> 0xdd8) it computesout[i] = buf40[i] ^ key[i]with the user-supplied key → garbage unless you already know the key.
The trick / backdoor
The author left a backdoor "lucky" branch that decrypts the flag with the
embedded correct key, but gated it behind six specific rand() return values.
Because obfuscated_getflag re-seeds with srand(time(0)), the rand()
sequence is normally uncontrollable, so the lucky branch effectively never
fires — but we can force it.
Solution
-
The binary runs natively on Apple-Silicon (arm64) inside Docker with
--platform linux/arm64— noqemu-userneeded. Agcc:12arm64 image also lets us compile the LD_PRELOAD hook. -
Write an
LD_PRELOADshim that overridessrand()(reset the index) andrand()to emit the magic sequence in order. Ordering note:
main'srand()runs beforeobfuscated_getflag'ssrand(). Oursrand()hook resets the index back to 0, so the six checkedrand()calls ingetflagreceive exactly the sequence — regardless of the one earlierrand()consumed bymain.
c read-only • copy allowed// hook.c #define _GNU_SOURCE #include <stdlib.h> static int idx = 0; static int seq[] = {31337, 12061, 13415, 23353, 23817, 3421}; void srand(unsigned int s){ (void)s; idx = 0; } int rand(void){ if (idx < (int)(sizeof(seq)/sizeof(seq[0]))) return seq[idx++]; return 0; }
- Build and run (inside the arm64 container):
sh read-only • copy allowedgcc -shared -fPIC -o hook.so hook.c -ldl chmod +x daddy_long_arms.elf LD_PRELOAD=./hook.so ./daddy_long_arms.elf AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
- Output:
luck=1
luck=2
luck=3
luck=4
luck=5
You're lucky today!
You don't need to guess the correct key, we're giving you the flag for free
Flag decrypted with your key:
SPbCTF{REDACTED}
The lucky branch ignores our AAAA... key entirely and XORs the encrypted flag
with the embedded correct key returned by obfuscated_getkey.
Flag wordplay
REDACTED is a pun: forcing the lucky branch effectively
NOP'd the random gate — and the binary is ARM. The title
"Daddy-Long-Arms" is itself an ARM pun.