iflow-mcp_developermode-korea_reversecore-mcp 1.0.0__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 (79) hide show
  1. iflow_mcp_developermode_korea_reversecore_mcp-1.0.0.dist-info/METADATA +543 -0
  2. iflow_mcp_developermode_korea_reversecore_mcp-1.0.0.dist-info/RECORD +79 -0
  3. iflow_mcp_developermode_korea_reversecore_mcp-1.0.0.dist-info/WHEEL +5 -0
  4. iflow_mcp_developermode_korea_reversecore_mcp-1.0.0.dist-info/entry_points.txt +2 -0
  5. iflow_mcp_developermode_korea_reversecore_mcp-1.0.0.dist-info/licenses/LICENSE +21 -0
  6. iflow_mcp_developermode_korea_reversecore_mcp-1.0.0.dist-info/top_level.txt +1 -0
  7. reversecore_mcp/__init__.py +9 -0
  8. reversecore_mcp/core/__init__.py +78 -0
  9. reversecore_mcp/core/audit.py +101 -0
  10. reversecore_mcp/core/binary_cache.py +138 -0
  11. reversecore_mcp/core/command_spec.py +357 -0
  12. reversecore_mcp/core/config.py +432 -0
  13. reversecore_mcp/core/container.py +288 -0
  14. reversecore_mcp/core/decorators.py +152 -0
  15. reversecore_mcp/core/error_formatting.py +93 -0
  16. reversecore_mcp/core/error_handling.py +142 -0
  17. reversecore_mcp/core/evidence.py +229 -0
  18. reversecore_mcp/core/exceptions.py +296 -0
  19. reversecore_mcp/core/execution.py +240 -0
  20. reversecore_mcp/core/ghidra.py +642 -0
  21. reversecore_mcp/core/ghidra_helper.py +481 -0
  22. reversecore_mcp/core/ghidra_manager.py +234 -0
  23. reversecore_mcp/core/json_utils.py +131 -0
  24. reversecore_mcp/core/loader.py +73 -0
  25. reversecore_mcp/core/logging_config.py +206 -0
  26. reversecore_mcp/core/memory.py +721 -0
  27. reversecore_mcp/core/metrics.py +198 -0
  28. reversecore_mcp/core/mitre_mapper.py +365 -0
  29. reversecore_mcp/core/plugin.py +45 -0
  30. reversecore_mcp/core/r2_helpers.py +404 -0
  31. reversecore_mcp/core/r2_pool.py +403 -0
  32. reversecore_mcp/core/report_generator.py +268 -0
  33. reversecore_mcp/core/resilience.py +252 -0
  34. reversecore_mcp/core/resource_manager.py +169 -0
  35. reversecore_mcp/core/result.py +132 -0
  36. reversecore_mcp/core/security.py +213 -0
  37. reversecore_mcp/core/validators.py +238 -0
  38. reversecore_mcp/dashboard/__init__.py +221 -0
  39. reversecore_mcp/prompts/__init__.py +56 -0
  40. reversecore_mcp/prompts/common.py +24 -0
  41. reversecore_mcp/prompts/game.py +280 -0
  42. reversecore_mcp/prompts/malware.py +1219 -0
  43. reversecore_mcp/prompts/report.py +150 -0
  44. reversecore_mcp/prompts/security.py +136 -0
  45. reversecore_mcp/resources.py +329 -0
  46. reversecore_mcp/server.py +727 -0
  47. reversecore_mcp/tools/__init__.py +49 -0
  48. reversecore_mcp/tools/analysis/__init__.py +74 -0
  49. reversecore_mcp/tools/analysis/capa_tools.py +215 -0
  50. reversecore_mcp/tools/analysis/die_tools.py +180 -0
  51. reversecore_mcp/tools/analysis/diff_tools.py +643 -0
  52. reversecore_mcp/tools/analysis/lief_tools.py +272 -0
  53. reversecore_mcp/tools/analysis/signature_tools.py +591 -0
  54. reversecore_mcp/tools/analysis/static_analysis.py +479 -0
  55. reversecore_mcp/tools/common/__init__.py +58 -0
  56. reversecore_mcp/tools/common/file_operations.py +352 -0
  57. reversecore_mcp/tools/common/memory_tools.py +516 -0
  58. reversecore_mcp/tools/common/patch_explainer.py +230 -0
  59. reversecore_mcp/tools/common/server_tools.py +115 -0
  60. reversecore_mcp/tools/ghidra/__init__.py +19 -0
  61. reversecore_mcp/tools/ghidra/decompilation.py +975 -0
  62. reversecore_mcp/tools/ghidra/ghidra_tools.py +1052 -0
  63. reversecore_mcp/tools/malware/__init__.py +61 -0
  64. reversecore_mcp/tools/malware/adaptive_vaccine.py +579 -0
  65. reversecore_mcp/tools/malware/dormant_detector.py +756 -0
  66. reversecore_mcp/tools/malware/ioc_tools.py +228 -0
  67. reversecore_mcp/tools/malware/vulnerability_hunter.py +519 -0
  68. reversecore_mcp/tools/malware/yara_tools.py +214 -0
  69. reversecore_mcp/tools/patch_explainer.py +19 -0
  70. reversecore_mcp/tools/radare2/__init__.py +13 -0
  71. reversecore_mcp/tools/radare2/r2_analysis.py +972 -0
  72. reversecore_mcp/tools/radare2/r2_session.py +376 -0
  73. reversecore_mcp/tools/radare2/radare2_mcp_tools.py +1183 -0
  74. reversecore_mcp/tools/report/__init__.py +4 -0
  75. reversecore_mcp/tools/report/email.py +82 -0
  76. reversecore_mcp/tools/report/report_mcp_tools.py +344 -0
  77. reversecore_mcp/tools/report/report_tools.py +1076 -0
  78. reversecore_mcp/tools/report/session.py +194 -0
  79. reversecore_mcp/tools/report_tools.py +11 -0
@@ -0,0 +1,481 @@
1
+ """
2
+ Ghidra decompiler integration helper.
3
+
4
+ This module provides utilities for decompiling binaries using Ghidra's
5
+ DecompInterface API through PyGhidra.
6
+ """
7
+
8
+ import os
9
+ import re
10
+ import shutil
11
+ import subprocess
12
+ import tempfile
13
+ from pathlib import Path
14
+ from typing import TYPE_CHECKING, Any, Optional
15
+
16
+ from reversecore_mcp.core.exceptions import ValidationError
17
+ from reversecore_mcp.core.logging_config import get_logger
18
+
19
+ if TYPE_CHECKING:
20
+ from ghidra.program.flatapi import FlatProgramAPI
21
+ from ghidra.program.model.listing import Function
22
+
23
+ logger = get_logger(__name__)
24
+ from reversecore_mcp.core.ghidra_manager import get_ghidra_manager
25
+
26
+ # OPTIMIZATION: Pre-compile pattern for hex prefix removal (case insensitive)
27
+ _HEX_PREFIX_PATTERN = re.compile(r"^0[xX]")
28
+
29
+
30
+ def _extract_structure_fields(data_type) -> list:
31
+ """
32
+ Extract fields from a Ghidra data type structure.
33
+
34
+ OPTIMIZATION: Helper function to reduce code duplication and improve performance
35
+ by avoiding repeated attribute checks and type conversions.
36
+
37
+ Args:
38
+ data_type: Ghidra DataType object (type hint omitted for compatibility)
39
+
40
+ Returns:
41
+ List of field dictionaries with offset, type, name, and size
42
+
43
+ Note: Type hint for data_type is intentionally omitted to avoid circular imports
44
+ and maintain compatibility with optional Ghidra dependency.
45
+ """
46
+ fields = []
47
+
48
+ if not hasattr(data_type, "getNumComponents"):
49
+ return fields
50
+
51
+ num_components = data_type.getNumComponents()
52
+
53
+ for j in range(num_components):
54
+ component = data_type.getComponent(j)
55
+ field_name = component.getFieldName()
56
+ field_type = component.getDataType().getName()
57
+ field_offset = component.getOffset()
58
+ field_size = component.getLength()
59
+
60
+ fields.append(
61
+ {
62
+ "offset": f"0x{field_offset:x}",
63
+ "type": field_type,
64
+ "name": field_name if field_name else f"field_{field_offset:x}",
65
+ "size": field_size,
66
+ }
67
+ )
68
+
69
+ return fields
70
+
71
+
72
+ def ensure_ghidra_available() -> bool:
73
+ """
74
+ Check if Ghidra and PyGhidra are available.
75
+
76
+ Returns:
77
+ True if Ghidra is available, False otherwise
78
+ """
79
+ try:
80
+ import pyghidra # noqa: F401
81
+
82
+ return True
83
+ except ImportError:
84
+ return False
85
+
86
+
87
+ def _configure_ghidra_environment():
88
+ """
89
+ Attempt to configure Ghidra environment variables to prevent launch errors.
90
+
91
+ This tries to locate JAVA_HOME if not set, which is required for PyGhidra
92
+ to launch Ghidra successfully without permission errors.
93
+ """
94
+ # 1. Try to set JAVA_HOME if missing
95
+ if not os.environ.get("JAVA_HOME"):
96
+ java_path = shutil.which("java")
97
+ if java_path:
98
+ try:
99
+ # Resolve symlinks to find real path
100
+ real_path = Path(java_path).resolve()
101
+ # Usually java is in bin/java, so JDK home is parent of bin
102
+ if real_path.name == "java" and real_path.parent.name == "bin":
103
+ java_home = real_path.parent.parent
104
+ os.environ["JAVA_HOME"] = str(java_home)
105
+ logger.info(f"Set JAVA_HOME to {java_home}")
106
+ except Exception as e:
107
+ logger.warning(f"Failed to resolve JAVA_HOME: {e}")
108
+
109
+
110
+ def decompile_function_with_ghidra(
111
+ file_path: Path, function_address: str, timeout: int = 300
112
+ ) -> tuple[str, dict[str, Any]]:
113
+ """
114
+ Decompile a function using Ghidra's decompiler.
115
+
116
+ Args:
117
+ file_path: Path to the binary file
118
+ function_address: Function address (hex string or symbol name)
119
+ timeout: Maximum execution time in seconds
120
+
121
+ Returns:
122
+ Tuple of (decompiled_code, metadata)
123
+
124
+ Raises:
125
+ ValidationError: If decompilation fails
126
+ ImportError: If PyGhidra is not available
127
+ """
128
+ try:
129
+ import pyghidra
130
+
131
+ # Ensure Ghidra environment is set up
132
+ _configure_ghidra_environment()
133
+
134
+ # Use GhidraManager for efficient project reuse
135
+ manager = get_ghidra_manager()
136
+
137
+ # Get or create context for this binary
138
+ # This will reuse existing project if available, avoiding re-import
139
+ with manager.context(file_path) as flat_api:
140
+ # Import Ghidra classes here, after JVM is started
141
+ from ghidra.app.decompiler import DecompileResults, DecompInterface
142
+
143
+ program = flat_api.getCurrentProgram()
144
+
145
+ # Resolve function address
146
+ function = _resolve_function(flat_api, function_address)
147
+
148
+ if function is None:
149
+ raise ValidationError(
150
+ f"Could not find function at address: {function_address}",
151
+ details={"address": function_address},
152
+ )
153
+
154
+ # Initialize decompiler
155
+ decompiler = DecompInterface()
156
+ decompiler.openProgram(program)
157
+
158
+ try:
159
+ # Decompile the function
160
+ logger.info(f"Decompiling function: {function.getName()}")
161
+
162
+ results: DecompileResults = decompiler.decompileFunction(
163
+ function, timeout, None
164
+ ) # monitor
165
+
166
+ # Check for errors
167
+ if not results.decompileCompleted():
168
+ error_msg = results.getErrorMessage()
169
+ raise ValidationError(
170
+ f"Decompilation failed: {error_msg}",
171
+ details={
172
+ "function": function.getName(),
173
+ "address": function_address,
174
+ },
175
+ )
176
+
177
+ # Extract decompiled C code
178
+ decompiled_function = results.getDecompiledFunction()
179
+ c_code = decompiled_function.getC()
180
+
181
+ # Extract metadata
182
+ high_function = results.getHighFunction()
183
+ metadata = {
184
+ "function_name": function.getName(),
185
+ "entry_point": str(function.getEntryPoint()),
186
+ "parameter_count": (
187
+ high_function.getFunctionPrototype().getNumParams()
188
+ if high_function
189
+ else 0
190
+ ),
191
+ "local_symbol_count": (
192
+ high_function.getLocalSymbolMap().getNumSymbols()
193
+ if high_function
194
+ else 0
195
+ ),
196
+ "signature": function.getSignature().getPrototypeString(),
197
+ "body_size": function.getBody().getNumAddresses(),
198
+ "decompiler": "ghidra",
199
+ }
200
+
201
+ logger.info(f"Successfully decompiled {function.getName()}")
202
+
203
+ return c_code, metadata
204
+
205
+ finally:
206
+ # Always dispose of decompiler resources
207
+ decompiler.dispose()
208
+
209
+ except ImportError as e:
210
+ raise ImportError("PyGhidra is not installed. Install with: pip install pyghidra") from e
211
+ except subprocess.CalledProcessError as e:
212
+ if "LaunchSupport" in str(e.cmd):
213
+ logger.error("Ghidra LaunchSupport failed. Check JAVA_HOME and permissions.")
214
+ raise ValidationError(
215
+ "Ghidra failed to launch. Please ensure JAVA_HOME is set correctly and the user has write permissions to Ghidra installation.",
216
+ details={"error": str(e), "command": str(e.cmd)},
217
+ )
218
+ raise
219
+ except Exception as e:
220
+ logger.error(f"Ghidra decompilation failed: {e}", exc_info=True)
221
+ raise
222
+
223
+
224
+ def _resolve_function(flat_api: "FlatProgramAPI", address_str: str) -> Optional["Function"]:
225
+ """
226
+ Resolve a function from an address string or symbol name.
227
+
228
+ Args:
229
+ flat_api: Ghidra FlatProgramAPI instance
230
+ address_str: Address as hex string (e.g., "0x401000") or symbol name (e.g., "main")
231
+
232
+ Returns:
233
+ Function object or None if not found
234
+ """
235
+ program = flat_api.getCurrentProgram()
236
+ function_manager = program.getFunctionManager()
237
+
238
+ # Try as symbol name first
239
+ symbol_table = program.getSymbolTable()
240
+ symbols = symbol_table.getSymbols(address_str)
241
+
242
+ if symbols.hasNext():
243
+ symbol = symbols.next()
244
+ address = symbol.getAddress()
245
+ return function_manager.getFunctionAt(address)
246
+
247
+ # Try as hex address (remove prefix once for both attempts)
248
+ # OPTIMIZATION: Use pre-compiled regex pattern instead of chained replace
249
+ addr_str = _HEX_PREFIX_PATTERN.sub("", address_str)
250
+
251
+ try:
252
+ address = flat_api.toAddr(int(addr_str, 16))
253
+ func = function_manager.getFunctionAt(address)
254
+ if func is not None:
255
+ return func
256
+ except (ValueError, Exception):
257
+ pass
258
+
259
+ # Try to find function containing this address (reuse cleaned addr_str)
260
+ try:
261
+ address = flat_api.toAddr(int(addr_str, 16))
262
+ return function_manager.getFunctionContaining(address)
263
+ except (ValueError, Exception):
264
+ pass
265
+
266
+ return None
267
+
268
+
269
+ def get_ghidra_version() -> str | None:
270
+ """
271
+ Get the installed Ghidra version.
272
+
273
+ Returns:
274
+ Version string or None if not available
275
+ """
276
+ try:
277
+ import pyghidra
278
+
279
+ _configure_ghidra_environment()
280
+ pyghidra.start()
281
+
282
+ from ghidra import framework
283
+
284
+ version = framework.Application.getApplicationVersion()
285
+ return str(version)
286
+ except Exception:
287
+ return None
288
+
289
+
290
+ def recover_structures_with_ghidra(
291
+ file_path: Path, function_address: str, timeout: int = 600
292
+ ) -> tuple[dict[str, Any], dict[str, Any]]:
293
+ """
294
+ Recover structure definitions from a function using Ghidra's data type analysis.
295
+
296
+ This function analyzes a binary function to identify structure usage patterns
297
+ and recover structure definitions with field names and types.
298
+
299
+ Args:
300
+ file_path: Path to the binary file
301
+ function_address: Function address (hex string or symbol name)
302
+ timeout: Maximum execution time in seconds
303
+
304
+ Returns:
305
+ Tuple of (structures_dict, metadata_dict)
306
+
307
+ Raises:
308
+ ValidationError: If structure recovery fails
309
+ ImportError: If PyGhidra is not available
310
+ """
311
+ try:
312
+ import pyghidra
313
+
314
+ _configure_ghidra_environment()
315
+
316
+ # Use GhidraManager for efficient project reuse
317
+ manager = get_ghidra_manager()
318
+
319
+ # Get or create context for this binary
320
+ with manager.context(file_path) as flat_api:
321
+ # Import Ghidra classes here
322
+ from ghidra.app.decompiler import DecompileResults, DecompInterface
323
+ from ghidra.program.model.pcode import HighFunction, HighVariable
324
+
325
+ program = flat_api.getCurrentProgram()
326
+
327
+ # Resolve function address
328
+ function = _resolve_function(flat_api, function_address)
329
+
330
+ if function is None:
331
+ raise ValidationError(
332
+ f"Could not find function at address: {function_address}",
333
+ details={"address": function_address},
334
+ )
335
+
336
+ # Initialize decompiler for high-level analysis
337
+ decompiler = DecompInterface()
338
+ decompiler.openProgram(program)
339
+
340
+ try:
341
+ # Decompile the function to get high-level representation
342
+ logger.info(f"Analyzing structures in function: {function.getName()}")
343
+
344
+ results: DecompileResults = decompiler.decompileFunction(
345
+ function, timeout, None
346
+ )
347
+
348
+ if not results.decompileCompleted():
349
+ error_msg = results.getErrorMessage()
350
+ raise ValidationError(
351
+ f"Structure analysis failed: {error_msg}",
352
+ details={
353
+ "function": function.getName(),
354
+ "address": function_address,
355
+ },
356
+ )
357
+
358
+ # Extract structure information from high function
359
+ high_function: HighFunction = results.getHighFunction()
360
+
361
+ if high_function is None:
362
+ raise ValidationError(
363
+ "Could not get high-level function representation",
364
+ details={"function": function.getName()},
365
+ )
366
+
367
+ # Collect all data types used in the function
368
+ structures_found = {}
369
+
370
+ # Analyze local variables for structure types
371
+ local_symbols = high_function.getLocalSymbolMap()
372
+
373
+ for i in range(local_symbols.getNumSymbols()):
374
+ symbol = local_symbols.getSymbol(i)
375
+ high_var: HighVariable = symbol.getHighVariable()
376
+
377
+ if high_var is not None:
378
+ data_type = high_var.getDataType()
379
+
380
+ # Check if this is a structure or pointer to structure
381
+ if data_type is not None:
382
+ type_name = data_type.getName()
383
+
384
+ # Look for structure types (including pointers to structures)
385
+ if "struct" in type_name.lower() or data_type.getLength() > 8:
386
+ # Try to get the underlying structure
387
+ actual_type = data_type
388
+
389
+ # If it's a pointer, get the pointed-to type
390
+ if hasattr(data_type, "getDataType"):
391
+ actual_type = data_type.getDataType()
392
+
393
+ struct_name = actual_type.getName()
394
+
395
+ if struct_name not in structures_found:
396
+ # OPTIMIZATION: Use helper function to extract fields
397
+ fields = _extract_structure_fields(actual_type)
398
+
399
+ structures_found[struct_name] = {
400
+ "name": struct_name,
401
+ "size": actual_type.getLength(),
402
+ "fields": fields,
403
+ }
404
+
405
+ # Also analyze function parameters for structure types
406
+ for param in function.getParameters():
407
+ param_type = param.getDataType()
408
+
409
+ if param_type is not None:
410
+ type_name = param_type.getName()
411
+
412
+ if "struct" in type_name.lower() and type_name not in structures_found:
413
+ # OPTIMIZATION: Use helper function to extract fields
414
+ fields = _extract_structure_fields(param_type)
415
+
416
+ structures_found[type_name] = {
417
+ "name": type_name,
418
+ "size": param_type.getLength(),
419
+ "fields": fields,
420
+ }
421
+
422
+ # Generate C structure definitions
423
+ # OPTIMIZATION: Build field strings outside of dict comprehension
424
+ c_definitions = []
425
+ for struct_name, struct_data in structures_found.items():
426
+ if struct_data["fields"]:
427
+ # Pre-build field strings for better performance
428
+ field_strs = [
429
+ f"{field['type']} {field['name']}; // offset {field['offset']}, size {field['size']}"
430
+ for field in struct_data["fields"]
431
+ ]
432
+ fields_str = "\n ".join(field_strs)
433
+ c_def = f"struct {struct_name} {{\n {fields_str}\n}};"
434
+ else:
435
+ c_def = f"struct {struct_name} {{ /* size: {struct_data['size']} bytes */ }};"
436
+
437
+ c_definitions.append(c_def)
438
+
439
+ # Prepare result
440
+ result = {
441
+ "structures": list(structures_found.values()),
442
+ "c_definitions": (
443
+ "\n\n".join(c_definitions)
444
+ if c_definitions
445
+ else "// No structures found"
446
+ ),
447
+ "count": len(structures_found),
448
+ }
449
+
450
+ # Metadata
451
+ metadata = {
452
+ "function_name": function.getName(),
453
+ "entry_point": str(function.getEntryPoint()),
454
+ "structure_count": len(structures_found),
455
+ "analyzed_variables": local_symbols.getNumSymbols(),
456
+ "decompiler": "ghidra",
457
+ }
458
+
459
+ logger.info(
460
+ f"Successfully recovered {len(structures_found)} structure(s) from {function.getName()}"
461
+ )
462
+
463
+ return result, metadata
464
+
465
+ finally:
466
+ # Always dispose of decompiler resources
467
+ decompiler.dispose()
468
+
469
+ except ImportError as e:
470
+ raise ImportError("PyGhidra is not installed. Install with: pip install pyghidra") from e
471
+ except subprocess.CalledProcessError as e:
472
+ if "LaunchSupport" in str(e.cmd):
473
+ logger.error("Ghidra LaunchSupport failed. Check JAVA_HOME and permissions.")
474
+ raise ValidationError(
475
+ "Ghidra failed to launch. Please ensure JAVA_HOME is set correctly and the user has write permissions to Ghidra installation.",
476
+ details={"error": str(e), "command": str(e.cmd)},
477
+ )
478
+ raise
479
+ except Exception as e:
480
+ logger.error(f"Ghidra structure recovery failed: {e}", exc_info=True)
481
+ raise