aboutsummaryrefslogtreecommitdiffstats
ModeNameSize
d---------.auto647logstatsplain
d---------.fastfeedback36logstatsplain
-rw-r--r--.gitignore361logstatsplainblame
-rw-r--r--README.md8492logstatsplainblame
-rw-r--r--TODO3497logstatsplainblame
d---------docs293logstatsplain
d---------experiments570logstatsplain
-rwxr-xr-xida-tui830logstatsplainblame
d---------idatui1056logstatsplain
-rw-r--r--logo-trans.png224298logstatsplainblame
-rw-r--r--logo.ans40439logstatsplainblame
-rw-r--r--logo.png818833logstatsplainblame
d---------plan34logstatsplain
-rw-r--r--pyproject.toml709logstatsplainblame
-rw-r--r--rehearsed-engineer.md452logstatsplainblame
d---------tests907logstatsplain
d---------tools122logstatsplain
-rw-r--r--uv.lock59148logstatsplainblame

ida-tui

IDA Pro in a terminal. Listing, decompiler, graph — keyboard-first, mouse-capable.

disasm · pseudocode · cfg · hex · strings · structs · traces · rpc


 ida-tui ─────────────────────────────────────────────────────────────────────┐
 .text:00002490 ; ---------- S U B R O U T I N E ----------                   
 .text:00002490 main            proc near                                     
 .text:00002490                 endbr64                                       
 .text:00002494                 push    rbp                ;  cursor         
 .text:00002495                 mov     rbp, rsp                              
 .text:00002498                 sub     rsp, 0B0h          ; `o`  176        
└──────────────────────────────────────────────────────────────────────────────┘

Status: personal project, actively hacked on. No packaging, no versioning, no support. It assumes a licensed IDA Pro and a venv at ~/ida-venv. Things move and break. Poke around; don't file expectations.

Why

IDA's own UI is excellent and it is a GUI. This is for the times you're in a terminal over SSH, in a tmux pane next to your notes, or driving RE from a script — and still want the listing, Hex-Rays, and a real control-flow graph.

It is not a reimplementation of IDA. It's a frontend: IDA does the analysis, this draws it.

Install

Needs Python ≥ 3.11 and IDA Pro 9.4+ with idalib.

uv sync

That pulls ida-codemode from PyPI, which is how ida-tui talks to IDA. To also attach to databases you have open in the IDA GUI, install its plugin:

uvx --prerelease=allow --from ida-codemode ida-codemode-mcp --install-plugin

Hacking on ida-codemode itself? Point at a checkout instead:

uv add --editable ../ida-codemode

Run

./ida-tui /path/to/binary        # attach to a GUI session, or open a managed database
./ida-tui                        # attach, when exactly one database is registered

ida-tui never owns an IDA process. It takes a lease: a matching database open in the IDA GUI is reused, otherwise Code Mode starts or shares a managed idalib worker. Quitting drops the lease and leaves everyone else alone.

Headerless blobs have no format to detect — IDA falls back to x86 at address 0 and analyses nothing, so say what it is:

./ida-tui fw.bin --processor arm --base 0x8000000

--base is a real address. These apply only when a database is being created; an existing IDB already records them. On ARM, t toggles ARM/Thumb decoding at the cursor and T scans a vector table for Thumb entry points.

Keys

enter escape follow / back
tab F5 listing ↔ pseudocode
space control-flow graph (z zoom, m minimap, J/K walk edges)
s split view — listing and pseudocode, cursor-synced
g / ? goto · search · search back
x n y ; xrefs · rename · retype · comment
c d p u a make code · data · function · undefine · string
o O B cycle this literal's format · reverse · opcode bytes
\ " ctrl+t hex · strings · structs
ctrl+n ctrl+p symbol palette · command palette
ctrl+s ctrl+l q save · reload as… · quit
F1 all of them

What's in it

Listing — one continuous IDA-style view: code, data and undefined runs together, with IDA's own colour tags and per-operand marks. Line-virtualized, so a 400 MB binary scrolls like a text file.

Decompiler — Hex-Rays pseudocode with syntax highlighting, per-line address anchors, and rename/retype/comment that write back.

Search (ctrl+f) — the whole database, two ways: text through the rendered disassembly (call cs:, xor eax, eax) and bytes with IDA's pattern language, wildcards included (48 8b ?? c3, nibbles like 8?, quoted literals). Which one you meant is guessed from the query — a hex-looking word like dead stays a text search — and hex:/text: or F2 override the guess. Enter searches, then Enter opens the hit.

Findings export (ctrl+e) — the session as a markdown writeup: your comments grouped by function, the names and prototypes you set, the types you declared. A .i64 does not record who wrote a comment — IDA's own analyzer uses the same call — so idatui journals its edits into the database as it makes them, and the report is built from that. Also python -m idatui.drive export.

Structs / types (ctrl+t) — local types as plain C: the list on the left, an editable, syntax-highlighted definition on the right. Ctrl+S declares it back into the database and reformats to IDA's own layout, Ctrl+N starts a new one, d deletes, and / fuzzy-filters the list.

Graph (space) — the current function's basic blocks, laid out with a real layered (Sugiyama) algorithm and routed edges: green taken, red fall-through, blue unconditional, purple loop. The boxes hold the same rows as the listing, so highlighting, renames and xrefs work inside them. Above 400 blocks it declines and says so, because nothing readable comes out at that size. → docs/GRAPH_VIEW.md

Split view (s) — listing and pseudocode side by side. The focused pane drives; the other highlights every instruction the current C line owns.

Literal formats (o) — hex → decimal → binary → char → offset, IDA's own key. Only stops that change what you see are visited, so no press is a silent no-op. The literal under the cursor is marked, and the mark is what changes — it keeps up as the text reflows. Works on Hex-Rays' separate number formats too.

Execution traces — load a Tenet trace and move through time:

./ida-tui /path/to/binary --trace trace.0.log

]/[ step, }/{ step over. Both code views are painted with the execution trail — including the pseudocode, since decomp_map knows which instructions each C line covers. The dock shows registers and the stack as of that instant; bytes the trace never saw print as ??, not zeros. Trace addresses are rebased onto the database automatically.

RPC — drive the live TUI from another process (agent-driven RE, livestreams):

./ida-tui /abs/path/bin --rpc /tmp/ida.sock
python -m idatui.drive where                 # terse-text helper
python -m idatui.drive pc main               # pseudocode of main
python -m idatui.drive rename sub_5BE0 foo   # goto + rename

docs/RPC.md

There's a scripted feature tour on top of it, for screen recordings — it spawns its own pane on a scratch copy, drives ten scenes through the real prompts, and reverts its own edits:

python tools/demo.py --spawn          # --speed 0.5 to rehearse, --list for the scenes

Splash — the logo renders as a real image on terminals that speak the kitty graphics protocol, logo.ans everywhere else. Support is detected by asking the terminal, not by sniffing $TERM (under a multiplexer, every variable you'd test is empty while the protocol works fine).

Tests

python3 tests/run.py --fast     # 302 checks, <1s, any python3 — between edits
python3 tests/run.py --list     # what runs, and what needs IDA
python3 tests/run.py            # 788 checks, ~2m — before a commit

Every suite declares NEEDS_IDA; --fast runs only the pure ones (stdlib, no IDA, no database). The rest drive a headless Textual Pilot against a real database. Iterate on one with --only:

~/ida-venv/bin/python tests/test_scenarios.py targets/echo --only hex,rename

Before optimising or debugging a slow run, read .fastfeedback/SPEED.md — per-suite timings, the known flake, and the four ways a test here wastes minutes.

Docs

sl0p.foo