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.
- iflow_mcp_developermode_korea_reversecore_mcp-1.0.0.dist-info/METADATA +543 -0
- iflow_mcp_developermode_korea_reversecore_mcp-1.0.0.dist-info/RECORD +79 -0
- iflow_mcp_developermode_korea_reversecore_mcp-1.0.0.dist-info/WHEEL +5 -0
- iflow_mcp_developermode_korea_reversecore_mcp-1.0.0.dist-info/entry_points.txt +2 -0
- iflow_mcp_developermode_korea_reversecore_mcp-1.0.0.dist-info/licenses/LICENSE +21 -0
- iflow_mcp_developermode_korea_reversecore_mcp-1.0.0.dist-info/top_level.txt +1 -0
- reversecore_mcp/__init__.py +9 -0
- reversecore_mcp/core/__init__.py +78 -0
- reversecore_mcp/core/audit.py +101 -0
- reversecore_mcp/core/binary_cache.py +138 -0
- reversecore_mcp/core/command_spec.py +357 -0
- reversecore_mcp/core/config.py +432 -0
- reversecore_mcp/core/container.py +288 -0
- reversecore_mcp/core/decorators.py +152 -0
- reversecore_mcp/core/error_formatting.py +93 -0
- reversecore_mcp/core/error_handling.py +142 -0
- reversecore_mcp/core/evidence.py +229 -0
- reversecore_mcp/core/exceptions.py +296 -0
- reversecore_mcp/core/execution.py +240 -0
- reversecore_mcp/core/ghidra.py +642 -0
- reversecore_mcp/core/ghidra_helper.py +481 -0
- reversecore_mcp/core/ghidra_manager.py +234 -0
- reversecore_mcp/core/json_utils.py +131 -0
- reversecore_mcp/core/loader.py +73 -0
- reversecore_mcp/core/logging_config.py +206 -0
- reversecore_mcp/core/memory.py +721 -0
- reversecore_mcp/core/metrics.py +198 -0
- reversecore_mcp/core/mitre_mapper.py +365 -0
- reversecore_mcp/core/plugin.py +45 -0
- reversecore_mcp/core/r2_helpers.py +404 -0
- reversecore_mcp/core/r2_pool.py +403 -0
- reversecore_mcp/core/report_generator.py +268 -0
- reversecore_mcp/core/resilience.py +252 -0
- reversecore_mcp/core/resource_manager.py +169 -0
- reversecore_mcp/core/result.py +132 -0
- reversecore_mcp/core/security.py +213 -0
- reversecore_mcp/core/validators.py +238 -0
- reversecore_mcp/dashboard/__init__.py +221 -0
- reversecore_mcp/prompts/__init__.py +56 -0
- reversecore_mcp/prompts/common.py +24 -0
- reversecore_mcp/prompts/game.py +280 -0
- reversecore_mcp/prompts/malware.py +1219 -0
- reversecore_mcp/prompts/report.py +150 -0
- reversecore_mcp/prompts/security.py +136 -0
- reversecore_mcp/resources.py +329 -0
- reversecore_mcp/server.py +727 -0
- reversecore_mcp/tools/__init__.py +49 -0
- reversecore_mcp/tools/analysis/__init__.py +74 -0
- reversecore_mcp/tools/analysis/capa_tools.py +215 -0
- reversecore_mcp/tools/analysis/die_tools.py +180 -0
- reversecore_mcp/tools/analysis/diff_tools.py +643 -0
- reversecore_mcp/tools/analysis/lief_tools.py +272 -0
- reversecore_mcp/tools/analysis/signature_tools.py +591 -0
- reversecore_mcp/tools/analysis/static_analysis.py +479 -0
- reversecore_mcp/tools/common/__init__.py +58 -0
- reversecore_mcp/tools/common/file_operations.py +352 -0
- reversecore_mcp/tools/common/memory_tools.py +516 -0
- reversecore_mcp/tools/common/patch_explainer.py +230 -0
- reversecore_mcp/tools/common/server_tools.py +115 -0
- reversecore_mcp/tools/ghidra/__init__.py +19 -0
- reversecore_mcp/tools/ghidra/decompilation.py +975 -0
- reversecore_mcp/tools/ghidra/ghidra_tools.py +1052 -0
- reversecore_mcp/tools/malware/__init__.py +61 -0
- reversecore_mcp/tools/malware/adaptive_vaccine.py +579 -0
- reversecore_mcp/tools/malware/dormant_detector.py +756 -0
- reversecore_mcp/tools/malware/ioc_tools.py +228 -0
- reversecore_mcp/tools/malware/vulnerability_hunter.py +519 -0
- reversecore_mcp/tools/malware/yara_tools.py +214 -0
- reversecore_mcp/tools/patch_explainer.py +19 -0
- reversecore_mcp/tools/radare2/__init__.py +13 -0
- reversecore_mcp/tools/radare2/r2_analysis.py +972 -0
- reversecore_mcp/tools/radare2/r2_session.py +376 -0
- reversecore_mcp/tools/radare2/radare2_mcp_tools.py +1183 -0
- reversecore_mcp/tools/report/__init__.py +4 -0
- reversecore_mcp/tools/report/email.py +82 -0
- reversecore_mcp/tools/report/report_mcp_tools.py +344 -0
- reversecore_mcp/tools/report/report_tools.py +1076 -0
- reversecore_mcp/tools/report/session.py +194 -0
- 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
|