Ghidra for Beginners: First Binary on CTF

Depov

Moderator
Staff member
MODERATOR
ULTIMATE
SUPREME
PREMIUM
MEMBER
Joined
Feb 18, 2025
Messages
464
Reaction score
738
Deposit
0$
Installation and first launch of Ghidra

Minimum requirements: 8 GB of RAM (from 4 GB will be closely - JVM will eat resources in the autoanalysis of large binary), JDK 21 and ZIP archive Ghidra.

On Linux (Ubuntu/Debian) installation is reduced to three operations: sudo apt update && sudo apt install openjdk-21-jdk for JDK, then download the archive with github.com/NationalSecurityAgency/ghidra/releases, unpacking through unzip ghidra_*_PUBLIC_*.zip and launch ./ghidraRun. On Windows - similar: JDK 21 from Adoptium, ZIP archive, unpacking, launch ghidraRun.bat. No installer, no dependencies.

Check the Java version with a team java -version – must show 21.x. If the version is old or JDK is not worth it, Ghidra will not start. At the first start, the “Tip of the Day” window will pop up – the tips are useful, but you can close. Here is a Ghidra project manager, still empty.
Exploration to disassembler: file, strings, ltrace

Before you get into the disassembler, spend 30 seconds on the command line. This stage of intelligence is missed by almost all beginners – and in vain: sometimes the task on the CTF is closed even before the launch of Ghidra.

Team file crackme will show the file type: ELF 64-bit or PE 32-bit, static or dynamic linking, processor architecture. This information determines which reverse engineering tools are applicable further and in what mode to download the binary.

strings crackme will display all readable text strings from the binary. From the experience of solving problems on PicoCTF and crackmes.one - a surprising number of CTF reverse tasks of the entry level is solved by one of this team. The password or flag is in the binary with an open text. See the format string in the output flag{...} or CTF{...} – check immediately, the task can close in seconds. For Filtration: strings crackme | grep -i flag – will cut off the noise of library lines.



Limitations: ltrace may be absent in the repositories of fresh distributions (Ubuntu 22.04+, Debian 12) – manually. With statically slinked binary (a frequent case on CTF), the utility does not work – for them strace or gdb with breakpoint on plt. Another nuance: modern versions of gcc are sometimes inlined strcmp, and ltrace doesn't see them. But the check takes two seconds – it makes no sense to pass. strace also useful: shows system calls, which will be useful if the program reads the flag from the file or makes network requests.
We create a project and run the analysis of binary in Ghidra

Ghidra organizes work through projects – a binary container and all the results of its analysis. One project per CTF task.

Sequence: File → New Project → Non-Shared Project (you work one) → specify the directory and project name → Finish. Next File → Import File → choose a binary.

Ghidra will automatically determine the format: ELF for Linux, PE for Windows, Mach-O for macOS. Import Results window with metadata will appear: architecture, bit, estimated compiler. You don’t need to change anything – click OK.

A double click on an imported file opens CodeBrowser – the main workspace of Ghidra for binary analysis. The tool will ask: “Analyze now?” – agree, the options leave by default. For PE files, you should look at the list of analyzers in Auto Analysis Options – the set of checkboxes depends on the version of Ghidra; the inclusion of PE-specific analyzers can improve the substitution of parameters of imported functions. Auto-analysis takes a few seconds to a couple of minutes. During this time, Ghidra defines the boundaries of functions, builds cross-references (xref), pulls out text strings and identifies library calls.

Pressing F1 when hovering over any interface element triggers contextual reference. While mastering - use, save time on picking in the menu.
CodeBrowser interface: windows for disassembling CTF reverse tasks

After autoanalysis, the CodeBrowser interface looks intimidating: dozens of panels, buttons, trees. In practice, CTF tasks need four windows. Close the rest, you won’t lose anything.
Symbol Tree – binary card

Left panel – tree of all program symbols: functions, imports, exports, labels. Here you are looking for a function main – entry point into custom logic. Deploy the Functions branch, find main, double click — Listing with Decompiler will move to this feature.

Take a look at the Imports branch separately: this is a list of libraries and API calls used, an instant snapshot of the binary capabilities. See WriteProcessMemory or VirtualAlloc – the program can modify its own code in rantime, and purely static analysis may not suffice.
Listing – Assembly Code

The central panel displays the disassembled code: addresses on the left, mnemonics on the right (MOV, CMP, JNE, CALL). For a beginner, this species is scary, and that’s okay. In 90% of entry-level tasks, you do not have to read the assembler directly, because there is a decompiler nearby. But one piece from Listing is critical: right-click → Patch Instruction — change instructions right in the listing. It is useful for quick problem solving through patching.

Navigation: double click on address CALL transfers the function to the called function, Alt+← returns back. These two actions are the basis for moving around the binary.
Decompiler — pseudocode in C

The right panel is decompiled code, your main weapon when working with Ghidra. The tool takes the assembly code of the function and generates a C-like pseudocode. Not perfect, but readable.

The backlight of the line in the decompiler highlights the appropriate instructions in Listing - and vice versa. See if (iVar1 == 0) right → on the left will be highlighted CMP and JNZ. Through a dozen such comparisons, the relationship between the C-code and the assembler becomes intuitive. For those who are just starting the analysis of executable files are an invaluable learning mechanism. Literally “assembler on fingers.”
Defined Strings – fast path to the target function

Opens through Window → Defined Strings. Shows all text strings from the binary: messages to the user, passwords, paths to files, sometimes the flag itself. Double click on the line moves to its location in the binary. Right-click → References → Show References to Address will show which functions use the string. The analysis of the CTF binary often begins from here: found “Wrong password” → switched to xref → found themselves in the target function.
Decompilation in Ghidra: how to read and improve pseudocode

The binary code decompiler does not know how the author called variables and data types. Instead of password you will see local_28, instead of char input[100] - something like char local_78[104]. Global variables get names at the memory address: DAT_00104020 – cell at the address 0x00104020. Type undefined8 – is 8 bytes of data, the type of which Ghidra did not automatically determine (for the returned value main It is almost always int).

Renaming variables is the first thing to do after opening a function in the decompiler. Right click on variable → Rename Variable (or key L). They saw that local_28 transmitted by the second argument in strcmp? Rename in user_input. Line at the address DAT_00104020 contains the text “Enter password:”? Rename in prompt_msg. Ghidra synchronizes names between the decompiler and the listing automatically.

After 5-10 renaming, the pseudocode from unreadable porridge turns into a clear program. It's literally like a fog is dissipating.

Two more techniques that speed up the work:

Right-click on the → Edit Function Signature feature – correct the parameters types if Ghidra is wrong. The decompiled code will be updated instantly.
Functions with names FUN_00401234 – those that Ghidra could not compare with known library challenges. Double clicks to their decompiled code. Often these are user functions of password or encryption verification.

The code from the decompiler window is copied directly – convenient for CTF reports and writeups.
Step-by-step parsing CTF reverse tasks in Ghidra

A typical task of the category reverse for beginners: given binary, you need to find a flag. The program either checks the password and displays the flag when you enter correctly (password checker), or validates the entered flag to correctness (flag checker). Let's look at both scenarios.
Simple password checker

Let's say, strings and ltrace did not give a result: the password does not lie open text, ltrace showed the call strcmp with an unreadable argument. Let's move on to Ghidra.

Import the binary, launch autoanalysis. In Symbol Tree → Functions find main. Double click – the decompiler shows the pseudocode. Here’s what a simple password checker might look like after renaming the variables:

void main(void) {
char user_input[104];
puts("Enter password:");
gets(user_input);
result = strcmp(user_input, "S3cr3tP@ss");
if (result == 0) {
puts("Correct! Flag: flag{r3v3rs3_101}");
} else {
puts("Wrong!");
}
}

Logic is obvious: the program accepts input through gets, compares to the string S3cr3tP@ss through strcmp. Coincidentally, the flag is taken out. The task is solved by reading one function in the decompiler. Without Ghidra, it would be necessary to deal with assembly instructions MOV, LEA, CALL For a beginner, it is much more difficult.

If main There is no Symbol Tree – don’t panic. Open Defined Strings, find the line "Wrong" or "Correct", go xref to the function that uses it. Here is your target function.
Patching of conditional transition

The second way to the flag is to inversion the verification conditions. In the listing after the instructions CMP (comparison) is worth it JNZ or JNE (jump if not zero/not equal). Right click → Patch Instruction → replace JNZ on JZ (jump if zero). Export Patched Binary: File → Export Program → Original File format. Run - the program will accept any password and display the flag.

Both approaches are counted on the CTF. The dirty path is not shameful. Shameful is when two hours you pick the algorithm, and you could invert one byte. On the blitz-rounds, time decides: see a simple check - patch and move on. You can understand the algorithm after the tournament, for writeup.
XOR coding and non-standard password checks

Tasks are more difficult not to store the password in plain text – strings It's powerless here. A frequent pattern in CTF reverse is XOR-coding: each byte of input is bitwise XOR-fits with a key and is compared with an array of reference bytes.

In the Ghidra decompiler, it looks like a cycle where each input symbol passes the XOR with the constant:


char encoded[] = {0x32, 0x26, 0x30, 0x2b};
for (i = 0; i < len; i++) {
if ((user_input ^ 0x41) != encoded) {
puts("Wrong!");
return;
}
}

To recover the password, use the reverse operation: encoded ^ key. XOR Reversible – A ^ K = B means B ^ K = A. Array of encoded bytes and key copy directly from the decompiler window: double click on the global variable (for example, DAT_00104040) will show the contents in the hex-form in the listing. Recovery in Python — three lines:

encoded = [0x32, 0x26, 0x30, 0x2b]
key = 0x41
print(''.join(chr(b ^ key) for b in encoded))

In addition to XOR, in the CTF-tasks of the entry level are found: a shift of the symbol to a fixed number (ROT-N, a special case - ROT13), a byte addition or subtraction, bit shifts. The approach is the same: from the decompiler, extract the conversion algorithm and reference data, then write the reverse transformation. Sometimes the reference data is stored not in the code, but in the data section – look for them through cross-links from the target function.

Nuance: if instead of simple strcmp or byte comparison you see a challenge FUN_00401300 – look inside with a double click. Often it is a self-written comparison function, and its analysis reveals the logic of the entire verification.
Stripped-binary: what to do when the main is not found

In a number of CTF tasks, the binary is compiled with the strip flag - debug symbols are removed from it. There is no Symbol Tree main, no meaningful function names – only FUN_00401XXX and entry. Stripped Payloads (T1027.008, Defense Evasion by MITRE ATT&CK) – it is not only CTF-reception: real malware is almost always stripped.

How to find main in stripped ELF-binary:

In Symbol Tree find entry - that's _start, ELF file entry point. She's calling __libc_start_main.
In the decompiler entry contains a call of the species __libc_start_main(FUN_00401156, ...). The first argument is address main.
Go to FUN_00401156 double click, right click → Rename Function → set the name main.

For PE files, the approach is similar: entry calls the runtime-initialization of CRT, which at the end transmits control to the custom function. Looking for CALL to a function that is not part of a standard library.

Faster way (and often more reliable): open Defined Strings, find the characteristic task lines (“Enter password”, “Correct”, “Wrong”), go xref to the processor function. Cross link works regardless of the presence of characters - even in a completely stripped binary strings remain readable, if no additional encryption is applied.
 
Top Bottom