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/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)