Skip to content

Repository files navigation

LLM Explainer logo

LLM Explainer

Explain and rename IDA functions with a local llama.cpp server — private, offline, nothing written to your database until you click Accept. Works in the Hex-Rays or the disassembly view.

LLM Explainer result dialog

Features

  • Right-click → explain — pseudocode or disassembly view, or a hotkey (Ctrl-Alt-E). Answer streams in live; reasoning models show their chain-of-thought separately.
  • Human in the loop — every suggestion is a separate, editable checkbox. Accept / Reason More / Cancel. The model never writes on its own.
  • Rename & retype — proposes a function name, full C signature, local-variable renames, code-label renames (LABEL_5 → cleanup_and_return), called-function renames, and global/data-variable renames (byte_…, qword_… → meaningful names). A called-function rename proposed for code the model hasn't actually seen yet is held back rather than applied blind — an Investigate & Reconsider Name(s) button fetches that function's code and asks the model to confirm or revise the name before it's offered for Accept.
  • Struct detection — infers an undefined struct from pointer-offset access patterns and applies it.
  • Packed-string-table recovery — spots a helper that slices fixed-length substrings out of one merged string blob (a common obfuscation), then reads the pointer/length constants at every call site and defines each carved string (get_partial_string(dst, blob, 6) → "REFLEX").
  • Call-graph aware — follows callees (configurable depth) and can fetch a specific callee's code on demand mid-answer, or pull in compact call-site snippets from the target's own callers (the call expression + inferred argument types, not their full code) to sharpen inferred parameter types, falling back to a caller's full code only if asked for.
  • Export as compilable C — right-click → Export function as compilable C… rewrites the pseudocode as a standalone .h + .c pair you can paste into a real project: stdint.h types instead of __int64/_QWORD, self-defined replacements for Hex-Rays intrinsics (LOBYTE, __ROL4__, …), and real struct/enum typedefs converted from IDA's own local types. Everything still living in the binary is bound to g_image_base + RVA (original VA in a comment) so it links and survives relocation — globals as typed accessors, un-emitted callees as a function-pointer typedef + same-named macro, while imported CRT/OS functions get their real header instead. String literals and small read-only tables are emitted as actual C data, so the result genuinely stands alone. One editable tab per file, Copy / Save Files…, a Refine box, and Continue for when the model hits its token limit mid-file. Writes nothing to your database.
  • Compiler-verified export — the generated files are handed to a real compiler and, if it fails, the diagnostics go straight back to the model to fix, for a couple of automatic rounds — so a missed intrinsic or an undeclared helper is corrected before you ever see it. Finds clang on PATH or in the usual places on Windows (the copy Visual Studio bundles, C:\Program Files\LLVM), macOS (Xcode, the command line tools, Homebrew LLVM) and Linux (/usr/lib/llvm-*, versioned clang-NN), falling back to a system gcc/cc. Verify Now re-checks your hand-edits without asking the model to change anything. Syntax-only by construction: -fsyntax-only is forced on, so LLM-written code is type-checked but never assembled, linked or run.
  • Open in Compiler Explorer — folds the .h/.c into one translation unit and opens it on godbolt.org to try other toolchains and read the generated assembly. Small sessions ride inside the URL, larger ones use the shortener. This is the only feature that sends anything off your machine — it always asks first, names the destination, and can be pointed at a self-hosted Compiler Explorer instead.
  • Batch mode — explain a checklist of functions, review (incl. each proposed new name), apply in bulk. Build the checklist either from a one-off picker (Batch Explain Functions…) or incrementally while browsing: right-click any function in the disassembly, pseudocode, or Functions window → Add function to batch to queue it, then Analyze Batch Queue… to process everything queued so far.
  • Recursive auto-accept — explains a function's undiscovered (sub_…) callees first, then the function itself, so it's analyzed with its callees' real names/signatures already known; applies automatically, and can re-analyze an already-named callee the model flags as misnamed.
  • Multi-server — list several llama-server endpoints for ~Nx parallel batch throughput, with priority order + automatic failover.
  • CFG recovery for obfuscated code — walks basic blocks, resolves opaque predicates / dead code / flattening dispatchers with a fast deterministic pass (falls back to the LLM only when unsure), then optionally patches or rebuilds the real control flow. x86/x64 and AArch64.
  • Agent server — an optional localhost HTTP API an external agent (an LLM agent loop, a script, another tool) can use to read code/data/types, search, mutate the database, and drive the debugger for dynamic analysis. Self-describing: GET /tools returns the full catalog, so an agent discovers the API without any hardcoded knowledge. Bearer-token auth; the bound URL + token are logged at startup.

Install

Copy llm_explainer.py into <IDA user dir>\plugins\ (Windows: %APPDATA%\Hex-Rays\IDA Pro\plugins\) and restart IDA. Requires IDA 9.3+ (PySide6 ships with IDA) and a reachable llama-server (default http://127.0.0.1:8080). Hex-Rays is optional — it falls back to disassembly. Or install the packaged dist/*.zip via hcli.

Quick start

  • One function — right-click → Explain function with LLM…, review the streamed suggestions, Accept & Add Comment.
  • Batch — Functions window → Batch Explain Functions…, check functions, Apply Selected when done. A New Name column shows the proposed rename per function as it finishes (marked (kept: …) when the existing non-default name would be preserved).
  • Recursive — right-click → Explain function with LLM (recursively)…. Auto-applies; capped by Max recursive callees; writes unattended, so use with care.
  • Export C — right-click → Export function as compilable C…; it syntax-checks the result with clang and lets the model fix its own errors, then Save Files… and build the .h/.c pair with your own toolchain. Use Refine ("target MSVC", "no macros for globals") for anything left.
  • CFG recovery — disassembly view → Trace/Recover CFG…, pick a start address, watch the live transcript/graph, then review each block and pick an On Accept mode.

Batch mode with a local Qwen model

Batch mode with auto-apply working through a heavily protected x64 binary, powered by a local Qwen model: stub after stub is named, typed and commented the moment it finishes, while llama.cpp's speculative decoding (~94% draft acceptance, ~200 tokens/s) keeps the queue moving — all on consumer hardware (Ryzen 9 9950X, 64 GB DDR5-6000, Radeon RX 7900 XTX 24 GB).

CFG patching modes

Chosen on the review screen (all re-verify actual bytes before touching anything, and refuse rather than guess):

  • Mark only (default) — colors + comments blocks. No bytes changed.
  • Patch in place — NOPs confirmed-dead code and redirects fully-resolved opaque-predicate branches to their real target; ensures a function exists at the entry. Also collapses a single-target computed/indirect jump to a direct branch (incl. AArch64 BR/B.cond).
  • Rebuild linear — writes just the real blocks as one straight-line sequence at the entry point, re-encoding every branch/call explicitly; touches only [entry, entry+size).

Results are cached for the session (Load Cached Result), and any in-place/rebuild patch is revertible with Undo Patches. Opt-in Enumerate ARM64 computed jump tables (experimental) recovers *(base + i*stride + field) dispatch handlers.

Agent server

Off by default. Enable it in Settings → Agent server, restart IDA, and the bound URL + bearer token are printed to IDA's output window (e.g. http://127.0.0.1:8181, token 3f9c…). An external agent then drives IDA over plain HTTP:

Endpoint Auth Purpose
GET /health none liveness + tool count
GET /tools token the self-describing catalog: every tool's name, description, and parameter list
POST /call token {"name": "getFunctionSummary", "args": {"address": "main"}} → one tool result
POST /batch token {"calls": [{"name": …, "args": …}, …]} → sequential results

Every address/name argument accepts either an address (0x401000, "0x401000", 4198400) or a symbol name ("main"). Responses are {"ok": true, "result": …} on success, {"ok": false, "error": …} on failure, with 400 for bad arguments, 403 for gated tools, 404 for unknown tools, 408 when the IDA main thread doesn't answer in time.

What an agent can do (83 tools): session meta (getMetadata, getAnalysisStatus, getCurrentCursor, showInIda, openFile to bring up another binary in a new IDA window); discovery (getFunctions, getStrings, getImports, getExports, getSegments, searchBytes, searchText, …); code reading (getDisassembly, getFunctionDisassembly, getDecompilation, getFunctionPrototype, getFunctionArguments, getCallers, getBasicBlocks, getCallGraph, getCrossReferencesTo/From, getFunctionSummary, …); data & types (readBytes, getDataAt, getGlobalVariables, listStructs, getStruct, listEnums, getTypeAt, …); mutation (renameFunction, renameGlobal, createLabel, renameVariable, setVariableType, setFunctionPrototype, setComment, makeFunction, createData, createStruct, bookmarks, …); and the debugger — live state reads (getDebugState, getLiveRegisters, getLiveMemory, getLiveMemoryMap, getThreads, getBreakpoints, getStackTrace) are always available, while process control (debugStart/debugSuspend/debugContinue/debugStep/debugRunTo/debugDetach/debugExit, setBreakpoint, removeBreakpoint, setRegisterValue, debugWriteMemory, setCurrentThread) is gated off by default.

Safety. Binds to 127.0.0.1 by default (change the host in settings to expose it). Everything the agent can do by default is the same undoable, human-reviewable IDA editing this plugin does everywhere else — the exceptions are gated tools that are off by default: patchBytes (write bytes to the IDB), runIdaScript (run arbitrary Python with IDA imports, an escape hatch for anything the catalog doesn't cover), and openFileInThisWindow/closeDatabase (save+close this session's database and exit the IDA process). Enable them explicitly if you want an agent to do those. Every result is truncated to a configurable character cap (default 20,000) so a single huge field can't blow an agent's context.

Configuration

Edit → Plugins → LLM Explainer. Persisted as llm_explainer.cfg.json in your IDA user dir. Key settings:

Setting Default Notes
Server base URL(s) http://127.0.0.1:8080 One endpoint per line, priority order, optional # name; batch runs across all, with failover
Model / API key (blank) Only if your server needs them
Temperature / Max tokens 0.2 / 16384 Keep tokens generous for reasoning models
Follow calls depth 0 N>0 eagerly includes N levels of callee code
Max on-demand code requests 5 Cap on the model's REQUEST_CODE/REQUEST_CALLERS round-trips per conversation
Max callers shown per request 3 How many callers REQUEST_CALLERS returns a compact call-site snippet for (not their full code)
Max recursive callees 10 Cap for the recursive auto-accept action
System prompt(s) (editable) Explain + CFG-trace + C-export protocols
Max globals exported 40 How many referenced globals the C export describes (name, VA, RVA, size, type, segment)
Embed initialized read-only data on Sends the actual bytes of strings/const tables so the exported C can define them as real data
Max embedded bytes per global 512 Per-global cap; anything larger stays an address-based accessor
Verify exported C with a compiler on Syntax-checks the export and feeds any errors back to the model
C compiler path (blank) Blank auto-detects clang (Windows/macOS/Linux), then gcc/cc
Compile check flags -fsyntax-only -std=c11 -Wall -fsyntax-only is always forced on; nothing is linked or run
Max compile fix rounds 2 How often the model may be asked to fix its own compile errors (0 = report only)
Compiler Explorer URL https://godbolt.org Where Open in Compiler Explorer sends code; set a self-hosted instance to keep it internal
Compiler Explorer compiler / flags cclang2010 / -std=c11 -Wall Preselected there; ids come from <instance>/api/compilers/c
Resolve branches via constant propagation on Fast deterministic pass before the LLM (disable to always ask)
Enumerate ARM64 computed jump tables off Experimental; see above
CFG trace colors / Max blocks green/red/amber, 200 REAL / DEAD / UNRESOLVED
Auto-compact long conversations on Summarize older turns when near the context window so long sessions keep working
Compact at / Context size / Chars-token 90% / 0 (auto) / 3.5 Trigger threshold; 0 auto-detects the server's context; chars/token is the fallback prompt estimate
Agent server off Start the localhost HTTP API for external agents; see the Agent server section
Agent host / Port 127.0.0.1 / 8181 Bind address; port 0 = auto-assign, and a busy configured port now falls back to auto-assign instead of failing (so multiple IDA sessions can each run their own agent server)
Agent token (blank) Bearer token; blank = random per session (logged at startup)
Allow agent to patch bytes / run scripts off / off Gate the two destructive tools (patchBytes, runIdaScript)
Allow agent to control the debugger off Gate the debugger control tools (debug*, setBreakpoint, setRegisterValue); live-state reads need no gate
Allow agent to close this session off Gate openFileInThisWindow/closeDatabase (save+close the database and exit this IDA process); openFile (new window, this session untouched) needs no gate
Agent max output chars 20000 Per-result truncation cap

Saved prompts auto-update to the current default when you haven't customized them, so plugin updates take effect without editing the config.

Prompt protocol

The system prompt asks the model to emit structured lines the plugin parses out of its free-form answer:

Marker Purpose
REQUEST_CODE: <fn> fetch a callee's code before answering (automatic)
REQUEST_CALLERS[: <fn>] fetch a compact call-site snippet (call expression + inferred argument types, not full code) from a few of the target's callers (automatic)
SUGGESTED_NAME: <name> function name
SUGGESTED_SIGNATURE: <decl> prototype (Hex-Rays only)
SUGGESTED_VAR: <old> -> <new> local rename (Hex-Rays only)
SUGGESTED_LABEL: <old> -> <new> goto-label rename, e.g. LABEL_5 (Hex-Rays only)
SUGGESTED_CALLEE_NAME: <fn> -> <new> rename a callee whose code was shown
SUGGESTED_GLOBAL_NAME: <g> -> <new> rename a referenced global/data variable
SUGGESTED_REANALYZE: <fn> - <why> flag an already-named callee for re-analysis (recursive scan)
SUGGESTED_STRUCT: <decl> define + register a struct type
SUGGESTED_VAR_TYPE: <var> <type> apply a type to a local
SUGGESTED_STRING_EXTRACTOR: <fn> ptr=<n> len=<m> flag a helper that slices fixed-length substrings out of a packed string blob; the plugin reads the pointer/length constants at every call site and defines each carved string
BEGIN_FILE: <name> … END_FILE one emitted source file (C export only); a reply cut off mid-file is stitched back together by Continue

The prose answer itself is kept to one sentence — it becomes the function comment.

Trace/Recover CFG live view

Changelog

  • v1.14.0 — incremental batch queue: right-click a function in the disassembly, pseudocode, or Functions window and choose Add function to batch to queue it (deduped, in-memory), then Analyze Batch Queue… (same right-click menu, or Edit > Plugins) runs every queued function through the existing batch review/apply dialog. Clear Batch Queue drops it without processing. This is a lighter-weight alternative to the existing Batch Explain Functions… picker for building up a list while browsing rather than all at once.
  • v1.13.0 — multi-instance agent server support: if the configured agent-server port is already taken (e.g. a second IDA session is open), the server now falls back to an OS-assigned free port instead of failing to start. Every running agent server also advertises itself in a shared registry file (agent_instances/ under the IDA user directory) with its host/port/token and the binary it's analyzing, and GET /health now reports pid/idb_name. A new standalone script, list_ida_agents.py, lists all live sessions (probing /health, pruning stale entries from crashed sessions) so an agent can find the right session by binary name instead of being told a fixed port up front. Three new agent-server tools: openFile opens another binary in a brand-new IDA window without touching the current session; openFileInThisWindow (gated) saves+closes this session and opens a different file in a new window, exiting this process (IDA has no in-place "swap the input file" operation, so this is as close to "reuse this window" as its architecture allows); closeDatabase (gated, new agent_allow_close setting) saves and closes the database and exits the process when the agent is done with a session.
  • v1.12.1 — agent server IDA 9.x API-drift fixes: listStructs/getStruct/getStructAt/deleteStruct and listEnums/getEnum rewritten on the modern type system (the classic idaapi.get_struc*/get_enum*/del_struc API used previously does not exist in IDA 9.x); getImports/getExports fixed to use ida_nalt.enum_import_names/ida_entry (the old calls referenced non-existent functions); getGlobalVariables fixed (idautils.Dirs() does not exist; uses Names()); createData argument/flag fix; makeFunction's add_func failure check fixed (was comparing a bool to None, so failures were silently ignored). All ~80 agent-server tools now dispatch under MFF_WRITE instead of MFF_FAST (mutating debugger/DB calls must not run under MFF_FAST). Two new gated debugger tools: debugWriteMemory, setCurrentThread. getStrings no longer forces a full string-list rebuild on every call (it now reuses the cached list like the rest of IDA does; pass refresh: true to force one), and now scans for both utf8 and utf16 ("Unicode") strings by default in one pass instead of IDA's factory default of utf8-only (narrow with encodings). Auto-compact's headroom now also accounts for the configured reply size (max_tokens), not just a flat fraction of the context window, and the Explain/Export-C dialogs gained a manual "Compact now" button.
  • v1.12.0 — debugger support in the agent server: 17 new tools. Read-only live-state tools (getDebugState, getThreads, getLiveRegisters, getLiveMemory, getLiveMemoryMap, getBreakpoints, getStackTrace) are always available; process control (debugStart/debugSuspend/debugContinue/debugStep/debugRunTo/debugDetach/debugExit, setBreakpoint, removeBreakpoint, setRegisterValue) is gated by a new agent_allow_debug setting (off by default). An agent can now confirm static hypotheses against live registers/memory/stacks and feed the evidence back into the database as names and types.
  • IDA 9.3 compatibility fixes — field-tested against IDA 9.3: idc.get_name → idaapi.get_name, idc.set_cmt → ida_bytes.set_cmt, idc.generate_disassembly → ida_lines.generate_disasm_line, idc.find_binary → ida_bytes.bin_search (image/mask), insn.is_call() → CF_CALL canon feature, idautils.Entries() 4-tuples, ida_auto.get_auto_state() (autoanalysis_required is gone), ida_loader.get_path / get_kernel_version / get_screen_ea / get_segm_class, and execute_sync without the timeout argument. The agent server is now verified working on IDA 9.3.
  • v1.11.0 — agent server: localhost HTTP API (61 tools, bearer-token auth, self-describing GET /tools, /call + /batch) for external agents to read code/data/types and mutate the database; patchBytes/runIdaScript gated off by default. Settings dialog reorganized into five tabs (Server / Explain / CFG trace / C export / Agent server), each scrollable.
  • v1.10.0 — auto-compact (LLM-summarizes older turns when the conversation nears the server's context window, with mechanical fallback), live context gauge with tokens/s in the Explain and Export-C dialogs, and a clean conversation per run.

License

MIT — see LICENSE. © 2026 Peter Garba

About

AI-assisted IDA Pro plugin (local llama.cpp) for function explanations, renames, struct detection, and batch analysis — human-in-the-loop throughout.

Resources

Stars

44 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages