At PicoCTF 2025, dozens of teams stalled on a 200-point binary challenge because they tried to read raw assembly instead of letting a decompiler do the heavy lifting. Ghidra — NSA’s free reverse engineering framework — turns stripped ELF binaries into readable C pseudocode in seconds. This walkthrough shows you exactly how to use it on two common CTF challenge types: a hardcoded flag check and a simple XOR obfuscation routine.
Setting Up and Finding the Entry Point
Download the challenge binary from your CTF platform. For this walkthrough, assume the file is called crackme, pulled from a competition server at 192.0.2.47 by user ctfplayer.
Before opening Ghidra, run a quick reconnaissance pass in your terminal:
$ file crackme
crackme: ELF 64-bit LSB executable, x86-64, dynamically linked, stripped
$ strings crackme | grep -i flag
Enter the flag:
Wrong flag. Try again.
Congratulations!
file tells you the architecture and that the binary is stripped — no function names survive. strings confirms there is a flag comparison happening somewhere inside. The strings “Wrong flag” and “Congratulations!” are your breadcrumbs. You know there is a branch in the code where one path wins and the other loses.
Open Ghidra, create a new project, import the binary, and let the auto-analysis run with default settings. When it finishes, open the Symbol Tree panel and navigate to Functions > entry. Right-click any string you spotted — such as “Congratulations!” — in the Defined Strings window and choose References > Show References to Address. That jumps you directly to the comparison logic.
Example 1 — Hardcoded Flag Comparison
Click into the function that references your winning string. Ghidra’s decompiler panel on the right will show something like this:
undefined8 FUN_00401180(void)
{
int iVar1;
char local_48 [64];
printf("Enter the flag: ");
fgets(local_48, 0x40, stdin);
local_48[strcspn(local_48, "\n")] = '\0';
iVar1 = strcmp(local_48, "pico{n0t_s0_h4rd_r1ght}");
if (iVar1 == 0) {
puts("Congratulations!");
}
else {
puts("Wrong flag. Try again.");
}
return 0;
}
That is your flag sitting in plain text: pico{n0t_s0_h4rd_r1ght}. The binary is doing a straight strcmp against a hardcoded string — a beginner-level mistake that is surprisingly common in introductory CTF challenges. The decompiler read the assembly branches and reconstructed the logic into C pseudocode you can read in five seconds.
What would an attacker do next? Submit the flag immediately. But more importantly, note the buffer size: local_48 [64] with fgets(..., 0x40, ...) is safe. A real binary might use gets() instead, opening a buffer overflow — that is your next investigation thread if the challenge is worth more points.
Example 2 — XOR Obfuscation Routine
Harder challenges hide the flag behind a simple cipher. In this scenario, a 300-point binary stores an XOR-encoded byte array and decodes it at runtime. The Ghidra decompiler output for the validation function looks like this:
void FUN_004012a0(char *user_input)
{
int i;
char decoded [32];
byte enc_flag [26] = {
0x1b, 0x07, 0x12, 0x1e, 0x7c,
0x36, 0x1c, 0x57, 0x36, 0x1d,
0x57, 0x06, 0x1b, 0x57, 0x36,
0x1b, 0x57, 0x1c, 0x36, 0x06,
0x1e, 0x57, 0x36, 0x07, 0x57, 0x7b
};
for (i = 0; i < 26; i++) {
decoded[i] = enc_flag[i] ^ 0x7f;
}
decoded[26] = '\0';
if (strcmp(user_input, decoded) == 0) {
puts("Congratulations!");
}
}
Ghidra hands you everything: the encrypted byte array and the XOR key (0x7f). You do not need to run the binary at all. Drop into a Python one-liner to decode it yourself:
$ python3 -c "
enc = [0x1b,0x07,0x12,0x1e,0x7c,0x36,0x1c,0x57,0x36,0x1d,
0x57,0x06,0x1b,0x57,0x36,0x1b,0x57,0x1c,0x36,0x06,
0x1e,0x57,0x36,0x07,0x57,0x7b]
print(''.join(chr(b ^ 0x7f) for b in enc))
"
dtf{x0r_1s_n0t_encrypti0n!}
Flag captured without executing a single potentially malicious instruction. This static analysis workflow — decompile, identify the encoding scheme, replicate the decode in Python — works on the majority of introductory and intermediate reversing challenges. When you see a loop iterating over a byte array with a fixed operand, always ask: is this XOR, ROT, or a substitution cipher?
What To Do Now
Download crackmes.one and grab any beginner-rated Linux ELF binary. Import it into Ghidra, let auto-analysis finish, then use Defined Strings to find your first interesting reference. Practice navigating from string to function to decompiled output without touching the assembly view at all. Build that muscle memory now — when a live CTF clock is running, you need the workflow to be automatic.
