ida-pro-mcp-xjoker 1.0.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_pro_mcp/__init__.py +0 -0
- ida_pro_mcp/__main__.py +6 -0
- ida_pro_mcp/ida_mcp/__init__.py +68 -0
- ida_pro_mcp/ida_mcp/api_analysis.py +1296 -0
- ida_pro_mcp/ida_mcp/api_core.py +337 -0
- ida_pro_mcp/ida_mcp/api_debug.py +617 -0
- ida_pro_mcp/ida_mcp/api_memory.py +304 -0
- ida_pro_mcp/ida_mcp/api_modify.py +406 -0
- ida_pro_mcp/ida_mcp/api_python.py +179 -0
- ida_pro_mcp/ida_mcp/api_resources.py +295 -0
- ida_pro_mcp/ida_mcp/api_stack.py +167 -0
- ida_pro_mcp/ida_mcp/api_types.py +480 -0
- ida_pro_mcp/ida_mcp/auth.py +166 -0
- ida_pro_mcp/ida_mcp/cache.py +232 -0
- ida_pro_mcp/ida_mcp/config.py +228 -0
- ida_pro_mcp/ida_mcp/framework.py +547 -0
- ida_pro_mcp/ida_mcp/http.py +859 -0
- ida_pro_mcp/ida_mcp/port_utils.py +104 -0
- ida_pro_mcp/ida_mcp/rpc.py +187 -0
- ida_pro_mcp/ida_mcp/server_manager.py +339 -0
- ida_pro_mcp/ida_mcp/sync.py +233 -0
- ida_pro_mcp/ida_mcp/tests/__init__.py +14 -0
- ida_pro_mcp/ida_mcp/tests/test_api_analysis.py +336 -0
- ida_pro_mcp/ida_mcp/tests/test_api_core.py +237 -0
- ida_pro_mcp/ida_mcp/tests/test_api_memory.py +207 -0
- ida_pro_mcp/ida_mcp/tests/test_api_modify.py +123 -0
- ida_pro_mcp/ida_mcp/tests/test_api_resources.py +199 -0
- ida_pro_mcp/ida_mcp/tests/test_api_stack.py +77 -0
- ida_pro_mcp/ida_mcp/tests/test_api_types.py +249 -0
- ida_pro_mcp/ida_mcp/ui.py +357 -0
- ida_pro_mcp/ida_mcp/utils.py +1186 -0
- ida_pro_mcp/ida_mcp/zeromcp/__init__.py +5 -0
- ida_pro_mcp/ida_mcp/zeromcp/jsonrpc.py +384 -0
- ida_pro_mcp/ida_mcp/zeromcp/mcp.py +883 -0
- ida_pro_mcp/ida_mcp.py +186 -0
- ida_pro_mcp/idalib_server.py +354 -0
- ida_pro_mcp/idalib_session_manager.py +259 -0
- ida_pro_mcp/server.py +1060 -0
- ida_pro_mcp/test.py +170 -0
- ida_pro_mcp_xjoker-1.0.1.dist-info/METADATA +405 -0
- ida_pro_mcp_xjoker-1.0.1.dist-info/RECORD +45 -0
- ida_pro_mcp_xjoker-1.0.1.dist-info/WHEEL +5 -0
- ida_pro_mcp_xjoker-1.0.1.dist-info/entry_points.txt +4 -0
- ida_pro_mcp_xjoker-1.0.1.dist-info/licenses/LICENSE +21 -0
- ida_pro_mcp_xjoker-1.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
from typing import Annotated
|
|
2
|
+
import ast
|
|
3
|
+
import io
|
|
4
|
+
import sys
|
|
5
|
+
import idaapi
|
|
6
|
+
import idc
|
|
7
|
+
import ida_bytes
|
|
8
|
+
import ida_dbg
|
|
9
|
+
import ida_entry
|
|
10
|
+
import ida_frame
|
|
11
|
+
import ida_funcs
|
|
12
|
+
import ida_hexrays
|
|
13
|
+
import ida_ida
|
|
14
|
+
import ida_kernwin
|
|
15
|
+
import ida_lines
|
|
16
|
+
import ida_nalt
|
|
17
|
+
import ida_name
|
|
18
|
+
import ida_segment
|
|
19
|
+
import ida_typeinf
|
|
20
|
+
import ida_xref
|
|
21
|
+
|
|
22
|
+
from .rpc import tool, unsafe
|
|
23
|
+
from .sync import idasync
|
|
24
|
+
from .utils import parse_address, get_function
|
|
25
|
+
|
|
26
|
+
# ============================================================================
|
|
27
|
+
# Python Evaluation
|
|
28
|
+
# ============================================================================
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@tool
|
|
32
|
+
@idasync
|
|
33
|
+
@unsafe
|
|
34
|
+
def py_eval(
|
|
35
|
+
code: Annotated[str, "Python code"],
|
|
36
|
+
) -> dict:
|
|
37
|
+
"""Execute Python code in IDA context.
|
|
38
|
+
Returns dict with result/stdout/stderr.
|
|
39
|
+
Has access to all IDA API modules.
|
|
40
|
+
Supports Jupyter-style evaluation."""
|
|
41
|
+
# Capture stdout/stderr
|
|
42
|
+
stdout_capture = io.StringIO()
|
|
43
|
+
stderr_capture = io.StringIO()
|
|
44
|
+
old_stdout = sys.stdout
|
|
45
|
+
old_stderr = sys.stderr
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
sys.stdout = stdout_capture
|
|
49
|
+
sys.stderr = stderr_capture
|
|
50
|
+
|
|
51
|
+
# Create execution context with IDA modules (lazy import to avoid errors)
|
|
52
|
+
def lazy_import(module_name):
|
|
53
|
+
try:
|
|
54
|
+
return __import__(module_name)
|
|
55
|
+
except Exception:
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
exec_globals = {
|
|
59
|
+
"__builtins__": __builtins__,
|
|
60
|
+
"idaapi": idaapi,
|
|
61
|
+
"idc": idc,
|
|
62
|
+
"idautils": lazy_import("idautils"),
|
|
63
|
+
"ida_allins": lazy_import("ida_allins"),
|
|
64
|
+
"ida_auto": lazy_import("ida_auto"),
|
|
65
|
+
"ida_bitrange": lazy_import("ida_bitrange"),
|
|
66
|
+
"ida_bytes": ida_bytes,
|
|
67
|
+
"ida_dbg": ida_dbg,
|
|
68
|
+
"ida_dirtree": lazy_import("ida_dirtree"),
|
|
69
|
+
"ida_diskio": lazy_import("ida_diskio"),
|
|
70
|
+
"ida_entry": ida_entry,
|
|
71
|
+
"ida_expr": lazy_import("ida_expr"),
|
|
72
|
+
"ida_fixup": lazy_import("ida_fixup"),
|
|
73
|
+
"ida_fpro": lazy_import("ida_fpro"),
|
|
74
|
+
"ida_frame": ida_frame,
|
|
75
|
+
"ida_funcs": ida_funcs,
|
|
76
|
+
"ida_gdl": lazy_import("ida_gdl"),
|
|
77
|
+
"ida_graph": lazy_import("ida_graph"),
|
|
78
|
+
"ida_hexrays": ida_hexrays,
|
|
79
|
+
"ida_ida": ida_ida,
|
|
80
|
+
"ida_idd": lazy_import("ida_idd"),
|
|
81
|
+
"ida_idp": lazy_import("ida_idp"),
|
|
82
|
+
"ida_ieee": lazy_import("ida_ieee"),
|
|
83
|
+
"ida_kernwin": ida_kernwin,
|
|
84
|
+
"ida_libfuncs": lazy_import("ida_libfuncs"),
|
|
85
|
+
"ida_lines": ida_lines,
|
|
86
|
+
"ida_loader": lazy_import("ida_loader"),
|
|
87
|
+
"ida_merge": lazy_import("ida_merge"),
|
|
88
|
+
"ida_mergemod": lazy_import("ida_mergemod"),
|
|
89
|
+
"ida_moves": lazy_import("ida_moves"),
|
|
90
|
+
"ida_nalt": ida_nalt,
|
|
91
|
+
"ida_name": ida_name,
|
|
92
|
+
"ida_netnode": lazy_import("ida_netnode"),
|
|
93
|
+
"ida_offset": lazy_import("ida_offset"),
|
|
94
|
+
"ida_pro": lazy_import("ida_pro"),
|
|
95
|
+
"ida_problems": lazy_import("ida_problems"),
|
|
96
|
+
"ida_range": lazy_import("ida_range"),
|
|
97
|
+
"ida_regfinder": lazy_import("ida_regfinder"),
|
|
98
|
+
"ida_registry": lazy_import("ida_registry"),
|
|
99
|
+
"ida_search": lazy_import("ida_search"),
|
|
100
|
+
"ida_segment": ida_segment,
|
|
101
|
+
"ida_segregs": lazy_import("ida_segregs"),
|
|
102
|
+
"ida_srclang": lazy_import("ida_srclang"),
|
|
103
|
+
"ida_strlist": lazy_import("ida_strlist"),
|
|
104
|
+
"ida_struct": lazy_import("ida_struct"),
|
|
105
|
+
"ida_tryblks": lazy_import("ida_tryblks"),
|
|
106
|
+
"ida_typeinf": ida_typeinf,
|
|
107
|
+
"ida_ua": lazy_import("ida_ua"),
|
|
108
|
+
"ida_undo": lazy_import("ida_undo"),
|
|
109
|
+
"ida_xref": ida_xref,
|
|
110
|
+
"ida_enum": lazy_import("ida_enum"),
|
|
111
|
+
"parse_address": parse_address,
|
|
112
|
+
"get_function": get_function,
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
result_value = None
|
|
116
|
+
exec_locals = {}
|
|
117
|
+
|
|
118
|
+
# Parse code with AST to properly handle execution
|
|
119
|
+
try:
|
|
120
|
+
tree = ast.parse(code)
|
|
121
|
+
except SyntaxError:
|
|
122
|
+
# If parsing fails, fall back to direct exec
|
|
123
|
+
exec(code, exec_globals, exec_locals)
|
|
124
|
+
exec_globals.update(exec_locals)
|
|
125
|
+
if "result" in exec_locals:
|
|
126
|
+
result_value = str(exec_locals["result"])
|
|
127
|
+
elif exec_locals:
|
|
128
|
+
last_key = list(exec_locals.keys())[-1]
|
|
129
|
+
result_value = str(exec_locals[last_key])
|
|
130
|
+
else:
|
|
131
|
+
if not tree.body:
|
|
132
|
+
# Empty code
|
|
133
|
+
pass
|
|
134
|
+
elif len(tree.body) == 1 and isinstance(tree.body[0], ast.Expr):
|
|
135
|
+
# Single expression - use eval
|
|
136
|
+
result_value = str(eval(code, exec_globals))
|
|
137
|
+
elif isinstance(tree.body[-1], ast.Expr):
|
|
138
|
+
# Multiple statements, last one is an expression (Jupyter-style)
|
|
139
|
+
# Execute all statements except the last
|
|
140
|
+
if len(tree.body) > 1:
|
|
141
|
+
exec_tree = ast.Module(body=tree.body[:-1], type_ignores=[])
|
|
142
|
+
exec(compile(exec_tree, "<string>", "exec"), exec_globals, exec_locals)
|
|
143
|
+
exec_globals.update(exec_locals)
|
|
144
|
+
# Eval only the last expression
|
|
145
|
+
eval_tree = ast.Expression(body=tree.body[-1].value)
|
|
146
|
+
result_value = str(eval(compile(eval_tree, "<string>", "eval"), exec_globals))
|
|
147
|
+
else:
|
|
148
|
+
# All statements (no trailing expression)
|
|
149
|
+
exec(code, exec_globals, exec_locals)
|
|
150
|
+
exec_globals.update(exec_locals)
|
|
151
|
+
# Return 'result' variable if explicitly set
|
|
152
|
+
if "result" in exec_locals:
|
|
153
|
+
result_value = str(exec_locals["result"])
|
|
154
|
+
# Return last assigned variable
|
|
155
|
+
elif exec_locals:
|
|
156
|
+
last_key = list(exec_locals.keys())[-1]
|
|
157
|
+
result_value = str(exec_locals[last_key])
|
|
158
|
+
|
|
159
|
+
# Collect output
|
|
160
|
+
stdout_text = stdout_capture.getvalue()
|
|
161
|
+
stderr_text = stderr_capture.getvalue()
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
"result": result_value or "",
|
|
165
|
+
"stdout": stdout_text,
|
|
166
|
+
"stderr": stderr_text,
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
except Exception:
|
|
170
|
+
import traceback
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
"result": "",
|
|
174
|
+
"stdout": "",
|
|
175
|
+
"stderr": traceback.format_exc(),
|
|
176
|
+
}
|
|
177
|
+
finally:
|
|
178
|
+
sys.stdout = old_stdout
|
|
179
|
+
sys.stderr = old_stderr
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"""MCP Resources - browsable IDB state
|
|
2
|
+
|
|
3
|
+
Resources represent browsable state (read-only data) following MCP's philosophy.
|
|
4
|
+
Use tools for actions that modify state or perform expensive computations.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Annotated
|
|
8
|
+
|
|
9
|
+
import ida_funcs
|
|
10
|
+
import ida_nalt
|
|
11
|
+
import ida_segment
|
|
12
|
+
import ida_typeinf
|
|
13
|
+
import idaapi
|
|
14
|
+
import idautils
|
|
15
|
+
import idc
|
|
16
|
+
|
|
17
|
+
from .rpc import resource
|
|
18
|
+
from .sync import idasync
|
|
19
|
+
from .utils import (
|
|
20
|
+
Metadata,
|
|
21
|
+
Segment,
|
|
22
|
+
StructureDefinition,
|
|
23
|
+
StructureMember,
|
|
24
|
+
get_image_size,
|
|
25
|
+
parse_address,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# ============================================================================
|
|
30
|
+
# Core IDB State
|
|
31
|
+
# ============================================================================
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@resource("ida://idb/metadata")
|
|
35
|
+
@idasync
|
|
36
|
+
def idb_metadata_resource() -> Metadata:
|
|
37
|
+
"""Get IDB file metadata (path, arch, base address, size, hashes)"""
|
|
38
|
+
import hashlib
|
|
39
|
+
|
|
40
|
+
path = idc.get_idb_path()
|
|
41
|
+
module = ida_nalt.get_root_filename()
|
|
42
|
+
base = hex(idaapi.get_imagebase())
|
|
43
|
+
size = hex(get_image_size())
|
|
44
|
+
|
|
45
|
+
input_path = ida_nalt.get_input_file_path()
|
|
46
|
+
try:
|
|
47
|
+
with open(input_path, "rb") as f:
|
|
48
|
+
data = f.read()
|
|
49
|
+
md5 = hashlib.md5(data).hexdigest()
|
|
50
|
+
sha256 = hashlib.sha256(data).hexdigest()
|
|
51
|
+
import zlib
|
|
52
|
+
|
|
53
|
+
crc32 = hex(zlib.crc32(data) & 0xFFFFFFFF)
|
|
54
|
+
filesize = hex(len(data))
|
|
55
|
+
except Exception:
|
|
56
|
+
md5 = sha256 = crc32 = filesize = "unavailable"
|
|
57
|
+
|
|
58
|
+
return Metadata(
|
|
59
|
+
path=path,
|
|
60
|
+
module=module,
|
|
61
|
+
base=base,
|
|
62
|
+
size=size,
|
|
63
|
+
md5=md5,
|
|
64
|
+
sha256=sha256,
|
|
65
|
+
crc32=crc32,
|
|
66
|
+
filesize=filesize,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@resource("ida://idb/segments")
|
|
71
|
+
@idasync
|
|
72
|
+
def idb_segments_resource() -> list[Segment]:
|
|
73
|
+
"""Get all memory segments with permissions"""
|
|
74
|
+
segments = []
|
|
75
|
+
for seg_ea in idautils.Segments():
|
|
76
|
+
seg = idaapi.getseg(seg_ea)
|
|
77
|
+
if seg:
|
|
78
|
+
perms = []
|
|
79
|
+
if seg.perm & idaapi.SEGPERM_READ:
|
|
80
|
+
perms.append("r")
|
|
81
|
+
if seg.perm & idaapi.SEGPERM_WRITE:
|
|
82
|
+
perms.append("w")
|
|
83
|
+
if seg.perm & idaapi.SEGPERM_EXEC:
|
|
84
|
+
perms.append("x")
|
|
85
|
+
|
|
86
|
+
segments.append(
|
|
87
|
+
Segment(
|
|
88
|
+
name=ida_segment.get_segm_name(seg),
|
|
89
|
+
start=hex(seg.start_ea),
|
|
90
|
+
end=hex(seg.end_ea),
|
|
91
|
+
size=hex(seg.size()),
|
|
92
|
+
permissions="".join(perms) if perms else "---",
|
|
93
|
+
)
|
|
94
|
+
)
|
|
95
|
+
return segments
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@resource("ida://idb/entrypoints")
|
|
99
|
+
@idasync
|
|
100
|
+
def idb_entrypoints_resource() -> list[dict]:
|
|
101
|
+
"""Get entry points (main, TLS callbacks, etc.)"""
|
|
102
|
+
entrypoints = []
|
|
103
|
+
entry_count = ida_nalt.get_entry_qty()
|
|
104
|
+
for i in range(entry_count):
|
|
105
|
+
ordinal = ida_nalt.get_entry_ordinal(i)
|
|
106
|
+
ea = ida_nalt.get_entry(ordinal)
|
|
107
|
+
name = ida_nalt.get_entry_name(ordinal)
|
|
108
|
+
entrypoints.append({"addr": hex(ea), "name": name, "ordinal": ordinal})
|
|
109
|
+
return entrypoints
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# ============================================================================
|
|
113
|
+
# UI State
|
|
114
|
+
# ============================================================================
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@resource("ida://cursor")
|
|
118
|
+
@idasync
|
|
119
|
+
def cursor_resource() -> dict:
|
|
120
|
+
"""Get current cursor position and function"""
|
|
121
|
+
import ida_kernwin
|
|
122
|
+
|
|
123
|
+
ea = ida_kernwin.get_screen_ea()
|
|
124
|
+
func = idaapi.get_func(ea)
|
|
125
|
+
|
|
126
|
+
result = {"addr": hex(ea)}
|
|
127
|
+
if func:
|
|
128
|
+
try:
|
|
129
|
+
func_name = func.get_name()
|
|
130
|
+
except AttributeError:
|
|
131
|
+
func_name = ida_funcs.get_func_name(func.start_ea)
|
|
132
|
+
|
|
133
|
+
result["function"] = {
|
|
134
|
+
"addr": hex(func.start_ea),
|
|
135
|
+
"name": func_name,
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return result
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@resource("ida://selection")
|
|
142
|
+
@idasync
|
|
143
|
+
def selection_resource() -> dict:
|
|
144
|
+
"""Get current selection range (if any)"""
|
|
145
|
+
import ida_kernwin
|
|
146
|
+
|
|
147
|
+
start = ida_kernwin.read_range_selection(None)
|
|
148
|
+
if start:
|
|
149
|
+
return {"start": hex(start[0]), "end": hex(start[1]) if start[1] else None}
|
|
150
|
+
return {"selection": None}
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# ============================================================================
|
|
154
|
+
# Type Information
|
|
155
|
+
# ============================================================================
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@resource("ida://types")
|
|
159
|
+
@idasync
|
|
160
|
+
def types_resource() -> list[dict]:
|
|
161
|
+
"""Get all local types"""
|
|
162
|
+
types = []
|
|
163
|
+
for ordinal in range(1, ida_typeinf.get_ordinal_qty(None)):
|
|
164
|
+
tif = ida_typeinf.tinfo_t()
|
|
165
|
+
if tif.get_numbered_type(None, ordinal):
|
|
166
|
+
name = tif.get_type_name()
|
|
167
|
+
types.append({"ordinal": ordinal, "name": name, "type": str(tif)})
|
|
168
|
+
return types
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@resource("ida://structs")
|
|
172
|
+
@idasync
|
|
173
|
+
def structs_resource() -> list[dict]:
|
|
174
|
+
"""Get all structures/unions"""
|
|
175
|
+
structs = []
|
|
176
|
+
limit = ida_typeinf.get_ordinal_limit()
|
|
177
|
+
for ordinal in range(1, limit):
|
|
178
|
+
tif = ida_typeinf.tinfo_t()
|
|
179
|
+
if tif.get_numbered_type(None, ordinal) and tif.is_udt():
|
|
180
|
+
udt_data = ida_typeinf.udt_type_data_t()
|
|
181
|
+
is_union = False
|
|
182
|
+
if tif.get_udt_details(udt_data):
|
|
183
|
+
is_union = udt_data.is_union
|
|
184
|
+
structs.append(
|
|
185
|
+
{
|
|
186
|
+
"name": tif.get_type_name(),
|
|
187
|
+
"size": hex(tif.get_size()),
|
|
188
|
+
"is_union": is_union,
|
|
189
|
+
}
|
|
190
|
+
)
|
|
191
|
+
return structs
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
@resource("ida://struct/{name}")
|
|
195
|
+
@idasync
|
|
196
|
+
def struct_name_resource(name: Annotated[str, "Structure name"]) -> dict:
|
|
197
|
+
"""Get structure definition with fields"""
|
|
198
|
+
tif = ida_typeinf.tinfo_t()
|
|
199
|
+
if not tif.get_named_type(None, name):
|
|
200
|
+
return {"error": f"Structure not found: {name}"}
|
|
201
|
+
|
|
202
|
+
if not tif.is_udt():
|
|
203
|
+
return {"error": f"'{name}' is not a structure/union"}
|
|
204
|
+
|
|
205
|
+
udt_data = ida_typeinf.udt_type_data_t()
|
|
206
|
+
if not tif.get_udt_details(udt_data):
|
|
207
|
+
return {"error": f"Failed to get struct details for '{name}'"}
|
|
208
|
+
|
|
209
|
+
members = []
|
|
210
|
+
for member in udt_data:
|
|
211
|
+
members.append(
|
|
212
|
+
StructureMember(
|
|
213
|
+
name=member.name,
|
|
214
|
+
offset=hex(member.offset // 8),
|
|
215
|
+
size=hex(member.size // 8),
|
|
216
|
+
type=str(member.type),
|
|
217
|
+
)
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
return StructureDefinition(name=name, size=hex(tif.get_size()), members=members)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
# ============================================================================
|
|
224
|
+
# Import/Export Lookup by Name
|
|
225
|
+
# ============================================================================
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
@resource("ida://import/{name}")
|
|
229
|
+
@idasync
|
|
230
|
+
def import_name_resource(name: Annotated[str, "Import name"]) -> dict:
|
|
231
|
+
"""Get specific import details by name"""
|
|
232
|
+
nimps = ida_nalt.get_import_module_qty()
|
|
233
|
+
for i in range(nimps):
|
|
234
|
+
module = ida_nalt.get_import_module_name(i)
|
|
235
|
+
result = {}
|
|
236
|
+
|
|
237
|
+
def callback(ea, imp_name, ordinal):
|
|
238
|
+
if imp_name == name or f"ord_{ordinal}" == name:
|
|
239
|
+
result.update(
|
|
240
|
+
{
|
|
241
|
+
"addr": hex(ea),
|
|
242
|
+
"name": imp_name or f"ord_{ordinal}",
|
|
243
|
+
"module": module,
|
|
244
|
+
"ordinal": ordinal,
|
|
245
|
+
}
|
|
246
|
+
)
|
|
247
|
+
return False # Stop enumeration
|
|
248
|
+
return True
|
|
249
|
+
|
|
250
|
+
ida_nalt.enum_import_names(i, callback)
|
|
251
|
+
if result:
|
|
252
|
+
return result
|
|
253
|
+
|
|
254
|
+
return {"error": f"Import not found: {name}"}
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@resource("ida://export/{name}")
|
|
258
|
+
@idasync
|
|
259
|
+
def export_name_resource(name: Annotated[str, "Export name"]) -> dict:
|
|
260
|
+
"""Get specific export details by name"""
|
|
261
|
+
entry_count = ida_nalt.get_entry_qty()
|
|
262
|
+
for i in range(entry_count):
|
|
263
|
+
ordinal = ida_nalt.get_entry_ordinal(i)
|
|
264
|
+
ea = ida_nalt.get_entry(ordinal)
|
|
265
|
+
entry_name = ida_nalt.get_entry_name(ordinal)
|
|
266
|
+
|
|
267
|
+
if entry_name == name:
|
|
268
|
+
return {
|
|
269
|
+
"addr": hex(ea),
|
|
270
|
+
"name": entry_name,
|
|
271
|
+
"ordinal": ordinal,
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return {"error": f"Export not found: {name}"}
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
# ============================================================================
|
|
278
|
+
# Cross-references
|
|
279
|
+
# ============================================================================
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
@resource("ida://xrefs/from/{addr}")
|
|
283
|
+
@idasync
|
|
284
|
+
def xrefs_from_resource(addr: Annotated[str, "Source address"]) -> list[dict]:
|
|
285
|
+
"""Get cross-references from address"""
|
|
286
|
+
ea = parse_address(addr)
|
|
287
|
+
xrefs = []
|
|
288
|
+
for xref in idautils.XrefsFrom(ea, 0):
|
|
289
|
+
xrefs.append(
|
|
290
|
+
{
|
|
291
|
+
"addr": hex(xref.to),
|
|
292
|
+
"type": "code" if xref.iscode else "data",
|
|
293
|
+
}
|
|
294
|
+
)
|
|
295
|
+
return xrefs
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""Stack frame operations for IDA Pro MCP.
|
|
2
|
+
|
|
3
|
+
This module provides batch operations for managing stack frame variables,
|
|
4
|
+
including reading, creating, and deleting stack variables in functions.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Annotated
|
|
8
|
+
import ida_typeinf
|
|
9
|
+
import ida_frame
|
|
10
|
+
import idaapi
|
|
11
|
+
|
|
12
|
+
from .rpc import tool
|
|
13
|
+
from .sync import idasync
|
|
14
|
+
from .utils import (
|
|
15
|
+
normalize_list_input,
|
|
16
|
+
normalize_dict_list,
|
|
17
|
+
parse_address,
|
|
18
|
+
get_type_by_name,
|
|
19
|
+
StackVarDecl,
|
|
20
|
+
StackVarDelete,
|
|
21
|
+
get_stack_frame_variables_internal,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# ============================================================================
|
|
26
|
+
# Stack Frame Operations
|
|
27
|
+
# ============================================================================
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@tool
|
|
31
|
+
@idasync
|
|
32
|
+
def stack_frame(addrs: Annotated[list[str] | str, "Address(es)"]) -> list[dict]:
|
|
33
|
+
"""Get stack vars"""
|
|
34
|
+
addrs = normalize_list_input(addrs)
|
|
35
|
+
results = []
|
|
36
|
+
|
|
37
|
+
for addr in addrs:
|
|
38
|
+
try:
|
|
39
|
+
ea = parse_address(addr)
|
|
40
|
+
vars = get_stack_frame_variables_internal(ea, True)
|
|
41
|
+
results.append({"addr": addr, "vars": vars})
|
|
42
|
+
except Exception as e:
|
|
43
|
+
results.append({"addr": addr, "vars": None, "error": str(e)})
|
|
44
|
+
|
|
45
|
+
return results
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@tool
|
|
49
|
+
@idasync
|
|
50
|
+
def declare_stack(
|
|
51
|
+
items: list[StackVarDecl] | StackVarDecl,
|
|
52
|
+
):
|
|
53
|
+
"""Create stack vars"""
|
|
54
|
+
items = normalize_dict_list(items)
|
|
55
|
+
results = []
|
|
56
|
+
for item in items:
|
|
57
|
+
fn_addr = item.get("addr", "")
|
|
58
|
+
offset = item.get("offset", "")
|
|
59
|
+
var_name = item.get("name", "")
|
|
60
|
+
type_name = item.get("ty", "")
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
func = idaapi.get_func(parse_address(fn_addr))
|
|
64
|
+
if not func:
|
|
65
|
+
results.append(
|
|
66
|
+
{"addr": fn_addr, "name": var_name, "error": "No function found"}
|
|
67
|
+
)
|
|
68
|
+
continue
|
|
69
|
+
|
|
70
|
+
ea = parse_address(offset)
|
|
71
|
+
|
|
72
|
+
frame_tif = ida_typeinf.tinfo_t()
|
|
73
|
+
if not ida_frame.get_func_frame(frame_tif, func):
|
|
74
|
+
results.append(
|
|
75
|
+
{"addr": fn_addr, "name": var_name, "error": "No frame returned"}
|
|
76
|
+
)
|
|
77
|
+
continue
|
|
78
|
+
|
|
79
|
+
tif = get_type_by_name(type_name)
|
|
80
|
+
if not ida_frame.define_stkvar(func, var_name, ea, tif):
|
|
81
|
+
results.append(
|
|
82
|
+
{"addr": fn_addr, "name": var_name, "error": "Failed to define"}
|
|
83
|
+
)
|
|
84
|
+
continue
|
|
85
|
+
|
|
86
|
+
results.append({"addr": fn_addr, "name": var_name, "ok": True})
|
|
87
|
+
except Exception as e:
|
|
88
|
+
results.append({"addr": fn_addr, "name": var_name, "error": str(e)})
|
|
89
|
+
|
|
90
|
+
return results
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@tool
|
|
94
|
+
@idasync
|
|
95
|
+
def delete_stack(
|
|
96
|
+
items: list[StackVarDelete] | StackVarDelete,
|
|
97
|
+
):
|
|
98
|
+
"""Delete stack vars"""
|
|
99
|
+
|
|
100
|
+
items = normalize_dict_list(items)
|
|
101
|
+
results = []
|
|
102
|
+
for item in items:
|
|
103
|
+
fn_addr = item.get("addr", "")
|
|
104
|
+
var_name = item.get("name", "")
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
func = idaapi.get_func(parse_address(fn_addr))
|
|
108
|
+
if not func:
|
|
109
|
+
results.append(
|
|
110
|
+
{"addr": fn_addr, "name": var_name, "error": "No function found"}
|
|
111
|
+
)
|
|
112
|
+
continue
|
|
113
|
+
|
|
114
|
+
frame_tif = ida_typeinf.tinfo_t()
|
|
115
|
+
if not ida_frame.get_func_frame(frame_tif, func):
|
|
116
|
+
results.append(
|
|
117
|
+
{"addr": fn_addr, "name": var_name, "error": "No frame returned"}
|
|
118
|
+
)
|
|
119
|
+
continue
|
|
120
|
+
|
|
121
|
+
idx, udm = frame_tif.get_udm(var_name)
|
|
122
|
+
if not udm:
|
|
123
|
+
results.append(
|
|
124
|
+
{
|
|
125
|
+
"addr": fn_addr,
|
|
126
|
+
"name": var_name,
|
|
127
|
+
"error": f"{var_name} not found",
|
|
128
|
+
}
|
|
129
|
+
)
|
|
130
|
+
continue
|
|
131
|
+
|
|
132
|
+
tid = frame_tif.get_udm_tid(idx)
|
|
133
|
+
if ida_frame.is_special_frame_member(tid):
|
|
134
|
+
results.append(
|
|
135
|
+
{
|
|
136
|
+
"addr": fn_addr,
|
|
137
|
+
"name": var_name,
|
|
138
|
+
"error": f"{var_name} is special frame member",
|
|
139
|
+
}
|
|
140
|
+
)
|
|
141
|
+
continue
|
|
142
|
+
|
|
143
|
+
udm = ida_typeinf.udm_t()
|
|
144
|
+
frame_tif.get_udm_by_tid(udm, tid)
|
|
145
|
+
offset = udm.offset // 8
|
|
146
|
+
size = udm.size // 8
|
|
147
|
+
if ida_frame.is_funcarg_off(func, offset):
|
|
148
|
+
results.append(
|
|
149
|
+
{
|
|
150
|
+
"addr": fn_addr,
|
|
151
|
+
"name": var_name,
|
|
152
|
+
"error": f"{var_name} is argument member",
|
|
153
|
+
}
|
|
154
|
+
)
|
|
155
|
+
continue
|
|
156
|
+
|
|
157
|
+
if not ida_frame.delete_frame_members(func, offset, offset + size):
|
|
158
|
+
results.append(
|
|
159
|
+
{"addr": fn_addr, "name": var_name, "error": "Failed to delete"}
|
|
160
|
+
)
|
|
161
|
+
continue
|
|
162
|
+
|
|
163
|
+
results.append({"addr": fn_addr, "name": var_name, "ok": True})
|
|
164
|
+
except Exception as e:
|
|
165
|
+
results.append({"addr": fn_addr, "name": var_name, "error": str(e)})
|
|
166
|
+
|
|
167
|
+
return results
|