[+] Decompiled Code Analysis
/ 5 min read
Table of Contents
How I Analyze Decompiled Code
When I started reversing binaries, I tried reading raw assembly for everything. That got old fast. Decompilers changed the game for me - they turn machine code into something closer to C, and suddenly a 200-line function becomes 20 lines of readable pseudocode.
I have spent time with all three major tools: IDA Pro, Ghidra, and Binary Ninja. Here is how I use each one and what I have learned.
Why I Rely on Decompiled Output
Reading assembly is important, but for understanding program logic quickly, I always start with the decompiler view. It helps me:
- Spot control flow (loops, conditions) without mentally tracing jumps
- Recognize API calls and their arguments immediately
- Find vulnerabilities like buffer overflows or format strings
- Map out data structures from how memory gets accessed
The trick I learned early: decompiled output is never perfect. Compilers optimize things in ways decompilers cannot fully reverse. I always cross-check suspicious output against the actual disassembly.
My Experience with IDA Pro
IDA was the first serious tool I used. The Hex-Rays decompiler produces the cleanest output I have seen.
What I Like
- Type propagation is excellent - set one struct and it cascades everywhere
- FLIRT signatures identify library functions automatically, saves me hours
- Cross-references (pressing X) let me trace any value through the entire binary
- The plugin ecosystem is massive
My Workflow
1. Load binary, wait for auto-analysis to finish2. Start from strings or imports to find interesting functions3. Press F5 to decompile4. Immediately rename variables - v4 means nothing, recv_buf tells me everything5. Set struct types on pointer arguments6. Use xrefs to follow data flow upstream and downstreamThings That Tripped Me Up
IDA sometimes shows _DWORD * casts everywhere when it cannot determine the actual type. I learned that manually defining structs early cleans up the entire decompilation. Also, IDA occasionally misidentifies function boundaries in heavily obfuscated code - I have to fix those manually with Edit > Functions.
My Experience with Ghidra
I moved to Ghidra when I wanted a free alternative I could use anywhere. Honestly, it surprised me how good it is.
What I Like
- Completely free - I can install it on any machine without license headaches
- The side-by-side decompiler + listing view is great for cross-checking
- Scripting in Python through the Ghidra API saved me on repetitive tasks
- Collaborative reversing with Ghidra Server when working with my team
My Workflow
1. Create project, import binary2. Run all analyzers (I check everything)3. Start from Symbol Tree or string search4. Read decompiler output on the right panel5. Right-click to retype variables and fix function signaturesGhidra’s Quirks
Ghidra names things like local_28, param_1. The naming follows stack offsets:
void vulnerable_function(char *param_1) { char local_28[32]; strcpy(local_28, param_1); // I spotted this overflow immediately return;}Where Ghidra struggles for me:
- Switch statements sometimes show as computed jumps - I have to manually create the switch table
- C++ virtual calls get flattened into pointer math that looks confusing
- Optimized loops can look weird until I annotate the types
My Experience with Binary Ninja
I picked up Binary Ninja for quick triage work. It sits between IDA and Ghidra in terms of features and price.
What I Like
- The tiered IL system (LLIL, MLIL, HLIL) gives me different views of the same function
- HLIL for understanding logic, MLIL when I need to track data flow precisely
- The Python API is clean and fast - I wrote several automation scripts with it
- Analysis is fast, great for triaging multiple samples quickly
The IL Levels
This is what sets Binary Ninja apart for me:
Disassembly -> Lifted IL -> Low Level IL -> Medium Level IL -> High Level ILWhen HLIL looks wrong, I drop down to MLIL to see what is actually happening. This saved me multiple times when dealing with optimized code.
Comparing Their Output
I loaded the same vulnerable function in all three to see how they present it. The original source (which I figured out after analysis):
int check_password(char *input) { char buffer[16]; strcpy(buffer, input); if (strcmp(buffer, "s3cr3t") == 0) return 1; return 0;}IDA Gave Me
int __cdecl check_password(const char *a1) { char v2[16]; strcpy(v2, a1); return strcmp(v2, "s3cr3t") == 0;}Clean. Spotted the strcpy overflow instantly.
Ghidra Gave Me
undefined4 check_password(char *param_1) { char local_18[16]; strcpy(local_18, param_1); int iVar1 = strcmp(local_18, "s3cr3t"); if (iVar1 == 0) { return 1; } return 0;}More verbose but equally clear. The undefined4 return type needed manual fixing.
Binary Ninja Gave Me
int32_t check_password(char* arg1) { char buf[16]; strcpy(&buf, arg1); if (strcmp(&buf, "s3cr3t") == 0) return 1; return 0;}All three showed me the vulnerability. The differences are mostly cosmetic.
Lessons I Learned
- Rename everything immediately - Future me always thanks present me for this
- Define structs early - One struct definition cleans up dozens of functions
- Start from strings - Error messages, URLs, hardcoded keys lead to the important code
- Check disassembly when pseudocode looks off - The decompiler lies sometimes
- Use multiple tools on hard targets - When I am stuck in IDA, Ghidra sometimes shows it differently and I get unstuck
- Script repetitive work - All three have Python APIs, and I use them constantly
Which Tool I Pick
| Situation | My Choice |
|---|---|
| CTF challenge, quick analysis | Ghidra |
| Deep malware investigation | IDA Pro |
| Triage multiple samples fast | Binary Ninja |
| Team collaboration | Ghidra Server |
| Scripting heavy automation | Binary Ninja |
| Budget is zero | Ghidra |
At the end of the day, the tool matters less than the skill. I got comfortable with one first (Ghidra), then expanded. The ability to read decompiled output, spot what is wrong, and annotate it properly - that transfers across all tools.