Summary
In this challenge, we are given a file named tinytrance.exe. The program requests a 32-character authorization code and divides the task into two parts: the “parent,” which is responsible for receiving input from the user, and the “worker” (child), which is responsible for verifying the correctness of the input via a dedicated communication channel.
I analyzed it using Ghidra and found that the locking logic works by randomizing the positions of the input characters, adding a constant value, and then performing an XOR operation on the result before comparing it to the target data in memory.
Vulnerability Analysis
The main weakness of this program is that the verification logic is static (deterministic) and all its locking variables are stored in raw form within the binary.
Based on the decompilation results for the FUN_140001480 function, the program performs a loop to check 32 characters of input following this flow:
- Fetching a random index from the permutation table
DAT_140019340 - Applying the XOR operation to the input character (
local_48) with the XOR key (local_90) - Adding the result with the addend value from
DAT_140019360 - Comparing the final result against the target data (
local_90[uVar2 + 0x20])
Here is a snippet of the logic from the decompiled output in Ghidra:
- Decompile
FUN_140001480(32-byte input validation loop)
do {
uVar2 = (ulonglong)(byte)(&DAT_140019340)[uVar3];
if ((byte)((local_90[uVar2] ^ local_48[uVar2]) + (&DAT_140019360)[uVar2]) !=
local_90[uVar2 + 0x20]) {
local_97[0] = 0;
goto LAB_14000161f;
}
uVar4 = (int)uVar3 + 1;
uVar3 = (ulonglong)uVar4;
} while ((int)uVar4 < 0x20);
Next, when examining the address DAT_140019360 in Ghidra, it appears that the appended data is stored in raw form as repeating patterns of 0x13 and 0x29 throughout the first 32 bytes.
DAT_140019360
DAT_140019360 XREF[1]: FUN_140001480:140001604(*)
140019360 13 ?? 13h
140019361 29 ?? 29h )
140019362 13 ?? 13h
140019363 29 ?? 29h )
140019364 13 ?? 13h
140019365 29 ?? 29h )
140019366 13 ?? 13h
140019367 29 ?? 29h )
140019368 13 ?? 13h
140019369 29 ?? 29h )
14001936a 13 ?? 13h
14001936b 29 ?? 29h )
14001936c 13 ?? 13h
14001936d 29 ?? 29h )
14001936e 13 ?? 13h
14001936f 29 ?? 29h )
140019370 13 ?? 13h
140019371 29 ?? 29h )
140019372 13 ?? 13h
140019373 29 ?? 29h )
140019374 13 ?? 13h
140019375 29 ?? 29h )
140019376 13 ?? 13h
140019377 29 ?? 29h )
140019378 13 ?? 13h
140019379 29 ?? 29h )
14001937a 13 ?? 13h
14001937b 29 ?? 29h )
14001937c 13 ?? 13h
14001937d 29 ?? 29h )
14001937e 13 ?? 13h
14001937f 29 ?? 29h )
Since the XOR key, the addend, and the target have already been obtained, we can crack it by reversing the process from back to front—that is, subtracting the addend from the target, then performing a reverse XOR operation with the XOR key to restore the original 32-character code.
#!/usr/bin/env python3
import sys
# ======== CONSTANTS IN BINARY ========
# local_90 (64 byte)
local_90 = bytes([
0x84, 0x5b, 0xf9, 0x9a, 0xc9, 0x50, 0xeb, 0x9b,
0x6c, 0x81, 0x41, 0x56, 0xe3, 0x49, 0xd9, 0x44,
0x01, 0x25, 0x29, 0xfd, 0x5c, 0xda, 0xf8, 0x41,
0x5f, 0x65, 0xdf, 0xc4, 0x2e, 0xa7, 0x83, 0xbd,
0xe1, 0x41, 0xcd, 0x0a, 0xc2, 0x89, 0x9a, 0x20,
0x6f, 0x1f, 0x31, 0x8a, 0x9e, 0xa3, 0x99, 0x50,
0x7c, 0x3d, 0x58, 0xc2, 0x16, 0xe1, 0x9d, 0x9b,
0x7e, 0x37, 0xc2, 0x1d, 0x32, 0xf2, 0x0a, 0xe9
])
# perm (DAT_140019340)
perm = bytes([
0x07, 0x01, 0x09, 0x10, 0x0d, 0x1a, 0x16, 0x1f,
0x17, 0x0c, 0x03, 0x00, 0x0e, 0x15, 0x19, 0x12,
0x13, 0x1c, 0x02, 0x1e, 0x0a, 0x05, 0x11, 0x0f,
0x14, 0x06, 0x08, 0x1b, 0x1d, 0x18, 0x04, 0x0b
])
# Extract from Ghidra at address 0x140019360
add = bytes.fromhex("1329132913291329132913291329132913291329132913291329132913291329")
# =========================================================
# ======== CALCULATE INPUT ========
input_bytes = [0] * 32
for i in range(32):
idx = perm[i]
xor_key = local_90[idx]
expected = local_90[idx + 32]
add_val = add[idx]
# ((xor_key ^ input) + add) & 0xFF == expected
input_bytes[idx] = xor_key ^ ((expected - add_val) & 0xFF)
result = bytes(input_bytes)
# ======== OUTPUT (32 byte + newline) ========
sys.stdout.buffer.write(result + b'\n')
FLAG: JCC{f0ll0w_7h3_ch1ld_br34kp01nt}