Summary
So, in this challenge, we were given a file named Sudoku.exe. I used Ghidra to open the game file. It turned out that the flag was hidden within the program as random data that had been strongly encrypted. To crack the encryption, I used a Python script to reverse the processing steps.
Vulnerability Analysis
In the decompiled output, I found several key sections that serve as loopholes for obtaining the flag without having to play the game:
FUN_140001f0b(Win condition and secret trigger)
if (DAT_1400092e8 == 0x539) {
FUN_140001e71();
}
DAT_140006000(Encrypted data payload)
DAT_140006000 XREF[3]: 1400001e4(*),
FUN_140001e71:140001e95(*),
FUN_140001e71:140001e9c(*)
140006000 4b ?? 4Bh K
140006001 ca ?? CAh
140006002 bb ?? BBh
140006003 9f ?? 9Fh
140006004 aa ?? AAh
140006005 ab ?? ABh
140006006 69 ?? 69h i
140006007 db ?? DBh
140006008 af ?? AFh
140006009 bf ?? BFh
14000600a aa ?? AAh
14000600b ca ?? CAh
14000600c ab ?? ABh
14000600d 69 ?? 69h i
14000600e af ?? AFh
14000600f 1a ?? 1Ah
140006010 db ?? DBh
140006011 69 ?? 69h i
140006012 ca ?? CAh
140006013 4a ?? 4Ah J
140006014 69 ?? 69h i
140006015 af ?? AFh
140006016 fb ?? FBh
140006017 8f ?? 8Fh
140006018 ea ?? EAh
140006019 69 ?? 69h i
14000601a bb ?? BBh
14000601b af ?? AFh
14000601c fb ?? FBh
14000601d af ?? AFh
14000601e 7a ?? 7Ah z
14000601f 69 ?? 69h i
140006020 cb ?? CBh
140006021 2a ?? 2Ah *
140006022 9f ?? 9Fh
140006023 da ?? DAh
140006024 cb ?? CBh
140006025 ab ?? ABh
140006026 2b ?? 2Bh +
140006027 a8 ?? A8h
140006028 a8 ?? A8h
- The 42-byte data is intentionally chained through three stages, such as:
- Array Reversal (FUN_140001d1a)
void FUN_140001d1a(longlong param_1,int param_2) { undefined1 uVar1; undefined4 local_10; undefined4 local_c; for (local_c = 0; local_10 = param_2 + -1, local_c < local_10; local_c = local_c + 1) { uVar1 = *(undefined1 *)(param_1 + local_c); *(undefined1 *)(param_1 + local_c) = *(undefined1 *)(param_1 + local_10); *(undefined1 *)(local_10 + param_1) = uVar1; param_2 = local_10; } return; }- XOR Cipher (FUN_140001d98)
void FUN_140001d98(longlong param_1,int param_2) { int iVar1; undefined4 local_c; iVar1 = DAT_1400092e8 * 7; for (local_c = 0; local_c < param_2; local_c = local_c + 1) { *(byte *)(param_1 + local_c) = *(byte *)(param_1 + local_c) ^ (byte)((iVar1 + 0xd) % 0x100); } return; }- Nibble Swap (FUN_140001e0a)
void FUN_140001e0a(longlong param_1,int param_2) { undefined4 local_c; for (local_c = 0; local_c < param_2; local_c = local_c + 1) { *(byte *)(param_1 + local_c) = *(byte *)(param_1 + local_c) >> 4 | *(char *)(param_1 + local_c) << 4; } return; }
After discovering the method and steps involved in the locking process, I used a Python script to reverse those three locking processes.
data = bytes.fromhex("4bcabb9faaab69dbafbfaacaab69af1adb69ca4a69affb8fea69bbaffbaf7a69cb2a9fdacbab2ba8a838") # Replace with 42 bytes in hex
key = 0x9C
# Step 1: reverse
rev = data[::-1]
# Step 2: XOR
xored = bytes([b ^ key for b in rev])
# Step 3: swap nibble
flag = bytes([((b & 0x0F) << 4) | ((b & 0xF0) >> 4) for b in xored])
print(flag.decode('ascii'))
FLAG: JCC{sud0ku_n3v3r_g1v3_me_th3_sec23t_sc0re}