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.
Files changed (45) hide show
  1. ida_pro_mcp/__init__.py +0 -0
  2. ida_pro_mcp/__main__.py +6 -0
  3. ida_pro_mcp/ida_mcp/__init__.py +68 -0
  4. ida_pro_mcp/ida_mcp/api_analysis.py +1296 -0
  5. ida_pro_mcp/ida_mcp/api_core.py +337 -0
  6. ida_pro_mcp/ida_mcp/api_debug.py +617 -0
  7. ida_pro_mcp/ida_mcp/api_memory.py +304 -0
  8. ida_pro_mcp/ida_mcp/api_modify.py +406 -0
  9. ida_pro_mcp/ida_mcp/api_python.py +179 -0
  10. ida_pro_mcp/ida_mcp/api_resources.py +295 -0
  11. ida_pro_mcp/ida_mcp/api_stack.py +167 -0
  12. ida_pro_mcp/ida_mcp/api_types.py +480 -0
  13. ida_pro_mcp/ida_mcp/auth.py +166 -0
  14. ida_pro_mcp/ida_mcp/cache.py +232 -0
  15. ida_pro_mcp/ida_mcp/config.py +228 -0
  16. ida_pro_mcp/ida_mcp/framework.py +547 -0
  17. ida_pro_mcp/ida_mcp/http.py +859 -0
  18. ida_pro_mcp/ida_mcp/port_utils.py +104 -0
  19. ida_pro_mcp/ida_mcp/rpc.py +187 -0
  20. ida_pro_mcp/ida_mcp/server_manager.py +339 -0
  21. ida_pro_mcp/ida_mcp/sync.py +233 -0
  22. ida_pro_mcp/ida_mcp/tests/__init__.py +14 -0
  23. ida_pro_mcp/ida_mcp/tests/test_api_analysis.py +336 -0
  24. ida_pro_mcp/ida_mcp/tests/test_api_core.py +237 -0
  25. ida_pro_mcp/ida_mcp/tests/test_api_memory.py +207 -0
  26. ida_pro_mcp/ida_mcp/tests/test_api_modify.py +123 -0
  27. ida_pro_mcp/ida_mcp/tests/test_api_resources.py +199 -0
  28. ida_pro_mcp/ida_mcp/tests/test_api_stack.py +77 -0
  29. ida_pro_mcp/ida_mcp/tests/test_api_types.py +249 -0
  30. ida_pro_mcp/ida_mcp/ui.py +357 -0
  31. ida_pro_mcp/ida_mcp/utils.py +1186 -0
  32. ida_pro_mcp/ida_mcp/zeromcp/__init__.py +5 -0
  33. ida_pro_mcp/ida_mcp/zeromcp/jsonrpc.py +384 -0
  34. ida_pro_mcp/ida_mcp/zeromcp/mcp.py +883 -0
  35. ida_pro_mcp/ida_mcp.py +186 -0
  36. ida_pro_mcp/idalib_server.py +354 -0
  37. ida_pro_mcp/idalib_session_manager.py +259 -0
  38. ida_pro_mcp/server.py +1060 -0
  39. ida_pro_mcp/test.py +170 -0
  40. ida_pro_mcp_xjoker-1.0.1.dist-info/METADATA +405 -0
  41. ida_pro_mcp_xjoker-1.0.1.dist-info/RECORD +45 -0
  42. ida_pro_mcp_xjoker-1.0.1.dist-info/WHEEL +5 -0
  43. ida_pro_mcp_xjoker-1.0.1.dist-info/entry_points.txt +4 -0
  44. ida_pro_mcp_xjoker-1.0.1.dist-info/licenses/LICENSE +21 -0
  45. ida_pro_mcp_xjoker-1.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,617 @@
1
+ """Debugger operations for IDA Pro MCP.
2
+
3
+ This module provides comprehensive debugging functionality including:
4
+ - Debugger control (start, exit, continue, step, run_to)
5
+ - Breakpoint management (add, delete, enable/disable, list)
6
+ - Register inspection (all registers, GP registers, specific registers)
7
+ - Memory operations (read/write debugger memory)
8
+ - Call stack inspection
9
+ """
10
+
11
+ import os
12
+ from typing import Annotated
13
+
14
+ import ida_dbg
15
+ import ida_entry
16
+ import ida_idd
17
+ import ida_idaapi
18
+ import ida_name
19
+ import idaapi
20
+
21
+ from .rpc import tool, unsafe, ext
22
+ from .sync import idasync, IDAError
23
+ from .utils import (
24
+ RegisterValue,
25
+ ThreadRegisters,
26
+ Breakpoint,
27
+ BreakpointOp,
28
+ MemoryRead,
29
+ MemoryPatch,
30
+ normalize_list_input,
31
+ normalize_dict_list,
32
+ parse_address,
33
+ )
34
+
35
+
36
+ # ============================================================================
37
+ # Constants and Helper Functions
38
+ # ============================================================================
39
+
40
+ GENERAL_PURPOSE_REGISTERS = {
41
+ "EAX",
42
+ "EBX",
43
+ "ECX",
44
+ "EDX",
45
+ "ESI",
46
+ "EDI",
47
+ "EBP",
48
+ "ESP",
49
+ "EIP",
50
+ "RAX",
51
+ "RBX",
52
+ "RCX",
53
+ "RDX",
54
+ "RSI",
55
+ "RDI",
56
+ "RBP",
57
+ "RSP",
58
+ "RIP",
59
+ "R8",
60
+ "R9",
61
+ "R10",
62
+ "R11",
63
+ "R12",
64
+ "R13",
65
+ "R14",
66
+ "R15",
67
+ }
68
+
69
+
70
+ def dbg_ensure_running() -> "ida_idd.debugger_t":
71
+ dbg = ida_idd.get_dbg()
72
+ if not dbg:
73
+ raise IDAError("Debugger not running")
74
+ if ida_dbg.get_ip_val() is None:
75
+ raise IDAError("Debugger not running")
76
+ return dbg
77
+
78
+
79
+ def _get_registers_for_thread(dbg: "ida_idd.debugger_t", tid: int) -> ThreadRegisters:
80
+ """Helper to get registers for a specific thread."""
81
+ regs = []
82
+ regvals: ida_idd.regvals_t = ida_dbg.get_reg_vals(tid)
83
+ for reg_index, rv in enumerate(regvals):
84
+ rv: ida_idd.regval_t
85
+ reg_info = dbg.regs(reg_index)
86
+
87
+ try:
88
+ reg_value = rv.pyval(reg_info.dtype)
89
+ except ValueError:
90
+ reg_value = ida_idaapi.BADADDR
91
+
92
+ if isinstance(reg_value, int):
93
+ reg_value = hex(reg_value)
94
+ if isinstance(reg_value, bytes):
95
+ reg_value = reg_value.hex(" ")
96
+ else:
97
+ reg_value = str(reg_value)
98
+ regs.append(
99
+ RegisterValue(
100
+ name=reg_info.name,
101
+ value=reg_value,
102
+ )
103
+ )
104
+ return ThreadRegisters(
105
+ thread_id=tid,
106
+ registers=regs,
107
+ )
108
+
109
+
110
+ def _get_registers_general_for_thread(
111
+ dbg: "ida_idd.debugger_t", tid: int
112
+ ) -> ThreadRegisters:
113
+ """Helper to get general-purpose registers for a specific thread."""
114
+ all_registers = _get_registers_for_thread(dbg, tid)
115
+ general_registers = [
116
+ reg
117
+ for reg in all_registers["registers"]
118
+ if reg["name"] in GENERAL_PURPOSE_REGISTERS
119
+ ]
120
+ return ThreadRegisters(
121
+ thread_id=tid,
122
+ registers=general_registers,
123
+ )
124
+
125
+
126
+ def _get_registers_specific_for_thread(
127
+ dbg: "ida_idd.debugger_t", tid: int, register_names: list[str]
128
+ ) -> ThreadRegisters:
129
+ """Helper to get specific registers for a given thread."""
130
+ all_registers = _get_registers_for_thread(dbg, tid)
131
+ specific_registers = [
132
+ reg for reg in all_registers["registers"] if reg["name"] in register_names
133
+ ]
134
+ return ThreadRegisters(
135
+ thread_id=tid,
136
+ registers=specific_registers,
137
+ )
138
+
139
+
140
+ def list_breakpoints():
141
+ breakpoints: list[Breakpoint] = []
142
+ for i in range(ida_dbg.get_bpt_qty()):
143
+ bpt = ida_dbg.bpt_t()
144
+ if ida_dbg.getn_bpt(i, bpt):
145
+ breakpoints.append(
146
+ Breakpoint(
147
+ addr=hex(bpt.ea),
148
+ enabled=bpt.flags & ida_dbg.BPT_ENABLED,
149
+ condition=str(bpt.condition) if bpt.condition else None,
150
+ )
151
+ )
152
+ return breakpoints
153
+
154
+
155
+ # ============================================================================
156
+ # Debugger Control Operations
157
+ # ============================================================================
158
+
159
+
160
+ @ext("dbg")
161
+ @unsafe
162
+ @tool
163
+ @idasync
164
+ def dbg_start():
165
+ """Start debugger"""
166
+ if len(list_breakpoints()) == 0:
167
+ for i in range(ida_entry.get_entry_qty()):
168
+ ordinal = ida_entry.get_entry_ordinal(i)
169
+ addr = ida_entry.get_entry(ordinal)
170
+ if addr != ida_idaapi.BADADDR:
171
+ ida_dbg.add_bpt(addr, 0, idaapi.BPT_SOFT)
172
+
173
+ if idaapi.start_process("", "", "") == 1:
174
+ ip = ida_dbg.get_ip_val()
175
+ if ip is not None:
176
+ return hex(ip)
177
+ raise IDAError("Failed to start debugger")
178
+
179
+
180
+ @ext("dbg")
181
+ @unsafe
182
+ @tool
183
+ @idasync
184
+ def dbg_exit():
185
+ """Exit debugger"""
186
+ dbg_ensure_running()
187
+ if idaapi.exit_process():
188
+ return
189
+ raise IDAError("Failed to exit debugger")
190
+
191
+
192
+ @ext("dbg")
193
+ @unsafe
194
+ @tool
195
+ @idasync
196
+ def dbg_continue() -> str:
197
+ """Continue debugger"""
198
+ dbg_ensure_running()
199
+ if idaapi.continue_process():
200
+ ip = ida_dbg.get_ip_val()
201
+ if ip is not None:
202
+ return hex(ip)
203
+ raise IDAError("Failed to continue debugger")
204
+
205
+
206
+ @ext("dbg")
207
+ @unsafe
208
+ @tool
209
+ @idasync
210
+ def dbg_run_to(
211
+ addr: Annotated[str, "Address"],
212
+ ):
213
+ """Run to address"""
214
+ dbg_ensure_running()
215
+ ea = parse_address(addr)
216
+ if idaapi.run_to(ea):
217
+ ip = ida_dbg.get_ip_val()
218
+ if ip is not None:
219
+ return hex(ip)
220
+ raise IDAError(f"Failed to run to address {hex(ea)}")
221
+
222
+
223
+ @ext("dbg")
224
+ @unsafe
225
+ @tool
226
+ @idasync
227
+ def dbg_step_into():
228
+ """Step into"""
229
+ dbg_ensure_running()
230
+ if idaapi.step_into():
231
+ ip = ida_dbg.get_ip_val()
232
+ if ip is not None:
233
+ return hex(ip)
234
+ raise IDAError("Failed to step into")
235
+
236
+
237
+ @ext("dbg")
238
+ @unsafe
239
+ @tool
240
+ @idasync
241
+ def dbg_step_over():
242
+ """Step over"""
243
+ dbg_ensure_running()
244
+ if idaapi.step_over():
245
+ ip = ida_dbg.get_ip_val()
246
+ if ip is not None:
247
+ return hex(ip)
248
+ raise IDAError("Failed to step over")
249
+
250
+
251
+ # ============================================================================
252
+ # Breakpoint Operations
253
+ # ============================================================================
254
+
255
+
256
+ @ext("dbg")
257
+ @unsafe
258
+ @tool
259
+ @idasync
260
+ def dbg_bps():
261
+ """List breakpoints"""
262
+ return list_breakpoints()
263
+
264
+
265
+ @ext("dbg")
266
+ @unsafe
267
+ @tool
268
+ @idasync
269
+ def dbg_add_bp(
270
+ addrs: Annotated[list[str] | str, "Address(es) to add breakpoints at"],
271
+ ) -> list[dict]:
272
+ """Add breakpoints"""
273
+ addrs = normalize_list_input(addrs)
274
+ results = []
275
+
276
+ for addr in addrs:
277
+ try:
278
+ ea = parse_address(addr)
279
+ if idaapi.add_bpt(ea, 0, idaapi.BPT_SOFT):
280
+ results.append({"addr": addr, "ok": True})
281
+ else:
282
+ breakpoints = list_breakpoints()
283
+ for bpt in breakpoints:
284
+ if bpt["addr"] == hex(ea):
285
+ results.append({"addr": addr, "ok": True})
286
+ break
287
+ else:
288
+ results.append({"addr": addr, "error": "Failed to set breakpoint"})
289
+ except Exception as e:
290
+ results.append({"addr": addr, "error": str(e)})
291
+
292
+ return results
293
+
294
+
295
+ @ext("dbg")
296
+ @unsafe
297
+ @tool
298
+ @idasync
299
+ def dbg_delete_bp(
300
+ addrs: Annotated[list[str] | str, "Address(es) to delete breakpoints from"],
301
+ ) -> list[dict]:
302
+ """Delete breakpoints"""
303
+ addrs = normalize_list_input(addrs)
304
+ results = []
305
+
306
+ for addr in addrs:
307
+ try:
308
+ ea = parse_address(addr)
309
+ if idaapi.del_bpt(ea):
310
+ results.append({"addr": addr, "ok": True})
311
+ else:
312
+ results.append({"addr": addr, "error": "Failed to delete breakpoint"})
313
+ except Exception as e:
314
+ results.append({"addr": addr, "error": str(e)})
315
+
316
+ return results
317
+
318
+
319
+ @ext("dbg")
320
+ @unsafe
321
+ @tool
322
+ @idasync
323
+ def dbg_toggle_bp(items: list[BreakpointOp] | BreakpointOp) -> list[dict]:
324
+ """Enable/disable breakpoints"""
325
+
326
+ items = normalize_dict_list(items)
327
+
328
+ results = []
329
+ for item in items:
330
+ addr = item.get("addr", "")
331
+ enable = item.get("enabled", True)
332
+
333
+ try:
334
+ ea = parse_address(addr)
335
+ if idaapi.enable_bpt(ea, enable):
336
+ results.append({"addr": addr, "ok": True})
337
+ else:
338
+ results.append(
339
+ {
340
+ "addr": addr,
341
+ "error": f"Failed to {'enable' if enable else 'disable'} breakpoint",
342
+ }
343
+ )
344
+ except Exception as e:
345
+ results.append({"addr": addr, "error": str(e)})
346
+
347
+ return results
348
+
349
+
350
+ # ============================================================================
351
+ # Register Operations
352
+ # ============================================================================
353
+
354
+
355
+ @ext("dbg")
356
+ @unsafe
357
+ @tool
358
+ @idasync
359
+ def dbg_regs_all() -> list[ThreadRegisters]:
360
+ """Get all registers"""
361
+ result: list[ThreadRegisters] = []
362
+ dbg = dbg_ensure_running()
363
+ for thread_index in range(ida_dbg.get_thread_qty()):
364
+ tid = ida_dbg.getn_thread(thread_index)
365
+ result.append(_get_registers_for_thread(dbg, tid))
366
+ return result
367
+
368
+
369
+ @ext("dbg")
370
+ @unsafe
371
+ @tool
372
+ @idasync
373
+ def dbg_regs_remote(
374
+ tids: Annotated[list[int] | int, "Thread ID(s) to get registers for"],
375
+ ) -> list[dict]:
376
+ """Get thread registers"""
377
+ if isinstance(tids, int):
378
+ tids = [tids]
379
+
380
+ dbg = dbg_ensure_running()
381
+ available_tids = [ida_dbg.getn_thread(i) for i in range(ida_dbg.get_thread_qty())]
382
+ results = []
383
+
384
+ for tid in tids:
385
+ try:
386
+ if tid not in available_tids:
387
+ results.append(
388
+ {"tid": tid, "regs": None, "error": f"Thread {tid} not found"}
389
+ )
390
+ continue
391
+ regs = _get_registers_for_thread(dbg, tid)
392
+ results.append({"tid": tid, "regs": regs})
393
+ except Exception as e:
394
+ results.append({"tid": tid, "regs": None, "error": str(e)})
395
+
396
+ return results
397
+
398
+
399
+ @ext("dbg")
400
+ @unsafe
401
+ @tool
402
+ @idasync
403
+ def dbg_regs() -> ThreadRegisters:
404
+ """Get current thread registers"""
405
+ dbg = dbg_ensure_running()
406
+ tid = ida_dbg.get_current_thread()
407
+ return _get_registers_for_thread(dbg, tid)
408
+
409
+
410
+ @ext("dbg")
411
+ @unsafe
412
+ @tool
413
+ @idasync
414
+ def dbg_gpregs_remote(
415
+ tids: Annotated[list[int] | int, "Thread ID(s) to get GP registers for"],
416
+ ) -> list[dict]:
417
+ """Get GP registers for threads"""
418
+ if isinstance(tids, int):
419
+ tids = [tids]
420
+
421
+ dbg = dbg_ensure_running()
422
+ available_tids = [ida_dbg.getn_thread(i) for i in range(ida_dbg.get_thread_qty())]
423
+ results = []
424
+
425
+ for tid in tids:
426
+ try:
427
+ if tid not in available_tids:
428
+ results.append(
429
+ {"tid": tid, "regs": None, "error": f"Thread {tid} not found"}
430
+ )
431
+ continue
432
+ regs = _get_registers_general_for_thread(dbg, tid)
433
+ results.append({"tid": tid, "regs": regs})
434
+ except Exception as e:
435
+ results.append({"tid": tid, "regs": None, "error": str(e)})
436
+
437
+ return results
438
+
439
+
440
+ @ext("dbg")
441
+ @unsafe
442
+ @tool
443
+ @idasync
444
+ def dbg_gpregs() -> ThreadRegisters:
445
+ """Get current thread GP registers"""
446
+ dbg = dbg_ensure_running()
447
+ tid = ida_dbg.get_current_thread()
448
+ return _get_registers_general_for_thread(dbg, tid)
449
+
450
+
451
+ @ext("dbg")
452
+ @unsafe
453
+ @tool
454
+ @idasync
455
+ def dbg_regs_named_remote(
456
+ thread_id: Annotated[int, "Thread ID"],
457
+ register_names: Annotated[
458
+ str, "Comma-separated register names (e.g., 'RAX, RBX, RCX')"
459
+ ],
460
+ ) -> ThreadRegisters:
461
+ """Get specific thread registers"""
462
+ dbg = dbg_ensure_running()
463
+ if thread_id not in [
464
+ ida_dbg.getn_thread(i) for i in range(ida_dbg.get_thread_qty())
465
+ ]:
466
+ raise IDAError(f"Thread with ID {thread_id} not found")
467
+ names = [name.strip() for name in register_names.split(",")]
468
+ return _get_registers_specific_for_thread(dbg, thread_id, names)
469
+
470
+
471
+ @ext("dbg")
472
+ @unsafe
473
+ @tool
474
+ @idasync
475
+ def dbg_regs_named(
476
+ register_names: Annotated[
477
+ str, "Comma-separated register names (e.g., 'RAX, RBX, RCX')"
478
+ ],
479
+ ) -> ThreadRegisters:
480
+ """Get specific current thread registers"""
481
+ dbg = dbg_ensure_running()
482
+ tid = ida_dbg.get_current_thread()
483
+ names = [name.strip() for name in register_names.split(",")]
484
+ return _get_registers_specific_for_thread(dbg, tid, names)
485
+
486
+
487
+ # ============================================================================
488
+ # Call Stack Operations
489
+ # ============================================================================
490
+
491
+
492
+ @ext("dbg")
493
+ @unsafe
494
+ @tool
495
+ @idasync
496
+ def dbg_stacktrace() -> list[dict[str, str]]:
497
+ """Get call stack"""
498
+ callstack = []
499
+ try:
500
+ tid = ida_dbg.get_current_thread()
501
+ trace = ida_idd.call_stack_t()
502
+
503
+ if not ida_dbg.collect_stack_trace(tid, trace):
504
+ return []
505
+ for frame in trace:
506
+ frame_info = {
507
+ "addr": hex(frame.callea),
508
+ }
509
+ try:
510
+ module_info = ida_idd.modinfo_t()
511
+ if ida_dbg.get_module_info(frame.callea, module_info):
512
+ frame_info["module"] = os.path.basename(module_info.name)
513
+ else:
514
+ frame_info["module"] = "<unknown>"
515
+
516
+ name = (
517
+ ida_name.get_nice_colored_name(
518
+ frame.callea,
519
+ ida_name.GNCN_NOCOLOR
520
+ | ida_name.GNCN_NOLABEL
521
+ | ida_name.GNCN_NOSEG
522
+ | ida_name.GNCN_PREFDBG,
523
+ )
524
+ or "<unnamed>"
525
+ )
526
+ frame_info["symbol"] = name
527
+
528
+ except Exception as e:
529
+ frame_info["module"] = "<error>"
530
+ frame_info["symbol"] = str(e)
531
+
532
+ callstack.append(frame_info)
533
+
534
+ except Exception:
535
+ pass
536
+ return callstack
537
+
538
+
539
+ # ============================================================================
540
+ # Debugger Memory Operations
541
+ # ============================================================================
542
+
543
+
544
+ @ext("dbg")
545
+ @unsafe
546
+ @tool
547
+ @idasync
548
+ def dbg_read(regions: list[MemoryRead] | MemoryRead) -> list[dict]:
549
+ """Read debug memory"""
550
+
551
+ regions = normalize_dict_list(regions)
552
+ dbg_ensure_running()
553
+ results = []
554
+
555
+ for region in regions:
556
+ try:
557
+ addr = parse_address(region["addr"])
558
+ size = region["size"]
559
+
560
+ data = idaapi.dbg_read_memory(addr, size)
561
+ if data:
562
+ results.append(
563
+ {
564
+ "addr": region["addr"],
565
+ "size": len(data),
566
+ "data": data.hex(),
567
+ "error": None,
568
+ }
569
+ )
570
+ else:
571
+ results.append(
572
+ {
573
+ "addr": region["addr"],
574
+ "size": 0,
575
+ "data": None,
576
+ "error": "Failed to read memory",
577
+ }
578
+ )
579
+
580
+ except Exception as e:
581
+ results.append(
582
+ {"addr": region.get("addr"), "size": 0, "data": None, "error": str(e)}
583
+ )
584
+
585
+ return results
586
+
587
+
588
+ @ext("dbg")
589
+ @unsafe
590
+ @tool
591
+ @idasync
592
+ def dbg_write(regions: list[MemoryPatch] | MemoryPatch) -> list[dict]:
593
+ """Write debug memory"""
594
+
595
+ regions = normalize_dict_list(regions)
596
+ dbg_ensure_running()
597
+ results = []
598
+
599
+ for region in regions:
600
+ try:
601
+ addr = parse_address(region["addr"])
602
+ data = bytes.fromhex(region["data"])
603
+
604
+ success = idaapi.dbg_write_memory(addr, data)
605
+ results.append(
606
+ {
607
+ "addr": region["addr"],
608
+ "size": len(data) if success else 0,
609
+ "ok": success,
610
+ "error": None if success else "Write failed",
611
+ }
612
+ )
613
+
614
+ except Exception as e:
615
+ results.append({"addr": region.get("addr"), "size": 0, "error": str(e)})
616
+
617
+ return results