1. Initial Inspection and Static Analysis
I began by dumping readable strings from the file with the standard strings command.
strings crackme1
Figure 1: strings output
The output openly contained the string FLAG{Tebrikler_Dogru_Flag}. I also found no success or failure messages such as congratulations or an incorrect-flag warning. A flag this conspicuous suggested a possible rabbit hole.
2. Dynamic Analysis and Anti-Debugging Findings
To test that suspicion, I ran the file under GDB (GNU Debugger) and also attempted to trace its dynamic-library calls with ltrace.
Figure 2: Fake flag
When launched in GDB, the program requested a flag. Entering FLAG{Tebrikler_Dogru_Flag} from the strings output produced the message Tunelin ucu :) https://www.youtube.com/watch?v=3ksNL9uJssw. The linked Kemal Sunal film scene mockingly confirmed that this was indeed a trap. In addition, the SIGTRAP interrupt raised while the program was running in GDB demonstrated the presence of an intentional anti-debugging mechanism and showed that debugger detection had redirected execution into a fake path. I moved to Ghidra to recover the actual flag.
Figure 3: The tunnel led somewhere unpleasant
3. Ghidra and Algorithm Analysis
After loading and decompiling the file in Ghidra, the protection mechanisms became clear. I located the entry point and followed it into the next function, where the welcome message and related logic identified the body of main.
signal(5,FUN_001011bc);
FUN_00101159("=== Trackbook Crackme 1 ===\n");
FUN_00101159("Lutfen flag giriniz: ");
FUN_00101199(local_98,0x3f);
local_fc = 0;
do {
if (0x3f < local_fc) {
LAB_001015c8:
pcVar1 = (code *)swi(3);
(*pcVar1)();
return;
// Ghidra decompile işlemini burada kesti!
Protection 1: Exception-Based Control Flow
The program registered a SIGTRAP handler with signal(5, ...), then immediately invoked swi(3), whose assembly equivalent is the INT 3 interrupt.
- During normal execution, the operating system catches this
INT 3, invokes the handler, and sets a variable to true in the background. - During dynamic analysis, GDB consumed the exception before the handler could run, leaving the control variable at zero. This caused execution to follow the path that exposed the fake flag.
Repairing the Decompiler with NOP Patching
When Ghidra encountered the INT 3 instruction, it assumed that control flow ended there and did not decompile the actual validation blocks. I patched the assembly to expose the remaining code.
Original Assembly Flow:
001015bd 7e 09 jle LAB_001015c8
001015bf 8b 45 fc mov eax,dword ptr [rbp + local_fc]
...
LAB_001015c8
001015c8 cc INT 3 ; Breakpoint
I replaced the CC opcode with 90:
Patched Assembly Flow:
LAB_001015c8
001015c8 90 NOP ; Ghidra artık buraan sonra devam edicek
Ghidra then reanalyzed the function and revealed the real validation block after the interrupt. The updated decompilation showed that the application branched on the state variable. Following the hidden function call in the legitimate path led to FUN_001012fe and FUN_001011d0.
if (0x3f < local_fc) {
code_r0x001015c9:
if (_DAT_0010402c == 1) {
iVar1 = FUN_001012fe(local_98);
if (iVar1 == 0) {
FUN_001011d0(&local_f8,0x13);
}
else {
FUN_001011d0(&local_58,0x42);
}
}
else {
iVar1 = strcmp(local_98,"FLAG{Tebrikler_Dogru_Flag}");
if (iVar1 == 0) {
FUN_001011d0(&local_d8,0x3d);
}
else {
FUN_001011d0(&local_f8,0x13);
}
}
4. String Obfuscation (XOR) and Flag Recovery
Inside the real validation function (FUN_001012fe), which executes when no debugger is detected, the actual flag was not stored in plaintext.
Protection 2: In-Memory XOR Obfuscation
The decompiled C code was as follows:
local_28[0] = 0x7c;
local_28[1] = 0x6b;
//diğer byte lar
local_28[0xf] = 0x4c;
local_30 = 0;
do {
//uzunluk ve güvenlik kontrolü
if ((int)(char)(*(byte *)(param_1 + local_30) ^ 0x31) != (uint)local_28[local_30]) {
uVar1 = 0;
goto LAB_0010139f; //hatalı flag
}
local_30 = local_30 + 1;
} while( true );
The program XORed each character of the user’s input with the 0x31 key and compared the result against a statically embedded encrypted hexadecimal array. I also determined that displayed messages were decrypted at runtime with the 0x55 key before being printed.
void FUN_001011d0(long param_1,int param_2)
{
long in_FS_OFFSET;
int local_9c;
byte local_98 [136];
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
//local_98 buffer'ının 0 ile doldurulduğu kısımlar]
for (local_9c = 0; local_9c < param_2; local_9c = local_9c + 1) {
local_98[local_9c] = *(byte *)(param_1 + local_9c) ^ 0x55;
}
local_98[param_2] = 0;
FUN_00101159(local_98); //Şifresi çözülen metnin ekrana basılması
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
__stack_chk_fail();
}
return;
}
I recovered the real flag by applying XOR with the 0x31 key to the extracted static hexadecimal array in CyberChef:
- Input Hex:
7c 6b 67 68 4a 78 5f 54 5a 6e 62 50 53 50 5f 4c - XOR Key:
31(Hex)
Figure 3: Real flag
Decrypted Real Flag: MZVY{Inek_Saban}
5. Verification
To verify the recovered flag, I ran the program through the normal terminal flow without a debugger.
Figure 5: Verification of the real flag
I accept the creator’s approval and take my seat. :)
Figure 6: Well done, Şaban