General reverse engineering methodology + MCP toolchain + Headless API.
Part 1: General Reverse Engineering Methodology
Key Principles
- F5 first, disasm second (HARD RULE) — MUST call
decompilebeforedisasmfor any function analysis. Only fall back todisasmwhen decompile explicitly fails/errors or returns obvious artifacts. NEVER skip decompile and go straight to disasm for "convenience" or "safety". - Rename-as-you-go (HARD RULE) — each time you decompile a function and understand its functionality, you must immediately
rename_symbol, "analyze everything first then name it all at once" is not allowed. Unnamedsub_XXXXXis technical debt and will make the caller's pseudocode unreadable.build_callgraphfind leaves → decompile bottom-up → each time you understand a functionimmediatelyrename_symbol→ re-decompile the caller. - Iterative loop — F5→annotate types→rename→F5 again→discover new offsets→verify struct→F5 again… compound effect
- Constants are literal — when you see
0x6Ein IDA, just write110, don't interpret the physical meaning - disasm when decompile disagrees — when diff is 1-10, switch to disasm and verify instruction by instruction
- Analysis log (HARD RULE) — during the reverse engineering process, you must maintain an analysis log. Each time you complete the reverse engineering of a function (decompile + rename + understand functionality), immediately append a log entry. At the end of the analysis (before
close_idb) output the complete log summary. For the log format and rules, see "Analysis Log Specification" below.
1. Decompilation Strategy
Conditions for falling back to disasm: F5 errors/times out (>5000 instructions); pseudocode has obvious artifacts.
The Decompiler's Four Great Lies:
| Lie | Pseudocode manifestation | Actual instruction |
|---|---|---|
| Constant folding | result = x * 1718750 / 1000000 |
MUL X8, X8, #110; UDIV X8, X8, #64 |
| Loses +1/-1 | discount = bias * 110 / 64 |
after division there is still ADD X8, X8, #1 |
| Type confusion | int v10 = *(int *)(ctx + 0x1DC) |
LDR W8, [X0, #0x1DC](actually u32) |
| Hidden saturating arithmetic | result = a - b |
SUBS X8, X9, X10; CSEL X8, XZR, X8, LO |
Pseudocode quality iteration:
初始 F5 → sub_XXXXX(v42, v38)
↓ rename_symbol 叶子
二次 F5 → parse_header(v42, v38)
↓ apply_type
三次 F5 → parse_header(ctx, buf)
↓ declare_c_type + rename_local_variable
最终 F5 → ctx->msg_type 可读
2. Naming and Annotation
Timing: name as you understand, don't save it all for the end.
Naming conventions:
| Prefix | Meaning | Example |
|---|---|---|
check_/validate_ |
validation | check_permissions |
parse_/deserialize_ |
deserialization | parse_config |
compute_/calc_ |
computation | compute_checksum |
dispatch_ |
dispatch entry | dispatch_command |
init_/setup_ |
initialization | init_context |
Compound effect: after renaming a callee,re-decompile the caller — the pseudocode updates immediately.
Cross-session persistence: all annotations are saved in .i64. When resuming analysis, open_idb("<file>.i64"), not the original binary.
2.5 Analysis Log Specification (HARD RULE)
During reverse engineering you must record while analyzing. This is not optional—reverse engineering without a log is as if it wasn't done.
When to record
| Trigger event | Must record |
|---|---|
| Rename a function | ✅ Function address, old name → new name, functionality summary |
| Discover a key struct | ✅ Struct name, field layout, purpose |
| Identify an arithmetic formula | ✅ Formula expression, constants involved, verification status |
| Discover an error code mapping | ✅ error code → meaning |
| Identify dispatch/selector | ✅ selector value → handler address → functionality |
| Complete an analysis phase | ✅ Phase Summary |
Log Format
## Analysis Log: <binary_name>
### Functions Reversed
| # | Address | Old Name | New Name | Purpose |
|---|---------|----------|----------|---------|
| 1 | 0x1234 | sub_1234 | parse_header | 从输入 buffer 解析消息头 |
| 2 | 0x5678 | sub_5678 | compute_checksum | CRC32 校验: crc = update(crc, data, len) |
### Structs Identified
| Struct | Size | Key Fields | Used By |
|--------|------|-----------|---------|
| MsgHeader | 0x40 | +0x00: magic (u32), +0x04: msg_type (u16), +0x08: payload_len (u32) | parse_header, dispatch_command |
### Key Findings
- Dispatch: tag byte at msg_type field, 0x01=init, 0x02=query, 0x03=update
- Error 0x1770: InvalidInput (length == 0)
- Checksum computed over payload only, header excluded
### Open Questions
- sub_9ABC: 被 compute_checksum 调用,疑似 lookup table init,待确认
Rules
- Record in real time — append a log entry immediately after rename, don't wait until the analysis is done to recall it
- Every rename must have a Purpose — one sentence explaining what the function does, not repeating the function name
- Formulas must be written out — record identified arithmetic formulas as mathematical expressions, annotate verification status (✅ verified / ⚠️ unverified)
- Open Questions must be listed — record functions/logic not fully understood here, don't pretend to understand everything
- At the end of the analysis, output —
close_idbBefore, output the complete analysis log as the deliverable of this reverse engineering session
3. Struct Reverse Engineering
From F5's *(ptr + 0xNNN) pattern, reconstruct the struct.
LDR → field type (ARM64):
LDR X8, [X0, #0x130] → u64 (64-bit, X register)
LDR W8, [X0, #0x144] → u32 (32-bit, W register)
LDRH W8, [X0, #0x168] → u16
LDRB W8, [X0, #0x178] → u8
LDP X8, X9, [X0, #0x130] → 两个 u64
decompile may show
LDR W8asint(signed), but it is actually u32. Must confirm with disasm.
Incremental verification loop:
1. decompile → 收集所有 ptr+offset 模式
2. disasm → 指令宽度确认字段类型
3. read_dword/read_qword → 读实际值 + 语义交叉验证
4. declare_c_type → 定义 struct
5. apply_type → 应用到参数
6. 重新 decompile → *(ptr+0x1DC) 变成 obj->field_name
7. get_xrefs_to_struct_field → 找所有访问该字段的代码
8. 发现新 offset → 回到步骤 2
Array detection: consecutive equally-spaced loads → [type; N]
4. Call Graph Navigation
Don't read large functions linearly. Dispatch function 10KB+, first build_callgraph target.
Leaf-first strategy:
1. build_callgraph(roots: [entry], max_depth: 3) → 找叶子
2. decompile 每个叶子 → 理解 + 立即 rename
3. decompile 父函数 → sub-call 显示有意义名字
4. 向上重复直到入口完全可读
Anti-pattern: decompile the entry first → see 48KB of sub_XXXXX(v42, v38) wall → completely unreadable.
5. Search and Locate
Byte search: watch out for little-endian (arm64/x86 = LE):
search_bytes(pattern: "6E 00 00 00") # u32 0x6E (110),LE
search_bytes(pattern: "80 96 98 00") # u32 0x989680 (10,000,000)
Immediate:search_text(kind: "imm", targets: ["110", "10000"])
Full pseudocode:search_pseudocode(pattern: "amount")
Instruction sequence:search_instructions(patterns: ["MUL", "UDIV"])
6. Formula Extraction
Translation rules:
- Translate constants directly —
0x6E→110 - Follow IDA strictly for arithmetic order —
a * 110 / 64 + 1≠(a * 110 + 64) / 64 - u128 precision — multiply two u64s first
as u128 - Saturation arithmetic — use
saturating_mul/saturating_sub - decompile might lie — switch to disasm when diff is small
IDA pseudocode arithmetic patterns:
| Pattern | Meaning |
|---|---|
__umulh(a, b) |
u128 multiplication high 64 bits |
__multi3(a, b) |
Compiler u128 multiplication |
__udivti3(hi, lo, denom) |
u128 division |
SUBS + CSEL XZR |
saturating_sub |
Convergence methodology:
| diff range | Typical root cause | Fix strategy |
|---|---|---|
| > 100 | Wrong scale or formula | Rewrite from IDA constants |
| 10-100 | Wrong operator/operand | Confirm MUL/DIV/ADD/SUB with disasm |
| 1-10 | off-by-one / saturation arithmetic | Compare instruction by instruction with disasm |
| 0 | ✅ exact match | Done |
7. decompile_structured Arithmetic Chain Analysis
When the diff is > 0 after pseudocode translation and manual comparison is inefficient:
1. decompile_structured(address: <func>, max_depth: 20, include_types: true)
→ JSON AST
2. 遍历 AST 找算术子树:
op: "mul"/"div"/"add"/"sub" → 核心算术
op: "call" → __umulh / __udivti3 等 u128 操作
op: "num" → 常量值(直接翻译)
op: "var" → 变量引用
3. 从 AST 直接生成代码表达式,避免人工翻译错误
4. 与 disasm 交叉验证算术链的完整性
Applicable scenarios: Complex multi-step formulas (5+ steps), repeatedly non-zero diff, need to compare formulas across multiple functions.
Part 2: MCP Tool Quick Reference
The IDA MCP server provides 73 tools. High-frequency tool parameters and dynamic discovery methods → mcp-tool-reference.md
Part 2.5: CLI Direct Invocation (non-MCP)
ida-cli(~/.local/bin/ida-cli) — connect directly to the running ida-cli server via Unix Socket, no MCP protocol required. Suitable for scripts, automation, pipe composition.
Zero configuration
Server auto-management—automatically starts on the first CLI call, then reused for subsequent calls. No manual serve-http &。
Command discovery
ida-cli --help # 列出全部子命令
ida-cli rename-symbol --help # 查看具体命令参数
Common examples
# --path 必传,自动 spawn worker + 打开文件
ida-cli --path <file> list-functions --limit 20
ida-cli --path <file> get-function-by-name --name main
ida-cli --path <file> decompile-function --address 0x1234
ida-cli --path <file> disassemble-function --name func_name --count 20
ida-cli --path <file> rename-symbol --address 0x1234 --new-name parse_pool
ida-cli --path <file> get-callees --address 0x1234
ida-cli --path <file> build-callgraph --roots 0x1234 --max-depth 3
ida-cli --path <file> search-pseudocode --pattern "amount" --limit 10
ida-cli --path <file> get-xrefs-to --address 0x1234
ida-cli --path <file> batch-decompile --addresses "0x1234,0x5678"
# 输出格式
ida-cli --json --path <file> list-functions --limit 5 # pretty JSON
ida-cli --compact --path <file> list-functions --limit 5 # 单行 JSON
# Server 管理(无需 --path)
ida-cli server-start # 手动启动(一般不需要,自动管理)
ida-cli server-status # 运行状态
ida-cli server-stop # 停止
ida-cli server-logs # tail -f 日志
# IDB 缓存管理
ida-cli idb-list # 列出所有缓存的 IDB
ida-cli idb-info --hash <hash> # 查看详情
ida-cli idb-remove --hash <hash> # 删除缓存(同时删除所有相关文件 .i64/.id0/.id1/.nam/.til/.imcp)
# Raw JSON-RPC(任意方法)
ida-cli raw '{"method":"list_functions","params":{"path":"<file>","limit":5}}'
# Pipe 模式(多命令流水线)
echo '{"method":"status"}
{"method":"decompile_function","params":{"path":"<file>","address":"0x1234"}}' | ida-cli --json pipe
Supported file types
| Type | Example | Behavior |
|---|---|---|
.i64 / .idb |
target.i64 |
Directly opens IDA database |
| Raw binary | Mach-O / ELF / PE | IDA auto-analysis |
Concurrency safety
Multiple files concurrently: each file has an independent worker process, with no interference between them:
ida-cli --path binary_a.elf list-functions --limit 5 &
ida-cli --path binary_b.elf list-functions --limit 5 &
wait # 并行打开 + 查询
ida-cli server-status # worker_count: 2
ida-cli --path binary_a.elf close # 只关 A,B 不受影响
Multiple clients concurrently on the same file: multiple CLIs can operate on the same program at the same time. The worker executes serially internally, and the router matches responses by request ID, so they will not get mixed up:
# 同时执行 3 个 rename —— 全部成功
ida-cli raw '{"method":"rename_symbol","params":{"path":"target.elf","current_name":"sub_1000","name":"main_loop","flags":0}}' &
ida-cli raw '{"method":"rename_symbol","params":{"path":"target.elf","current_name":"sub_2000","name":"parse_input","flags":0}}' &
ida-cli raw '{"method":"rename_symbol","params":{"path":"target.elf","current_name":"sub_3000","name":"handle_request","flags":0}}' &
wait
# 读写混合也安全:rename + decompile + disasm 同时发
ida-cli raw '{"method":"rename_symbol","params":{"path":"target.elf","current_name":"sub_4000","name":"send_response","flags":0}}' &
ida-cli --path target.elf decompile-function --address 0x5e8 &
ida-cli --path target.elf disassemble-function-at --address 0x700 --count 10 &
wait
Auto-start race prevention: when multiple CLIs cold-start at the same time, the file lock ensures only 1 server is started, and the rest wait until it is ready and then reuse it.
Server failure recovery
When a CLI request times out or returns a connection error, the server may be stuck. Handling steps:
# 1. 先尝试正常 shutdown
ida-cli server-stop
# 2. 如果 shutdown 也卡住(5s 无响应),强杀
pkill -9 -f "ida-cli"
rm -f ~/.ida/server.sock ~/.ida/server.pid ~/.ida/startup.lock
# 3. 下次 CLI 调用自动重新启动 server
ida-cli --path <file> list-functions --limit 5
Log troubleshooting:ida-cli server-logs or cat ~/.ida/logs/server.log。
Part 3: General Workflow
Workflow 1: Binary Orientation (first open, 30 seconds)
1. get_database_info() → arch, file type, functions
2. list_segments() → code/data/rodata layout
3. list_exports() → 找入口
4. list_imports() → 导入符号/API
5. list_functions(limit: 50) → first batch
6. build_callgraph(roots: [entry_addr], max_depth: 2) → top-level structure
Workflow 2: Struct Reconstruction
1. decompile → 收集所有 ptr+offset 模式
2. disasm → 确认字段类型(LDR W vs X / MOV 宽度)
3. read_dword/read_qword → 读实际值,交叉验证语义
4. declare_c_type(decl: "struct Foo { ... }")
5. apply_type(addr: param, decl: "Foo *")
6. get_xrefs_to_struct_field(name: "Foo", member_name: "bar")
7. 重新 decompile → ptr+offset 变成 obj->field_name
Workflow 3: Arithmetic Chain Verification
1. decompile_function(addr: func) → high-level formula
2. get_pseudocode_at(addr: suspicious_line) → zoom in
3. disassemble_function_at(addr: func) → full disasm
4. search_instructions(patterns: ["MUL", "UDIV"]) → arithmetic ops
5. search_instruction_operands(patterns: ["#0x6E"]) → constant usage
6. 逐指令验证:operand width, saturation, order
7. convert_number(inputs: ["0x989680"]) → verify constants
Workflow 4: Error Code Mapping
1. search_text(kind: "imm", targets: ["0x0F00000000"]) → error constant loads
2. get_function_at_address(address: match) → 所属函数
3. get_pseudocode_at(address: match) → 条件上下文
4. set_comment(address: match, comment: "Error: InvalidAmount")
5. search_pseudocode(pattern: "return 0x") → 所有错误返回点
6. 复杂场景 → run_script 批量提取
Workflow 5: Factory/Dispatch Table Analysis
1. list_strings(query: "factory") / list_strings(query: "registry")
2. get_xrefs_to_string → 谁构建/读取这个表
3. decompile table builder → 找 table base address
4. scan_memory_table(base: addr, stride: 24, count: 50) → 读取 entries
5. 每个 fn_ptr → get_address_info → decompile → handler 逻辑
Workflow 6: Multi-Database Concurrent Analysis
1. Orchestrator: open_idb → db_handle + close_token(每个 IDB 独立 worker 进程)
2. Share db_handle with concurrent agents
3. Read ops (decompile, xrefs, callees) → 安全并行
Write ops (rename_symbol, apply_type) → 串行避免冲突
4. 用 batch_decompile 减少往返
5. 完成后: close_idb(close_token)
支持同时调试:
6. 每个 IDB 可独立 dbg_load_debugger + dbg_start_process(各自的 debug server + 端口)
7. 所有 dbg_* 工具通过 db_handle 路由到对应 IDB 的调试会话
8. 两个调试会话完全隔离:断点、寄存器、内存、执行控制互不影响
9. 调试期间可对任意 IDB 执行反编译/反汇编(使用 rebase 后的地址)
Workflow 7: Batch Annotation
1. batch_rename(renames: [
{"address": "0x1000", "name": "parse_header"},
{"address": "0x2000", "name": "dispatch_command"},
{"address": "0x3000", "name": "handle_request"}
]) → 批量重命名,返回每条成功/失败
2. set_function_prototype(address: "0x1000",
prototype: "int64_t __fastcall parse_header(MsgHeader *hdr)")
→ 设置函数原型
3. set_function_comment(address: "0x1000",
comment: "Parses message header from input buffer")
→ 设置函数注释
4. rename_stack_variable(func_address: "0x2000",
var_name: "v1", new_name: "cmd_type") → 重命名栈变量
5. set_stack_variable_type(func_address: "0x2000",
var_name: "cmd_type", type_str: "uint32_t") → 设置栈变量类型
6. create_enum(decl: "enum ErrorCode { InvalidInput = 0, BufferOverflow = 1 };")
→ 创建错误码枚举
7. batch_decompile(addresses: ["0x1000", "0x2000", "0x3000"])
→ 验证所有标注已生效
Workflow 8: Dynamic Debugging
8a: start_process (control target process lifecycle)
1. open_idb(path: "target.i64") → db_handle
2. dbg_load_debugger(debugger: "mac", is_remote: true) → 加载远程调试器
3. dbg_start_process(path: "/path/to/target", args: "", timeout: 30)
→ 启动进程并自动附加,返回 pid + ip
4. dbg_add_breakpoint(address: "0x<main_addr>") → IDB 地址,IDA 自动 rebase
5. dbg_continue(timeout: 10) → 运行到断点
→ event_code=5 (BREAKPOINT)
6. dbg_get_registers() → 检查寄存器状态
7. decompile_function(address: "<rebased_ip>") → 调试期间反编译正常工作
8. dbg_step_into / dbg_step_over / dbg_run_to → 执行控制
9. dbg_read_memory(address: "<addr>", size: 64) → 读运行时内存
10. dbg_exit_process(timeout: 10) → 终止进程 + 清理 debug server
8b: attach_process (attach to a running process)
1. 外部启动目标进程(或 HostDebugRunner) → 获取 PID
2. open_idb(path: "target.i64")
3. dbg_load_debugger(debugger: "mac", is_remote: true)
4. dbg_attach_process(pid: <PID>, timeout: 15) → 附加
5. dbg_add_breakpoint / dbg_continue / ... → 正常调试
6. dbg_detach_process(timeout: 10) → 分离,进程继续运行
Analysis capabilities during debugging
During debugging (while the process is suspended), all of the following operations are available:
decompile_function/decompile_structured— decompile ✅disassemble_function/disassemble_function_at— disassemble ✅list_functions/get_function_by_name— function query ✅get_xrefs_to/get_xrefs_from— cross-references ✅rename_symbol/set_comment— annotations ✅
Note: when using tools that operate by address, you need to use the rebased runtime address.
Part 4: Error recovery
decompile failure
→ get_pseudocode_at(address, end_address) — 只看关键代码段
→ disassemble_function_at(address) — 降级到反汇编
→ build_callgraph 找子函数 → 分别 decompile — 拆分分析
get_analysis_status returns auto_is_ok=false
→ run_auto_analysis(timeout_secs: 120) — 强制等待分析完成
→ 或重复调用 get_analysis_status 直到 auto_is_ok=true
→ 重要:xrefs/decompile 在分析完成前可能返回不完整结果!
Tool timeout
→ run_script 默认 120s,最大 600s:run_script(code: "...", timeout_secs: 300)
→ batch_decompile 对大型数据库可能较慢,分批处理
Common Pitfalls
- Decompile the root function first → 48KB
sub_XXXXXwall → use build_callgraph + leaf-first - After rename, the caller was not decompiled again → the caller pseudocode still shows the old name
- Trust the exact arithmetic of decompile → when the diff is nonzero, you must verify with disasm
- search_bytes does not consider endianness → arm64/x86 are LE,
0x6Esearch6E 00 00 00 - Ignore the number of results from get_xrefs_to → high-frequency helpers have 50+ xrefs; combine with build_callgraph to filter
- Do not use read_dword/read_qword to verify the offset → what decompile displays as int64_t may actually be u32
- Open the raw binary instead of the .i64 to restore the session → all annotations are lost
- Query before analysis is complete →
get_analysis_statusconfirmauto_is_ok=true
Part 5: Headless API
IDA Domain API (pip install ida-domain) + idalib + idapro headless execution → headless-api.md
Reference Files
⚠️ Load on demand, do not read everything. Each Reference file is 96-727 lines.Read only the one matching the current task. Most MCP reverse-engineering tasks only need
mcp-tool-reference.md。
| File | Lines | When to Load | NOT needed for |
|---|---|---|---|
| mcp-tool-reference.md | 300 | When MCP tool parameters are uncertain(high frequency, default first choice) | — |
| counterfactual-patch.md | 96 | Counterfactual Patching methodology | Pure static analysis |
| headless-api.md | 124 | IDA Domain API / idapro headless execution | MCP reversing (no need for headless) |
| idapython-cheatsheet.md | 727 | Write IDAPython scripts (run_script) |
When MCP tools are sufficient |
| ida-domain-api.md | 680 | Full reference for IDA Domain API (Python headless) | MCP reversing |
| idalib-headless.md | 385 | Batch analysis with idalib C++ / idapro module | MCP reversing |
| binary-analysis-patterns.md | 312 | Malware analysis, vulnerability discovery, firmware RE | Simple static analysis |
| plugin-development.md | 564 | IDA plugin development | Reverse engineering tasks |