Reverse Engineeringmedium
ARM_Obfuscated
by Pavel Knespl (capyplivl)
A 67 KB stripped aarch64 PIE password checker wrapped in anti-debug. The check is a Blowfish-CTR style stream cipher whose keystream is tied to the buffer address, so it is solved by emulating the check function with Unicorn instead of running the binary.
Solve
- Triage in radare2.
main(0xac0) isif (argc==2 && check(argv[1])==1) puts("You solved the ARM64 Medium+ binary!");.check(0x1208) does anti-debug (signal(SIGTRAP)+raise,clock_gettimetiming > 500 fail,ptrace(PTRACE_TRACEME), an internalfcn.00000f00), then builds a 52-byte expected blob in a loop at0x1240and callstransform(buf, 52, key=0x123456789abcdef0)at0xc8c, then compares 52 bytes. - Read the blob construction. Each byte is
sbox[idx_table[i]] ^ rolling_key ^ 0x3a,rolling_keystarts0x54and increments by0x17per byte, tables at0x1490/0x14d0. No string literals. - Identify
transform(0xc8c) as 16-round Blowfish (F=((S0[a]+S1[b])^S2[c])+S3[d]) used in CTR/OFB mode: it encrypts the block index and XORs the result into the data. So it is a stream cipher andflag = blob XOR keystream. - Emulate
checkwith Unicorn. Map both PT_LOAD segments at their vaddrs, allocate a stack, enable NEON. Add a code hook that stubs everyBL(return 0) except the realbl 0xc8c, and captures the transformed buffer (x21) and expected buffer (x20) just before the compare loop at0x1364. - Force the NEON cipher path.
transformreads a CPU-dispatch flag at0x200b0(file holds1, which routes to a scalar decoy thatmemsets the output). Zero it:mu.mem_write(0x200b0, b'\x00\x00\x00\x00'). - The keystream is address-dependent, so generate it in the exact calling context (input at
sp+0x90). Run the wholecheckon a zero input to get keystream + blob, XOR, then self-verify.
_, c = run_check(b'\x00'*52)
flag = xor(c['expected'], c['keystream'])
r, c2 = run_check(flag)
assert r == 1 and c2['transformed'] == c2['expected']
print(flag)
Flag: SVIUSCG{Even_an_0bFusC4T3D_ARM64_bin_is_No_Match_4U}