ida-code 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ida_code/__init__.py +2 -0
- ida_code/_search_utils.py +33 -0
- ida_code/comments.py +191 -0
- ida_code/config.py +9 -0
- ida_code/doc_search.py +255 -0
- ida_code/example_search.py +570 -0
- ida_code/executor.py +145 -0
- ida_code/guidelines.py +370 -0
- ida_code/macho.py +67 -0
- ida_code/prompts.py +176 -0
- ida_code/server.py +1011 -0
- ida_code/session.py +293 -0
- ida_code/snapshots.py +110 -0
- ida_code/structures.py +227 -0
- ida_code/undo.py +102 -0
- ida_code/variables.py +206 -0
- ida_code-0.2.1.dist-info/METADATA +167 -0
- ida_code-0.2.1.dist-info/RECORD +21 -0
- ida_code-0.2.1.dist-info/WHEEL +4 -0
- ida_code-0.2.1.dist-info/entry_points.txt +2 -0
- ida_code-0.2.1.dist-info/licenses/LICENSE +21 -0
ida_code/guidelines.py
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
"""Coding guidelines and templates for IDAPython development."""
|
|
2
|
+
|
|
3
|
+
_STANDALONE_SCRIPT = """\
|
|
4
|
+
# Standalone idalib Script
|
|
5
|
+
|
|
6
|
+
## Overview
|
|
7
|
+
|
|
8
|
+
A standalone script uses idalib to analyze binaries outside the IDA GUI.
|
|
9
|
+
It runs as a normal Python program and loads IDA's analysis engine in-process
|
|
10
|
+
via the `idapro` package. Use this when you need batch processing, CI
|
|
11
|
+
integration, or headless analysis.
|
|
12
|
+
|
|
13
|
+
## Template
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
#!/usr/bin/env python3
|
|
17
|
+
\"\"\"Standalone idalib analysis script.\"\"\"
|
|
18
|
+
|
|
19
|
+
import os
|
|
20
|
+
import sys
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
# --- idalib bootstrap (must happen before any ida_* imports) ---
|
|
24
|
+
IDA_DIR = os.environ.get("IDA_INSTALL_DIR")
|
|
25
|
+
if not IDA_DIR:
|
|
26
|
+
print("Error: IDA_INSTALL_DIR env is not set")
|
|
27
|
+
sys.exit(1)
|
|
28
|
+
IDA_DIR = Path(IDA_DIR)
|
|
29
|
+
sys.path.insert(0, str(IDA_DIR / "idalib" / "python"))
|
|
30
|
+
os.environ.setdefault("IDADIR", str(IDA_DIR))
|
|
31
|
+
|
|
32
|
+
import idapro # Must be first — before any ida_* modules
|
|
33
|
+
|
|
34
|
+
# --- Now safe to import ida_* ---
|
|
35
|
+
import ida_funcs
|
|
36
|
+
import ida_bytes
|
|
37
|
+
import ida_name
|
|
38
|
+
import idautils
|
|
39
|
+
import idc
|
|
40
|
+
# import ida_hexrays # Only if Hex-Rays decompiler is available
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def analyze(binary_path: str) -> None:
|
|
44
|
+
\"\"\"Main analysis logic.\"\"\"
|
|
45
|
+
rc = idapro.open_database(binary_path, True) # True = wait for auto-analysis
|
|
46
|
+
if rc != 0:
|
|
47
|
+
print(f"Error: open_database returned {rc}")
|
|
48
|
+
return
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
# --- Your analysis code here ---
|
|
52
|
+
for ea in idautils.Functions():
|
|
53
|
+
name = ida_funcs.get_func_name(ea)
|
|
54
|
+
print(f"{ea:#x} {name}")
|
|
55
|
+
finally:
|
|
56
|
+
idapro.close_database()
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
if __name__ == "__main__":
|
|
60
|
+
if len(sys.argv) < 2:
|
|
61
|
+
print(f"Usage: {sys.argv[0]} <binary>")
|
|
62
|
+
sys.exit(1)
|
|
63
|
+
analyze(sys.argv[1])
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Key Constraints
|
|
67
|
+
|
|
68
|
+
1. **Import order** — `import idapro` MUST come before any `ida_*` imports.
|
|
69
|
+
Violating this causes segfaults or ImportError.
|
|
70
|
+
2. **Single database** — idalib supports only one open database at a time.
|
|
71
|
+
Close before opening another.
|
|
72
|
+
3. **Single thread** — All `ida_*` calls must come from the thread that opened
|
|
73
|
+
the database.
|
|
74
|
+
4. **auto_analysis** — Pass `True` to `open_database` to wait for IDA's
|
|
75
|
+
auto-analysis to finish. Pass `False` for pre-analyzed databases (.i64/.idb).
|
|
76
|
+
5. **Cleanup** — Always call `idapro.close_database()` in a `try/finally` block.
|
|
77
|
+
|
|
78
|
+
## Common Patterns
|
|
79
|
+
|
|
80
|
+
### Iterate functions
|
|
81
|
+
```python
|
|
82
|
+
for ea in idautils.Functions():
|
|
83
|
+
func = ida_funcs.get_func(ea)
|
|
84
|
+
name = ida_funcs.get_func_name(ea)
|
|
85
|
+
size = func.size()
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Read bytes
|
|
89
|
+
```python
|
|
90
|
+
data = ida_bytes.get_bytes(ea, size)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Cross-references to an address
|
|
94
|
+
```python
|
|
95
|
+
for xref in idautils.XrefsTo(ea):
|
|
96
|
+
print(f" referenced from {xref.frm:#x}")
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Decompile (requires Hex-Rays)
|
|
100
|
+
```python
|
|
101
|
+
import ida_hexrays
|
|
102
|
+
cfunc = ida_hexrays.decompile(ea)
|
|
103
|
+
if cfunc:
|
|
104
|
+
print(cfunc)
|
|
105
|
+
```
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
_PLUGIN = """\
|
|
109
|
+
# IDA Plugin
|
|
110
|
+
|
|
111
|
+
## Overview
|
|
112
|
+
|
|
113
|
+
An IDA plugin is loaded inside the IDA GUI. It subclasses `idaapi.plugin_t`
|
|
114
|
+
and is placed in IDA's `plugins/` directory. Plugins can add menu items,
|
|
115
|
+
register hotkeys, hook into events, and extend the UI.
|
|
116
|
+
|
|
117
|
+
## Template
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
\"\"\"My IDA Plugin — brief description.\"\"\"
|
|
121
|
+
|
|
122
|
+
import idaapi
|
|
123
|
+
import ida_kernwin
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class MyPlugin(idaapi.plugin_t):
|
|
127
|
+
flags = idaapi.PLUGIN_PROC # Loaded at startup, stays resident
|
|
128
|
+
comment = "Brief description"
|
|
129
|
+
help = "Extended help text"
|
|
130
|
+
wanted_name = "My Plugin" # Shown in Edit > Plugins
|
|
131
|
+
wanted_hotkey = "Ctrl-Alt-M" # Hotkey to trigger run()
|
|
132
|
+
|
|
133
|
+
def init(self):
|
|
134
|
+
\"\"\"Called when IDA loads the plugin. Return PLUGIN_KEEP to stay loaded.\"\"\"
|
|
135
|
+
print(f"[{self.wanted_name}] Loaded")
|
|
136
|
+
return idaapi.PLUGIN_KEEP
|
|
137
|
+
|
|
138
|
+
def run(self, arg):
|
|
139
|
+
\"\"\"Called when the user activates the plugin (hotkey or menu).\"\"\"
|
|
140
|
+
print(f"[{self.wanted_name}] Running")
|
|
141
|
+
|
|
142
|
+
def term(self):
|
|
143
|
+
\"\"\"Called when IDA shuts down or unloads the plugin.\"\"\"
|
|
144
|
+
print(f"[{self.wanted_name}] Unloaded")
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def PLUGIN_ENTRY():
|
|
148
|
+
\"\"\"Required entry point — IDA calls this to instantiate the plugin.\"\"\"
|
|
149
|
+
return MyPlugin()
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Plugin Flags
|
|
153
|
+
|
|
154
|
+
| Flag | Meaning |
|
|
155
|
+
|------|---------|
|
|
156
|
+
| `PLUGIN_PROC` | Load when a processor module is loaded (most common) |
|
|
157
|
+
| `PLUGIN_FIX` | Load at startup, never unload |
|
|
158
|
+
| `PLUGIN_HIDE` | Don't show in the Plugins menu |
|
|
159
|
+
| `PLUGIN_UNL` | Unload after each `run()` call |
|
|
160
|
+
| `PLUGIN_MULTI` | Can have multiple instances (IDA 7.4+) |
|
|
161
|
+
|
|
162
|
+
## init() Return Values
|
|
163
|
+
|
|
164
|
+
| Return | Meaning |
|
|
165
|
+
|--------|---------|
|
|
166
|
+
| `PLUGIN_KEEP` | Keep the plugin loaded |
|
|
167
|
+
| `PLUGIN_OK` | Keep loaded, can be unloaded by IDA if needed |
|
|
168
|
+
| `PLUGIN_SKIP` | Do not load (wrong file type, missing dependency, etc.) |
|
|
169
|
+
|
|
170
|
+
## Adding Menu Items (Action-Based)
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
class MyActionHandler(idaapi.action_handler_t):
|
|
174
|
+
def activate(self, ctx):
|
|
175
|
+
print("Action triggered!")
|
|
176
|
+
return 1
|
|
177
|
+
|
|
178
|
+
def update(self, ctx):
|
|
179
|
+
return idaapi.AST_ENABLE_ALWAYS
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
# In init():
|
|
183
|
+
action_desc = idaapi.action_desc_t(
|
|
184
|
+
"my_plugin:my_action", # Unique action name
|
|
185
|
+
"My Action", # Display text
|
|
186
|
+
MyActionHandler(), # Handler instance
|
|
187
|
+
"Ctrl-Shift-M", # Hotkey (optional)
|
|
188
|
+
"Tooltip text", # Tooltip (optional)
|
|
189
|
+
)
|
|
190
|
+
idaapi.register_action(action_desc)
|
|
191
|
+
idaapi.attach_action_to_menu(
|
|
192
|
+
"Edit/Plugins/", # Menu path
|
|
193
|
+
"my_plugin:my_action", # Action name
|
|
194
|
+
idaapi.SETMENU_APP,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
# In term():
|
|
198
|
+
idaapi.unregister_action("my_plugin:my_action")
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Hooks
|
|
202
|
+
|
|
203
|
+
### UI Hooks
|
|
204
|
+
```python
|
|
205
|
+
class MyUIHooks(ida_kernwin.UI_Hooks):
|
|
206
|
+
def finish_populating_widget_popup(self, widget, popup_handle, ctx):
|
|
207
|
+
idaapi.attach_action_to_popup(
|
|
208
|
+
widget, popup_handle, "my_plugin:my_action",
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
# In init(): hooks = MyUIHooks(); hooks.hook()
|
|
212
|
+
# In term(): hooks.unhook()
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### IDB Hooks (database events)
|
|
216
|
+
```python
|
|
217
|
+
import ida_idp
|
|
218
|
+
|
|
219
|
+
class MyIDBHooks(ida_idp.IDB_Hooks):
|
|
220
|
+
def auto_empty_finally(self):
|
|
221
|
+
# Called when auto-analysis completes
|
|
222
|
+
return 0
|
|
223
|
+
|
|
224
|
+
# In init(): hooks = MyIDBHooks(); hooks.hook()
|
|
225
|
+
# In term(): hooks.unhook()
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Key Constraints
|
|
229
|
+
|
|
230
|
+
1. **`PLUGIN_ENTRY()`** — Required top-level function. IDA calls it to get the
|
|
231
|
+
plugin instance.
|
|
232
|
+
2. **File location** — Place the `.py` file in `$IDA_DIR/plugins/` for auto-load,
|
|
233
|
+
or use `ida_loader.load_plugin(path)` for dynamic loading.
|
|
234
|
+
3. **GUI thread** — All UI operations must run on IDA's main thread. Use
|
|
235
|
+
`idaapi.execute_sync()` if calling from another thread.
|
|
236
|
+
4. **init() gating** — Return `PLUGIN_SKIP` if the plugin doesn't apply to the
|
|
237
|
+
current database (e.g., wrong architecture).
|
|
238
|
+
5. **Cleanup in term()** — Unhook all hooks, unregister all actions, free
|
|
239
|
+
resources. Failing to do so causes crashes on exit or reload.
|
|
240
|
+
"""
|
|
241
|
+
|
|
242
|
+
_IDAPYTHON_SCRIPT = """\
|
|
243
|
+
# IDAPython Script (In-GUI)
|
|
244
|
+
|
|
245
|
+
## Overview
|
|
246
|
+
|
|
247
|
+
A classic IDAPython script runs inside the IDA GUI via File > Script File,
|
|
248
|
+
the output window's Python console, or Alt-F7. All `ida_*` modules are
|
|
249
|
+
already available — no bootstrap needed. Use this for quick analysis tasks,
|
|
250
|
+
one-off automation, and interactive exploration.
|
|
251
|
+
|
|
252
|
+
## Template
|
|
253
|
+
|
|
254
|
+
```python
|
|
255
|
+
\"\"\"IDAPython script — brief description.
|
|
256
|
+
|
|
257
|
+
Run via File > Script File (Alt-F7) or the Python console.
|
|
258
|
+
\"\"\"
|
|
259
|
+
|
|
260
|
+
import ida_funcs
|
|
261
|
+
import ida_bytes
|
|
262
|
+
import ida_name
|
|
263
|
+
import idautils
|
|
264
|
+
import idc
|
|
265
|
+
import ida_kernwin
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def main():
|
|
269
|
+
\"\"\"Main script logic.\"\"\"
|
|
270
|
+
ea = ida_kernwin.get_screen_ea() # Current cursor address
|
|
271
|
+
func = ida_funcs.get_func(ea)
|
|
272
|
+
if not func:
|
|
273
|
+
print(f"No function at {ea:#x}")
|
|
274
|
+
return
|
|
275
|
+
|
|
276
|
+
name = ida_funcs.get_func_name(func.start_ea)
|
|
277
|
+
print(f"Current function: {name} ({func.start_ea:#x} - {func.end_ea:#x})")
|
|
278
|
+
|
|
279
|
+
# --- Your analysis code here ---
|
|
280
|
+
for head in idautils.Heads(func.start_ea, func.end_ea):
|
|
281
|
+
disasm = idc.GetDisasm(head)
|
|
282
|
+
print(f" {head:#x} {disasm}")
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
if __name__ == "__main__":
|
|
286
|
+
main()
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## Key Differences from Standalone Scripts
|
|
290
|
+
|
|
291
|
+
- **No bootstrap** — `ida_*` modules are pre-loaded by IDA. No `import idapro`,
|
|
292
|
+
no `sys.path` manipulation, no `open_database`/`close_database`.
|
|
293
|
+
- **Database is already open** — the script operates on whichever database the
|
|
294
|
+
user has open in IDA.
|
|
295
|
+
- **GUI available** — `ida_kernwin` functions work: dialogs, choosers, forms,
|
|
296
|
+
`get_screen_ea()`, etc.
|
|
297
|
+
- **Output goes to IDA's Output window** — `print()` writes there, not to a
|
|
298
|
+
terminal.
|
|
299
|
+
|
|
300
|
+
## Common Patterns
|
|
301
|
+
|
|
302
|
+
### Get current cursor position
|
|
303
|
+
```python
|
|
304
|
+
ea = ida_kernwin.get_screen_ea()
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Ask the user for input
|
|
308
|
+
```python
|
|
309
|
+
val = ida_kernwin.ask_str("default", 0, "Enter a value:")
|
|
310
|
+
ea = ida_kernwin.ask_addr(0, "Enter an address:")
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### Show a chooser (list dialog)
|
|
314
|
+
```python
|
|
315
|
+
class MyChooser(ida_kernwin.Choose):
|
|
316
|
+
def __init__(self, items):
|
|
317
|
+
super().__init__("Title", [["Address", 16], ["Name", 30]])
|
|
318
|
+
self.items = items
|
|
319
|
+
|
|
320
|
+
def OnGetSize(self):
|
|
321
|
+
return len(self.items)
|
|
322
|
+
|
|
323
|
+
def OnGetLine(self, n):
|
|
324
|
+
return self.items[n]
|
|
325
|
+
|
|
326
|
+
chooser = MyChooser([
|
|
327
|
+
[f"{ea:#x}", name]
|
|
328
|
+
for ea, name in my_results
|
|
329
|
+
])
|
|
330
|
+
chooser.Show()
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### Color an address
|
|
334
|
+
```python
|
|
335
|
+
idc.set_color(ea, idc.CIC_ITEM, 0x00FF00) # Green
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
### Add a comment
|
|
339
|
+
```python
|
|
340
|
+
idc.set_cmt(ea, "my comment", 0) # Regular comment
|
|
341
|
+
idc.set_cmt(ea, "my comment", 1) # Repeatable comment
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### Iterate all strings
|
|
345
|
+
```python
|
|
346
|
+
import ida_bytes
|
|
347
|
+
for s in idautils.Strings():
|
|
348
|
+
print(f"{s.ea:#x} {ida_bytes.get_strlit_contents(s.ea, s.length, s.strtype)}")
|
|
349
|
+
```
|
|
350
|
+
"""
|
|
351
|
+
|
|
352
|
+
_GUIDELINES: dict[str, str] = {
|
|
353
|
+
"standalone_script": _STANDALONE_SCRIPT,
|
|
354
|
+
"plugin": _PLUGIN,
|
|
355
|
+
"idapython_script": _IDAPYTHON_SCRIPT,
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def get(target: str) -> str:
|
|
360
|
+
"""Return coding guidelines for the given target type."""
|
|
361
|
+
text = _GUIDELINES.get(target)
|
|
362
|
+
if text is None:
|
|
363
|
+
available = ", ".join(_GUIDELINES)
|
|
364
|
+
raise KeyError(f"Unknown target {target!r}. Available: {available}")
|
|
365
|
+
return text
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def list_targets() -> list[str]:
|
|
369
|
+
"""Return all available guideline target names."""
|
|
370
|
+
return list(_GUIDELINES)
|
ida_code/macho.py
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Fat Mach-O architecture listing and slice extraction using LIEF."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
|
|
7
|
+
import lief
|
|
8
|
+
|
|
9
|
+
log = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _arch_name(binary: lief.MachO.Binary) -> str:
|
|
13
|
+
"""Derive a human-friendly architecture name from a Mach-O binary header."""
|
|
14
|
+
name = binary.header.cpu_type.name.lower()
|
|
15
|
+
# ARM64 subtype 2 is arm64e (pointer authentication).
|
|
16
|
+
if name == "arm64" and binary.header.cpu_subtype == 2:
|
|
17
|
+
return "arm64e"
|
|
18
|
+
return name
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def list_architectures(path: str) -> list[str]:
|
|
22
|
+
"""List architecture names in a fat Mach-O binary.
|
|
23
|
+
|
|
24
|
+
Returns e.g. ["x86_64", "arm64e"]. Returns an empty list if *path*
|
|
25
|
+
is not a fat (universal) Mach-O.
|
|
26
|
+
"""
|
|
27
|
+
fat = lief.MachO.parse(path)
|
|
28
|
+
if fat is None:
|
|
29
|
+
return []
|
|
30
|
+
# A single-slice FatBinary is effectively a thin binary.
|
|
31
|
+
if len(fat) <= 1:
|
|
32
|
+
return []
|
|
33
|
+
return [_arch_name(binary) for binary in fat]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def extract_slice(path: str, arch: str) -> str:
|
|
37
|
+
"""Extract a single architecture slice to ``{path}.{arch}`` and return that path.
|
|
38
|
+
|
|
39
|
+
Raises ``ValueError`` if *path* is not a fat Mach-O or the requested
|
|
40
|
+
architecture is not found.
|
|
41
|
+
"""
|
|
42
|
+
fat = lief.MachO.parse(path)
|
|
43
|
+
if fat is None or len(fat) <= 1:
|
|
44
|
+
raise ValueError(f"Not a fat Mach-O: {path}")
|
|
45
|
+
|
|
46
|
+
# Try exact match first, then fall back to base cpu_type match.
|
|
47
|
+
exact: lief.MachO.Binary | None = None
|
|
48
|
+
base_match: lief.MachO.Binary | None = None
|
|
49
|
+
for binary in fat:
|
|
50
|
+
name = _arch_name(binary)
|
|
51
|
+
if name == arch:
|
|
52
|
+
exact = binary
|
|
53
|
+
break
|
|
54
|
+
if binary.header.cpu_type.name.lower() == arch:
|
|
55
|
+
base_match = binary
|
|
56
|
+
|
|
57
|
+
chosen = exact or base_match
|
|
58
|
+
if chosen is None:
|
|
59
|
+
available = [_arch_name(b) for b in fat]
|
|
60
|
+
raise ValueError(
|
|
61
|
+
f"Architecture '{arch}' not found. Available: {available}"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
output_path = f"{path}.{arch}"
|
|
65
|
+
log.info("Extracting %s slice from %s -> %s", arch, path, output_path)
|
|
66
|
+
chosen.write(output_path)
|
|
67
|
+
return output_path
|
ida_code/prompts.py
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""MCP prompt templates for common IDA workflows."""
|
|
2
|
+
|
|
3
|
+
from ida_code import guidelines
|
|
4
|
+
|
|
5
|
+
_REVERSE_ENGINEER = """\
|
|
6
|
+
# Reverse Engineering Workflow
|
|
7
|
+
|
|
8
|
+
A structured approach to analyzing an unknown binary using ida-code MCP tools.
|
|
9
|
+
|
|
10
|
+
## Phase 1: Reconnaissance
|
|
11
|
+
|
|
12
|
+
Start by opening the binary and gathering high-level information.
|
|
13
|
+
|
|
14
|
+
1. **Open the binary** — Use `open_database` with the path to the binary. \
|
|
15
|
+
For fat (universal) Mach-O binaries, call `list_architectures` first to discover \
|
|
16
|
+
available slices, then pass the desired `arch` to `open_database`.
|
|
17
|
+
2. **Survey the database** — Call `get_database_info` to see the processor type, \
|
|
18
|
+
bitness, segments, and entry points.
|
|
19
|
+
3. **List functions** — Use `list_functions` to browse the function table. Start \
|
|
20
|
+
with a small `limit` to get an overview, then paginate or use `name_filter`.
|
|
21
|
+
4. **Enumerate strings** — Use `get_strings` to list strings in the database. \
|
|
22
|
+
It searches both ASCII and UTF-16 strings. Use `name_filter` to search for \
|
|
23
|
+
specific content and `min_length` to filter out noise.
|
|
24
|
+
5. **Check imports/exports** — Use `get_imports` to list imported functions \
|
|
25
|
+
grouped by module, and `get_exports` to list exported symbols.
|
|
26
|
+
|
|
27
|
+
## Phase 2: Triage
|
|
28
|
+
|
|
29
|
+
Prioritize which functions to analyze first.
|
|
30
|
+
|
|
31
|
+
- **Name-based filtering** — Use `list_functions` with `name_filter` to find \
|
|
32
|
+
functions related to security (`auth`, `crypt`, `hash`, `verify`, `sign`, \
|
|
33
|
+
`key`), parsing (`parse`, `decode`, `deserialize`, `read`, `load`), networking \
|
|
34
|
+
(`send`, `recv`, `connect`, `socket`, `http`), or file I/O (`open`, `write`, \
|
|
35
|
+
`fopen`, `mmap`).
|
|
36
|
+
- **Size-based prioritization** — Large functions often contain the most logic. \
|
|
37
|
+
Sort by size to find the most complex code.
|
|
38
|
+
- **String cross-references** — Interesting strings (error messages, format \
|
|
39
|
+
strings, URLs, file paths) often lead to important code. Use `get_xrefs_to` \
|
|
40
|
+
with a string's address to find which functions reference it.
|
|
41
|
+
|
|
42
|
+
## Phase 3: Deep Analysis
|
|
43
|
+
|
|
44
|
+
Dive into individual functions.
|
|
45
|
+
|
|
46
|
+
1. **Decompile** — Use `decompile` with the function name or address. Read the \
|
|
47
|
+
pseudocode to understand the logic.
|
|
48
|
+
2. **Cross-reference tracing** — Use `get_xrefs_to` to find callers of a \
|
|
49
|
+
function ("who calls this?") and `get_xrefs_from` to find callees ("what does \
|
|
50
|
+
this call?"). The xref type field distinguishes calls, jumps, and data references.
|
|
51
|
+
3. **Disassembly** — Use `get_disassembly` for instruction-level detail when the \
|
|
52
|
+
decompiler output is unclear or for analyzing data sections.
|
|
53
|
+
4. **Structure recovery** — When you identify structured data, use \
|
|
54
|
+
`create_structure` to define it and `set_variable` to apply the type to variables.
|
|
55
|
+
|
|
56
|
+
## Phase 4: Annotation
|
|
57
|
+
|
|
58
|
+
Document your findings directly in the database.
|
|
59
|
+
|
|
60
|
+
- **Rename functions** — Use `rename_function` to give meaningful names to \
|
|
61
|
+
auto-named functions (e.g., rename `sub_3f08` to `parse_header`).
|
|
62
|
+
- **Retype functions** — Use `retype_function` to fix function signatures \
|
|
63
|
+
(e.g., `"int __fastcall(struct header *hdr, size_t len)"`).
|
|
64
|
+
- **Rename variables** — Use `set_variable` to give meaningful names to local \
|
|
65
|
+
and global variables (e.g., rename `v12` to `buffer_size`).
|
|
66
|
+
- **Retype variables** — Use `set_variable` with `new_type` to apply correct C \
|
|
67
|
+
types (e.g., `"struct my_header *"`).
|
|
68
|
+
- **Add comments** — Use `set_comment` to annotate key addresses with your \
|
|
69
|
+
findings. Use `function` type for function-level summaries, `regular` for \
|
|
70
|
+
inline notes.
|
|
71
|
+
- **Define structures** — Use `create_structure` and `edit_structure` to build \
|
|
72
|
+
type definitions that match the binary's data layouts.
|
|
73
|
+
|
|
74
|
+
## Phase 5: Iteration
|
|
75
|
+
|
|
76
|
+
Reverse engineering is iterative — each pass reveals more.
|
|
77
|
+
|
|
78
|
+
1. **Re-decompile** — After renaming and retyping, call `decompile` again. The \
|
|
79
|
+
pseudocode will be dramatically more readable with proper names and types.
|
|
80
|
+
2. **Verify with disassembly** — Use `get_disassembly` to confirm the decompiler's \
|
|
81
|
+
interpretation matches the actual instructions.
|
|
82
|
+
3. **Expand scope** — Follow cross-references to related functions and repeat \
|
|
83
|
+
the analysis cycle.
|
|
84
|
+
|
|
85
|
+
## Best Practices
|
|
86
|
+
|
|
87
|
+
- **Prefer dedicated tools over `execute`** — Use `get_strings`, `get_imports`, \
|
|
88
|
+
`get_exports`, `get_xrefs_to`, `get_xrefs_from`, `rename_function`, and \
|
|
89
|
+
`retype_function` instead of writing IDAPython boilerplate via `execute`. They \
|
|
90
|
+
return structured data, handle errors, and are faster to use.
|
|
91
|
+
- **Use `execute` for custom analysis** — The `execute` tool gives you full \
|
|
92
|
+
IDAPython access. Write custom scripts for pattern matching, data extraction, \
|
|
93
|
+
or anything the dedicated tools don't cover.
|
|
94
|
+
- **Search docs and examples** — Use `search_docs` to look up unfamiliar IDA \
|
|
95
|
+
APIs. Use `search_examples` to find working IDAPython code patterns — it indexes \
|
|
96
|
+
125 official examples with metadata, API usage, and source code.
|
|
97
|
+
- **Snapshot before bulk changes** — Call `create_snapshot` before renaming or \
|
|
98
|
+
retyping many symbols. Use `restore_snapshot` to roll back if something goes wrong.
|
|
99
|
+
- **Work incrementally** — Rename and retype a few variables, re-decompile, \
|
|
100
|
+
verify, then continue. Small batches are easier to validate.
|
|
101
|
+
- **Namespace persistence** — Variables and functions defined via `execute` \
|
|
102
|
+
persist across calls. Build up helper functions incrementally.
|
|
103
|
+
- **Close when done** — Call `close_database` when analysis is complete to free \
|
|
104
|
+
resources and save the database.
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
_SCRIPT_BEST_PRACTICES = """\
|
|
108
|
+
|
|
109
|
+
## IDAPython Best Practices
|
|
110
|
+
|
|
111
|
+
### Error Handling
|
|
112
|
+
- Always check return values: `ida_funcs.get_func()` returns `None` if no \
|
|
113
|
+
function exists at the address.
|
|
114
|
+
- Wrap `ida_hexrays.decompile()` in try/except — it raises \
|
|
115
|
+
`DecompilationFailure` for functions the decompiler can't handle.
|
|
116
|
+
- Call `ida_hexrays.init_hexrays_plugin()` before using any Hex-Rays APIs \
|
|
117
|
+
and check the return value.
|
|
118
|
+
|
|
119
|
+
### Performance
|
|
120
|
+
- Cache `ida_name.get_name_ea()` lookups — name resolution is not free.
|
|
121
|
+
- Use `ida_bytes.get_bytes(ea, size)` for bulk reads instead of reading \
|
|
122
|
+
byte-by-byte with `ida_bytes.get_byte()`.
|
|
123
|
+
- Prefer `idautils.Functions()`, `idautils.Heads()`, `idautils.XrefsTo()` \
|
|
124
|
+
iterators over manual linked-list traversal.
|
|
125
|
+
|
|
126
|
+
### Naming Conventions
|
|
127
|
+
- `ea` — effective address (an integer, not a pointer)
|
|
128
|
+
- `pfn` — pointer to `func_t` (from `ida_funcs.get_func()`)
|
|
129
|
+
- `cfunc` — `cfunc_t` object (from `ida_hexrays.decompile()`)
|
|
130
|
+
- `tif` — `tinfo_t` object (type information)
|
|
131
|
+
- `ti` — `ida_typeinf` module
|
|
132
|
+
|
|
133
|
+
### Common Pitfalls
|
|
134
|
+
- **`idc` vs `ida_funcs`** — `idc` functions are thin wrappers with less \
|
|
135
|
+
control. Prefer `ida_funcs`, `ida_bytes`, etc. for new code.
|
|
136
|
+
- **String encoding** — `ida_bytes.get_strlit_contents()` returns `bytes`, \
|
|
137
|
+
not `str`. Decode with `.decode('utf-8', errors='replace')` if needed.
|
|
138
|
+
- **Address arithmetic** — Addresses are plain integers. Use `& 0xFFFFFFFF` \
|
|
139
|
+
(32-bit) or `& 0xFFFFFFFFFFFFFFFF` (64-bit) to handle overflow, or better, \
|
|
140
|
+
use the database's bitness from `ida_ida.inf_get_app_bitness()`.
|
|
141
|
+
- **Segment boundaries** — Don't assume contiguous address space. Check \
|
|
142
|
+
segment membership with `ida_segment.getseg(ea)` before accessing data.
|
|
143
|
+
|
|
144
|
+
### MCP Tool Usage for Testing
|
|
145
|
+
- Use `execute` to test script snippets interactively before assembling \
|
|
146
|
+
the final script.
|
|
147
|
+
- The execution namespace persists — define helpers in one call and use \
|
|
148
|
+
them in the next.
|
|
149
|
+
- Use `search_docs` and `search_examples` to find API patterns.
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def reverse_engineer() -> str:
|
|
154
|
+
"""Return a comprehensive reverse engineering workflow guide."""
|
|
155
|
+
return _REVERSE_ENGINEER
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def create_script(target: str, description: str | None = None) -> str:
|
|
159
|
+
"""Return coding guidelines for the given script type plus best practices.
|
|
160
|
+
|
|
161
|
+
*target* is one of: ``standalone_script``, ``plugin``, ``idapython_script``.
|
|
162
|
+
*description* is an optional description of what the script should do.
|
|
163
|
+
"""
|
|
164
|
+
try:
|
|
165
|
+
text = guidelines.get(target)
|
|
166
|
+
except KeyError:
|
|
167
|
+
available = ", ".join(guidelines.list_targets())
|
|
168
|
+
raise ValueError(
|
|
169
|
+
f"Unknown target {target!r}. Available targets: {available}"
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
parts = [text, _SCRIPT_BEST_PRACTICES]
|
|
173
|
+
if description:
|
|
174
|
+
parts.append(f"\n## Task\n\n{description}\n")
|
|
175
|
+
|
|
176
|
+
return "\n".join(parts)
|