Inspiration

I've competed in a lot of CTFs, and reverse engineering challenges are always my favourite. They're also the hardest category to get started in: the first time you open a decompiler you're staring at hundreds of functions named FUN_00101234, with no idea where to begin. reView is the tool I wished I'd had: it shows you the shape of a program first, then lets you dig into the details.

What it does

Give reView a binary and one command, and it opens an interactive, explained map of the program in your browser.

Call graph from main: Ghidra decompiles the binary and exports who-calls-whom. You start at main and click to expand outward, so you never face the whole graph at once. Clicking an expanded node collapses it again. Plain-English summaries: Gemini explains each function in 2-3 sentences for a beginner and ends with a "Look for:" hint pointing at the most telling detail. Common libc imports (strlen, fgets, __stack_chk_fail...) have built-in explanations, so they cost no API calls. "Interest" highlighting: functions that call risky APIs (gets, strcpy, system), call comparison functions (strcmp, memcmp), have suspicious names, or contain strings like "access denied" glow red, and the inspector explains why. It builds the habit of asking where the interesting part is. Animated execution traces: reView runs the binary under gdb and replays the real call/return sequence as a token moving through the graph. A step-by-step log shows arguments and return values, and a live call stack sits beside it. Play, pause, or step through at your own speed. Live mode (--live): type new input in the browser and watch the program run on it without restarting anything. Try a wrong key, then the right one, and see exactly where the paths diverge. Learning aids built in: a call path from main to any function, a callers/callees view, and a decompiler cheat sheet (param_1, undefined8, stack canaries, and so on). How I built it

The pipeline has four stages:

Ghidra (headless with a Java script [not JavaScript] ) imports the binary, decompiles every function, collapses PLT stubs into their real imports, hides CRT boilerplate, and exports graph.json. gdb, driven by Python, sets breakpoints on every function, tracks the call stack, and records calls, returns, and values. It handles ASLR-shifted addresses, recursion, tail calls, crashes, timeouts, and runaway programs, with a cap on trace length. Gemini enriches each node with a beginner-friendly explanation, with retries and a preflight check, so a bad API key fails gracefully instead of halfway through. A Cytoscape.js + dagre viewer, with Prism for syntax highlighting, renders the graph. Nodes are placed incrementally, so the layout doesn't jump around as you explore.

My first version needed three programs, two terminals, and a browser. I wrapped it all into a single orchestrator, review.py, plus a Dockerfile, so it's now simply "python3 review.py ./binary."

Since the tool executes untrusted binaries, I built some guardrails:

It asks for confirmation before running anything. Live mode binds to localhost only, with host/origin checks and a per-session token. Input length is capped. Anything that looks like an API key or secret is stripped from the traced program's environment. It runs on a private copy of the binary. Running inside Docker is the recommended way to analyze anything you don't trust.

Challenges I ran into

Making the graph readable. Showing everything at once was useless, so I moved to expand-on-click with incremental placement, which took far longer than I'd like to admit. Reliable tracing. Matching Ghidra's static addresses to runtime addresses under ASLR, telling real calls from libc-internal ones, and matching returns to the right frame (recursion, longjmp, tail calls) all took several rounds of debugging. Safety. A tool that runs arbitrary binaries from a web UI needs real thought about who can trigger it. Sleep. This write-up was done after ~18 hours of staring at code.

Accomplishments that I'm proud of

Fully automatic trace animations that hold up in the hard cases: recursion, call loops, multithreading, crashes, and infinitely spinning programs. My first animation was hardcoded to my demo, and getting from that to a generic gdb-based tracer that works on arbitrary x86-64 binaries is the part I'm happiest with. I'm also proud that the whole thing runs from one command.

What I learned

How to build interactive graph visualizations with Cytoscape, how to script gdb from Python, how Ghidra's headless mode and decompiler API work, and how to turn a pile of scripts into a bundle someone else can actually use.

What's next for reView

Better support for C++ and Rust binaries (name demangling, much larger graphs) A startup GUI as an alternative to the command line Support for architectures beyond x86-64 Support for other LLM APIs Support for custom themes UI customization settings

Acknowledgements

I want to thank James, Paurav, and Nathan, for providing me with valuable feedback, an extra thanks goes to James for letting me borrow his Gemini API key, and another extra thanks to Nathan letting me use his codex account for some UI touch ups.

Built With

Share this project:

Updates

Submission history