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,591 @@
|
|
|
1
|
+
"""Signature generation tools for creating YARA rules and binary signatures."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from functools import lru_cache
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from reversecore_mcp.core.config import get_config
|
|
8
|
+
from reversecore_mcp.core.decorators import log_execution
|
|
9
|
+
from reversecore_mcp.core.error_handling import handle_tool_errors
|
|
10
|
+
from reversecore_mcp.core.execution import execute_subprocess_async
|
|
11
|
+
from reversecore_mcp.core.logging_config import get_logger
|
|
12
|
+
from reversecore_mcp.core.metrics import track_metrics
|
|
13
|
+
from reversecore_mcp.core.r2_helpers import (
|
|
14
|
+
build_r2_cmd as _build_r2_cmd,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
# Import shared R2 helper functions from core (avoids circular dependencies)
|
|
18
|
+
from reversecore_mcp.core.r2_helpers import (
|
|
19
|
+
execute_r2_command as _execute_r2_command,
|
|
20
|
+
)
|
|
21
|
+
from reversecore_mcp.core.r2_helpers import (
|
|
22
|
+
parse_json_output as _parse_json_output,
|
|
23
|
+
)
|
|
24
|
+
from reversecore_mcp.core.result import ToolResult, failure, success
|
|
25
|
+
from reversecore_mcp.core.security import validate_file_path
|
|
26
|
+
from reversecore_mcp.core.validators import validate_tool_parameters
|
|
27
|
+
|
|
28
|
+
# Load default timeout from configuration
|
|
29
|
+
DEFAULT_TIMEOUT = get_config().default_tool_timeout
|
|
30
|
+
|
|
31
|
+
logger = get_logger(__name__)
|
|
32
|
+
|
|
33
|
+
# OPTIMIZATION: Character translation table for filename sanitization
|
|
34
|
+
_FILENAME_SANITIZE_TRANS = str.maketrans({"-": "_", ".": "_"})
|
|
35
|
+
|
|
36
|
+
# OPTIMIZATION: Pre-compile regex patterns used in hot paths
|
|
37
|
+
# Note: _HEX_PATTERN is used for both addresses and byte sequences
|
|
38
|
+
_HEX_PATTERN = re.compile(r"^[0-9a-fA-F]+$")
|
|
39
|
+
_ALL_FF_PATTERN = re.compile(r"^(ff)+$", re.IGNORECASE)
|
|
40
|
+
_ALL_00_PATTERN = re.compile(r"^(00)+$")
|
|
41
|
+
_RULE_NAME_PATTERN = re.compile(r"^[a-zA-Z][a-zA-Z0-9_]*$")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _validate_address_or_fail(address: str, param_name: str = "address"):
|
|
45
|
+
"""
|
|
46
|
+
Validate address format and return failure ToolResult if invalid.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
address: Address string to validate
|
|
50
|
+
param_name: Parameter name for error messages
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
None if validation passes, or ToolResult failure if invalid
|
|
54
|
+
"""
|
|
55
|
+
from reversecore_mcp.core.exceptions import ValidationError
|
|
56
|
+
from reversecore_mcp.core.validators import validate_address_format
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
validate_address_format(address, param_name)
|
|
60
|
+
return None # Validation passed
|
|
61
|
+
except ValidationError as e:
|
|
62
|
+
return failure("VALIDATION_ERROR", str(e))
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _format_hex_bytes(hex_string: str) -> str:
|
|
66
|
+
"""
|
|
67
|
+
Efficiently format hex string as space-separated byte pairs.
|
|
68
|
+
|
|
69
|
+
Optimized to avoid intermediate list creation by using a generator.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
hex_string: Hex string without spaces (e.g., "4883ec20")
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
Space-separated hex bytes (e.g., "48 83 ec 20")
|
|
76
|
+
"""
|
|
77
|
+
# Use generator expression to avoid creating intermediate list
|
|
78
|
+
return " ".join(hex_string[i : i + 2] for i in range(0, len(hex_string), 2))
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@lru_cache(maxsize=128)
|
|
82
|
+
def _sanitize_filename_for_rule(file_path: str) -> str:
|
|
83
|
+
"""
|
|
84
|
+
Extract and sanitize filename for use in YARA rule names.
|
|
85
|
+
|
|
86
|
+
Cached to avoid repeated Path operations and string replacements.
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
file_path: Path to the file
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Sanitized filename with special characters replaced
|
|
93
|
+
"""
|
|
94
|
+
# OPTIMIZATION: Use str.translate() instead of chained replace()
|
|
95
|
+
return Path(file_path).stem.translate(_FILENAME_SANITIZE_TRANS)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@log_execution(tool_name="generate_signature")
|
|
99
|
+
@track_metrics("generate_signature")
|
|
100
|
+
@handle_tool_errors
|
|
101
|
+
async def generate_signature(
|
|
102
|
+
file_path: str,
|
|
103
|
+
address: str,
|
|
104
|
+
length: int = 32,
|
|
105
|
+
timeout: int = DEFAULT_TIMEOUT,
|
|
106
|
+
) -> ToolResult:
|
|
107
|
+
"""
|
|
108
|
+
Generate a YARA signature from opcode bytes at a specific address.
|
|
109
|
+
|
|
110
|
+
This tool extracts opcode bytes from a function or code section and formats
|
|
111
|
+
them as a YARA rule, enabling automated malware detection. It attempts to
|
|
112
|
+
mask variable values (addresses, offsets) to create more flexible signatures.
|
|
113
|
+
|
|
114
|
+
**Use Cases:**
|
|
115
|
+
- Generate detection signatures for malware samples
|
|
116
|
+
- Create YARA rules for threat hunting
|
|
117
|
+
- Automate IOC (Indicator of Compromise) generation
|
|
118
|
+
- Build malware family signatures
|
|
119
|
+
|
|
120
|
+
**Workflow:**
|
|
121
|
+
1. Extract opcode bytes from specified address
|
|
122
|
+
2. Apply basic masking for variable values (optional)
|
|
123
|
+
3. Format as YARA rule template
|
|
124
|
+
4. Return ready-to-use YARA rule
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
file_path: Path to the binary file (must be in workspace)
|
|
128
|
+
address: Start address for signature extraction (e.g., 'main', '0x401000')
|
|
129
|
+
length: Number of bytes to extract (default 32, recommended 16-64)
|
|
130
|
+
timeout: Execution timeout in seconds (default 300)
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
ToolResult with YARA rule string
|
|
134
|
+
|
|
135
|
+
Example:
|
|
136
|
+
generate_signature("/app/workspace/malware.exe", "0x401000", 48)
|
|
137
|
+
# Returns a YARA rule with extracted byte pattern
|
|
138
|
+
"""
|
|
139
|
+
# 1. Validate parameters
|
|
140
|
+
validated_path = validate_file_path(file_path)
|
|
141
|
+
|
|
142
|
+
if not isinstance(length, int) or length < 1 or length > 1024:
|
|
143
|
+
return failure(
|
|
144
|
+
"VALIDATION_ERROR",
|
|
145
|
+
"Length must be between 1 and 1024 bytes",
|
|
146
|
+
hint="Typical signature lengths are 16-64 bytes for good detection accuracy",
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# 2. Security check for address
|
|
150
|
+
validation_error = _validate_address_or_fail(address, "address")
|
|
151
|
+
if validation_error:
|
|
152
|
+
return validation_error
|
|
153
|
+
|
|
154
|
+
# 3. Extract hex bytes using radare2's p8 command
|
|
155
|
+
r2_cmds = [
|
|
156
|
+
f"s {address}", # Seek to address
|
|
157
|
+
f"p8 {length}", # Print hex bytes
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
# Adaptive analysis: if address is hex, we don't need full analysis
|
|
161
|
+
analysis_level = ""
|
|
162
|
+
# OPTIMIZATION: Use pre-compiled regex pattern (faster)
|
|
163
|
+
if address.startswith("0x") or _HEX_PATTERN.match(address):
|
|
164
|
+
analysis_level = "-n"
|
|
165
|
+
|
|
166
|
+
# Extract hex bytes using helper
|
|
167
|
+
# Note: analysis_level may be "" (empty) which means default r2 behavior (parse headers/symbols)
|
|
168
|
+
output, bytes_read = await _execute_r2_command(
|
|
169
|
+
validated_path,
|
|
170
|
+
r2_cmds,
|
|
171
|
+
analysis_level=analysis_level or "aaa",
|
|
172
|
+
max_output_size=1_000_000,
|
|
173
|
+
base_timeout=timeout,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# 4. Validate output
|
|
177
|
+
hex_bytes = output.strip()
|
|
178
|
+
# OPTIMIZATION: Use pre-compiled regex pattern (faster)
|
|
179
|
+
if not hex_bytes or not _HEX_PATTERN.match(hex_bytes):
|
|
180
|
+
return failure(
|
|
181
|
+
"SIGNATURE_ERROR",
|
|
182
|
+
f"Failed to extract valid hex bytes from address: {address}",
|
|
183
|
+
hint="Verify the address is valid and contains executable code",
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
# Check for all 0xFF or 0x00 (likely unmapped memory)
|
|
187
|
+
# OPTIMIZATION: Use pre-compiled regex patterns (faster)
|
|
188
|
+
if _ALL_FF_PATTERN.match(hex_bytes) or _ALL_00_PATTERN.match(hex_bytes):
|
|
189
|
+
# If we used -n, try again without it to force mapping
|
|
190
|
+
if analysis_level == "-n":
|
|
191
|
+
from reversecore_mcp.core.r2_helpers import calculate_dynamic_timeout
|
|
192
|
+
|
|
193
|
+
effective_timeout = calculate_dynamic_timeout(str(validated_path))
|
|
194
|
+
cmd = _build_r2_cmd(str(validated_path), r2_cmds, "aaa")
|
|
195
|
+
output, _ = await execute_subprocess_async(
|
|
196
|
+
cmd,
|
|
197
|
+
max_output_size=1_000_000,
|
|
198
|
+
timeout=effective_timeout,
|
|
199
|
+
)
|
|
200
|
+
hex_bytes = output.strip()
|
|
201
|
+
|
|
202
|
+
# Re-check
|
|
203
|
+
# OPTIMIZATION: Use pre-compiled regex patterns (faster)
|
|
204
|
+
if _ALL_FF_PATTERN.match(hex_bytes) or _ALL_00_PATTERN.match(hex_bytes):
|
|
205
|
+
return failure(
|
|
206
|
+
"SIGNATURE_ERROR",
|
|
207
|
+
f"Extracted bytes are all 0xFF or 0x00 at {address}. The memory might be unmapped or empty.",
|
|
208
|
+
hint="Try a different address or ensure the binary is loaded correctly.",
|
|
209
|
+
)
|
|
210
|
+
else:
|
|
211
|
+
return failure(
|
|
212
|
+
"SIGNATURE_ERROR",
|
|
213
|
+
f"Extracted bytes are all 0xFF or 0x00 at {address}. The memory might be unmapped or empty.",
|
|
214
|
+
hint="Try a different address or ensure the binary is loaded correctly.",
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
# 5. Format as YARA hex string (space-separated pairs)
|
|
218
|
+
# Convert: "4883ec20" -> "48 83 ec 20"
|
|
219
|
+
# OPTIMIZED: Use generator expression to avoid intermediate list
|
|
220
|
+
formatted_bytes = _format_hex_bytes(hex_bytes)
|
|
221
|
+
|
|
222
|
+
# 6. Generate YARA rule template
|
|
223
|
+
# Extract filename for rule name using cached helper
|
|
224
|
+
file_name = _sanitize_filename_for_rule(file_path)
|
|
225
|
+
rule_name = f"suspicious_{file_name}_{address.replace('0x', 'x')}"
|
|
226
|
+
|
|
227
|
+
yara_rule = f"""rule {rule_name} {{
|
|
228
|
+
meta:
|
|
229
|
+
description = "Auto-generated signature for {file_name}"
|
|
230
|
+
address = "{address}"
|
|
231
|
+
length = {length}
|
|
232
|
+
author = "Reversecore_MCP"
|
|
233
|
+
date = "auto-generated"
|
|
234
|
+
|
|
235
|
+
strings:
|
|
236
|
+
$code = {{ {formatted_bytes} }}
|
|
237
|
+
|
|
238
|
+
condition:
|
|
239
|
+
$code
|
|
240
|
+
}}"""
|
|
241
|
+
|
|
242
|
+
# 7. Return YARA rule
|
|
243
|
+
return success(
|
|
244
|
+
yara_rule,
|
|
245
|
+
bytes_read=bytes_read,
|
|
246
|
+
address=address,
|
|
247
|
+
length=length,
|
|
248
|
+
format="yara",
|
|
249
|
+
hex_bytes=formatted_bytes,
|
|
250
|
+
description=f"YARA signature generated from {length} bytes at {address}",
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
@log_execution(tool_name="generate_yara_rule")
|
|
255
|
+
@track_metrics("generate_yara_rule")
|
|
256
|
+
@handle_tool_errors
|
|
257
|
+
async def generate_yara_rule(
|
|
258
|
+
file_path: str,
|
|
259
|
+
function_address: str,
|
|
260
|
+
rule_name: str = "auto_generated_rule",
|
|
261
|
+
byte_length: int = 64,
|
|
262
|
+
timeout: int = 300,
|
|
263
|
+
) -> ToolResult:
|
|
264
|
+
"""
|
|
265
|
+
Generate a YARA rule from function bytes.
|
|
266
|
+
|
|
267
|
+
This tool extracts bytes from a function and generates a ready-to-use
|
|
268
|
+
YARA rule for malware detection and threat hunting.
|
|
269
|
+
|
|
270
|
+
Args:
|
|
271
|
+
file_path: Path to the binary file (must be in workspace)
|
|
272
|
+
function_address: Function address to extract bytes from (e.g., 'main', '0x401000')
|
|
273
|
+
rule_name: Name for the YARA rule (default 'auto_generated_rule')
|
|
274
|
+
byte_length: Number of bytes to extract (default 64, max 1024)
|
|
275
|
+
timeout: Execution timeout in seconds (default 300)
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
ToolResult with YARA rule string
|
|
279
|
+
"""
|
|
280
|
+
# 1. Validate parameters
|
|
281
|
+
validate_tool_parameters(
|
|
282
|
+
"generate_yara_rule",
|
|
283
|
+
{
|
|
284
|
+
"function_address": function_address,
|
|
285
|
+
"rule_name": rule_name,
|
|
286
|
+
"byte_length": byte_length,
|
|
287
|
+
},
|
|
288
|
+
)
|
|
289
|
+
validated_path = validate_file_path(file_path)
|
|
290
|
+
|
|
291
|
+
# 2. Validate rule_name format
|
|
292
|
+
if not _RULE_NAME_PATTERN.match(rule_name):
|
|
293
|
+
return failure(
|
|
294
|
+
"VALIDATION_ERROR",
|
|
295
|
+
"rule_name must start with a letter and contain only alphanumeric characters and underscores",
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
# 3. Security check for function address (prevent shell injection)
|
|
299
|
+
validation_error = _validate_address_or_fail(function_address, "function_address")
|
|
300
|
+
if validation_error:
|
|
301
|
+
return validation_error
|
|
302
|
+
|
|
303
|
+
# 4. Extract hex bytes using radare2's p8 command
|
|
304
|
+
r2_cmds = [
|
|
305
|
+
f"s {function_address}", # Seek to address
|
|
306
|
+
f"p8 {byte_length}", # Print hex bytes
|
|
307
|
+
]
|
|
308
|
+
|
|
309
|
+
# Always use -n for p8 to avoid analysis hang
|
|
310
|
+
analysis_level = "-n"
|
|
311
|
+
|
|
312
|
+
# 4. Extract hex bytes using helper
|
|
313
|
+
output, bytes_read = await _execute_r2_command(
|
|
314
|
+
validated_path,
|
|
315
|
+
r2_cmds,
|
|
316
|
+
analysis_level=analysis_level,
|
|
317
|
+
max_output_size=1_000_000,
|
|
318
|
+
base_timeout=timeout,
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
# 5. Validate output
|
|
322
|
+
hex_bytes = output.strip()
|
|
323
|
+
if not hex_bytes or not _HEX_PATTERN.match(hex_bytes):
|
|
324
|
+
return failure(
|
|
325
|
+
"YARA_GENERATION_ERROR",
|
|
326
|
+
f"Failed to extract valid hex bytes from address: {function_address}",
|
|
327
|
+
hint="Verify the address is valid and contains executable code",
|
|
328
|
+
)
|
|
329
|
+
|
|
330
|
+
# Check for invalid patterns (all 00 or all FF)
|
|
331
|
+
if len(hex_bytes) > 16 and (
|
|
332
|
+
_ALL_00_PATTERN.match(hex_bytes) or _ALL_FF_PATTERN.match(hex_bytes)
|
|
333
|
+
):
|
|
334
|
+
# Smart Offset Search: Try to find a better address
|
|
335
|
+
# 1. Try 'main' if we weren't already there
|
|
336
|
+
# 2. Try entry point 'entry0'
|
|
337
|
+
# 3. Find largest function
|
|
338
|
+
|
|
339
|
+
logger.info(f"Invalid bytes at {function_address}, attempting smart offset search...")
|
|
340
|
+
|
|
341
|
+
# Try to find a better function
|
|
342
|
+
cmd = "aflj"
|
|
343
|
+
out, _ = await _execute_r2_command(
|
|
344
|
+
validated_path, [cmd], analysis_level="aaa", base_timeout=timeout
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
try:
|
|
348
|
+
funcs = _parse_json_output(out)
|
|
349
|
+
if funcs and isinstance(funcs, list):
|
|
350
|
+
# Find the largest function as a fallback
|
|
351
|
+
largest_func = max(funcs, key=lambda x: x.get("size", 0))
|
|
352
|
+
suggested_addr = hex(largest_func.get("offset", 0))
|
|
353
|
+
suggested_name = largest_func.get("name", "unknown")
|
|
354
|
+
|
|
355
|
+
return failure(
|
|
356
|
+
"YARA_GENERATION_ERROR",
|
|
357
|
+
f"Address {function_address} contains invalid bytes (all 0x00 or 0xFF). "
|
|
358
|
+
f"Try using a different address.",
|
|
359
|
+
hint=f"Suggested alternative: {suggested_name} at {suggested_addr}",
|
|
360
|
+
)
|
|
361
|
+
except Exception:
|
|
362
|
+
pass
|
|
363
|
+
|
|
364
|
+
return failure(
|
|
365
|
+
"YARA_GENERATION_ERROR",
|
|
366
|
+
f"Address {function_address} contains invalid bytes (all 0x00 or 0xFF)",
|
|
367
|
+
hint="Try a different function or address that contains actual code",
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
# 5. Format as YARA hex string (space-separated pairs)
|
|
371
|
+
formatted_bytes = _format_hex_bytes(hex_bytes)
|
|
372
|
+
|
|
373
|
+
# 6. Generate YARA rule
|
|
374
|
+
file_name = _sanitize_filename_for_rule(file_path)
|
|
375
|
+
|
|
376
|
+
yara_rule = f"""rule {rule_name} {{
|
|
377
|
+
meta:
|
|
378
|
+
description = "Auto-generated YARA rule for {file_name}"
|
|
379
|
+
function = "{function_address}"
|
|
380
|
+
length = {byte_length}
|
|
381
|
+
author = "Reversecore_MCP"
|
|
382
|
+
date = "auto-generated"
|
|
383
|
+
|
|
384
|
+
strings:
|
|
385
|
+
$code = {{ {formatted_bytes} }}
|
|
386
|
+
|
|
387
|
+
condition:
|
|
388
|
+
$code
|
|
389
|
+
}}"""
|
|
390
|
+
|
|
391
|
+
# 7. Return YARA rule
|
|
392
|
+
return success(
|
|
393
|
+
yara_rule,
|
|
394
|
+
bytes_read=bytes_read,
|
|
395
|
+
function_address=function_address,
|
|
396
|
+
rule_name=rule_name,
|
|
397
|
+
length=byte_length,
|
|
398
|
+
format="yara",
|
|
399
|
+
hex_bytes=formatted_bytes,
|
|
400
|
+
description=f"YARA rule '{rule_name}' generated from {byte_length} bytes at {function_address}",
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
@log_execution(tool_name="generate_enhanced_yara_rule")
|
|
405
|
+
@track_metrics("generate_enhanced_yara_rule")
|
|
406
|
+
@handle_tool_errors
|
|
407
|
+
async def generate_enhanced_yara_rule(
|
|
408
|
+
file_path: str,
|
|
409
|
+
rule_name: str,
|
|
410
|
+
strings: list[str],
|
|
411
|
+
imports: list[str] = None,
|
|
412
|
+
file_type: str = "PE",
|
|
413
|
+
min_filesize: int = None,
|
|
414
|
+
max_filesize: int = None,
|
|
415
|
+
section_names: list[str] = None,
|
|
416
|
+
entry_point_pattern: str = None,
|
|
417
|
+
description: str = "",
|
|
418
|
+
author: str = "Reversecore_MCP",
|
|
419
|
+
min_string_matches: int = None,
|
|
420
|
+
) -> ToolResult:
|
|
421
|
+
"""
|
|
422
|
+
Generate an enhanced YARA rule with structural conditions to reduce false positives.
|
|
423
|
+
|
|
424
|
+
This function creates YARA rules that combine:
|
|
425
|
+
- String patterns (required)
|
|
426
|
+
- Structural conditions (PE characteristics, file size)
|
|
427
|
+
- Import table checks (optional)
|
|
428
|
+
- Section name checks (optional)
|
|
429
|
+
- Entry point patterns (optional)
|
|
430
|
+
|
|
431
|
+
**Why Enhanced Rules?**
|
|
432
|
+
Simple string-only rules cause high false positive rates. By adding structural
|
|
433
|
+
conditions, rules become more precise and suitable for production use.
|
|
434
|
+
|
|
435
|
+
Args:
|
|
436
|
+
file_path: Path to reference binary (for metadata extraction)
|
|
437
|
+
rule_name: Name for the YARA rule
|
|
438
|
+
strings: List of strings to include in the rule
|
|
439
|
+
imports: List of imported functions to check (e.g., ["CryptEncrypt", "WriteFile"])
|
|
440
|
+
file_type: Target file type - "PE" or "ELF" (default: "PE")
|
|
441
|
+
min_filesize: Minimum file size in bytes (optional)
|
|
442
|
+
max_filesize: Maximum file size in bytes (optional)
|
|
443
|
+
section_names: Required section names (e.g., [".rsrc", ".text"])
|
|
444
|
+
entry_point_pattern: Hex pattern at entry point (optional)
|
|
445
|
+
description: Rule description for metadata
|
|
446
|
+
author: Rule author for metadata
|
|
447
|
+
min_string_matches: Minimum number of strings that must match (default: 2/3 of total)
|
|
448
|
+
|
|
449
|
+
Returns:
|
|
450
|
+
ToolResult with enhanced YARA rule string
|
|
451
|
+
|
|
452
|
+
Example:
|
|
453
|
+
generate_enhanced_yara_rule(
|
|
454
|
+
"/app/workspace/wannacry.exe",
|
|
455
|
+
"WannaCry_Ransomware",
|
|
456
|
+
strings=["WANACRY!", "WNcry@2ol7", "bitcoin"],
|
|
457
|
+
imports=["CryptEncrypt", "CreateServiceA"],
|
|
458
|
+
min_filesize=3000000,
|
|
459
|
+
max_filesize=4000000,
|
|
460
|
+
section_names=[".rsrc"],
|
|
461
|
+
)
|
|
462
|
+
"""
|
|
463
|
+
# 1. Validate parameters
|
|
464
|
+
validated_path = validate_file_path(file_path)
|
|
465
|
+
|
|
466
|
+
if not _RULE_NAME_PATTERN.match(rule_name):
|
|
467
|
+
return failure(
|
|
468
|
+
"VALIDATION_ERROR",
|
|
469
|
+
"rule_name must start with a letter and contain only alphanumeric characters and underscores",
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
if not strings or len(strings) == 0:
|
|
473
|
+
return failure(
|
|
474
|
+
"VALIDATION_ERROR",
|
|
475
|
+
"At least one string is required for YARA rule generation",
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
# 2. Build strings section (max 10 strings)
|
|
479
|
+
string_definitions = []
|
|
480
|
+
for i, s in enumerate(strings[:10]):
|
|
481
|
+
# Escape special characters
|
|
482
|
+
escaped = s.replace("\\", "\\\\").replace('"', '\\"')
|
|
483
|
+
string_definitions.append(f' $str{i} = "{escaped}" ascii wide nocase')
|
|
484
|
+
|
|
485
|
+
strings_section = "\n".join(string_definitions)
|
|
486
|
+
|
|
487
|
+
# 3. Build imports section (optional)
|
|
488
|
+
import_definitions = []
|
|
489
|
+
if imports:
|
|
490
|
+
for i, imp in enumerate(imports[:10]):
|
|
491
|
+
escaped = imp.replace("\\", "\\\\").replace('"', '\\"')
|
|
492
|
+
import_definitions.append(f' $imp{i} = "{escaped}" ascii')
|
|
493
|
+
strings_section += "\n" + "\n".join(import_definitions)
|
|
494
|
+
|
|
495
|
+
# 4. Calculate min_string_matches (default: 2/3 of total, minimum 1)
|
|
496
|
+
total_strings = len(strings[:10])
|
|
497
|
+
if min_string_matches is None:
|
|
498
|
+
min_string_matches = max(1, (total_strings * 2) // 3)
|
|
499
|
+
min_string_matches = min(min_string_matches, total_strings)
|
|
500
|
+
|
|
501
|
+
# 5. Build condition section
|
|
502
|
+
conditions = []
|
|
503
|
+
|
|
504
|
+
# File type condition
|
|
505
|
+
if file_type.upper() == "PE":
|
|
506
|
+
conditions.append("uint16(0) == 0x5A4D") # MZ header
|
|
507
|
+
conditions.append("uint32(uint32(0x3C)) == 0x00004550") # PE signature
|
|
508
|
+
elif file_type.upper() == "ELF":
|
|
509
|
+
conditions.append("uint32(0) == 0x464C457F") # ELF magic
|
|
510
|
+
|
|
511
|
+
# File size conditions
|
|
512
|
+
if min_filesize:
|
|
513
|
+
conditions.append(f"filesize > {min_filesize}")
|
|
514
|
+
if max_filesize:
|
|
515
|
+
conditions.append(f"filesize < {max_filesize}")
|
|
516
|
+
|
|
517
|
+
# String match condition
|
|
518
|
+
if total_strings > 1:
|
|
519
|
+
conditions.append(f"{min_string_matches} of ($str*)")
|
|
520
|
+
else:
|
|
521
|
+
conditions.append("$str0")
|
|
522
|
+
|
|
523
|
+
# Import conditions (if provided)
|
|
524
|
+
if imports and len(imports) > 0:
|
|
525
|
+
min_import_matches = max(1, len(imports[:10]) // 2)
|
|
526
|
+
conditions.append(f"{min_import_matches} of ($imp*)")
|
|
527
|
+
|
|
528
|
+
# Section name conditions (optional)
|
|
529
|
+
if section_names:
|
|
530
|
+
for section in section_names[:5]:
|
|
531
|
+
# Use pe module for section checks
|
|
532
|
+
conditions.append(f'pe.sections[pe.number_of_sections - 1].name contains "{section}"')
|
|
533
|
+
|
|
534
|
+
# Entry point pattern (optional)
|
|
535
|
+
if entry_point_pattern:
|
|
536
|
+
if file_type_upper == "PE":
|
|
537
|
+
conditions.append(f"$ep at pe.entry_point")
|
|
538
|
+
elif file_type_upper == "ELF":
|
|
539
|
+
conditions.append(f"$ep at elf.entry_point")
|
|
540
|
+
string_definitions.append(f' $ep = {{ {entry_point_pattern} }}')
|
|
541
|
+
|
|
542
|
+
# 6. Build condition string
|
|
543
|
+
condition_str = " and\n ".join(conditions)
|
|
544
|
+
file_type = file_type.upper()
|
|
545
|
+
|
|
546
|
+
# Imports section
|
|
547
|
+
imports_declaration = ""
|
|
548
|
+
# LOGIC FIX: Don't import "pe" for ELF files or others
|
|
549
|
+
if "PE" in file_type and (section_names or entry_point_pattern):
|
|
550
|
+
imports_declaration = 'import "pe"\n\n'
|
|
551
|
+
elif "ELF" in file_type and (section_names or entry_point_pattern):
|
|
552
|
+
imports_declaration = 'import "elf"\n\n'
|
|
553
|
+
|
|
554
|
+
# Handle tags
|
|
555
|
+
tags_str = ""
|
|
556
|
+
if tags:
|
|
557
|
+
tags_str = " : " + " ".join(tags)
|
|
558
|
+
|
|
559
|
+
# 7. Generate complete rule
|
|
560
|
+
yara_rule = f'''{imports_declaration}rule {rule_name}{tags_str} {{
|
|
561
|
+
meta:
|
|
562
|
+
description = "{description or f'Enhanced detection rule for {rule_name}'}"
|
|
563
|
+
author = "{author}"
|
|
564
|
+
date = "{datetime.now().strftime('%Y-%m-%d')}"
|
|
565
|
+
reference = "Generated by Reversecore MCP"
|
|
566
|
+
false_positive_reduction = "Structural conditions applied"
|
|
567
|
+
min_string_matches = {min_string_matches}
|
|
568
|
+
hash = "{hash_value}"
|
|
569
|
+
confidence = "verdict"
|
|
570
|
+
|
|
571
|
+
strings:
|
|
572
|
+
{strings_section}
|
|
573
|
+
|
|
574
|
+
condition:
|
|
575
|
+
{condition_str}
|
|
576
|
+
}}'''
|
|
577
|
+
|
|
578
|
+
return success(
|
|
579
|
+
yara_rule,
|
|
580
|
+
rule_name=rule_name,
|
|
581
|
+
string_count=len(strings[:10]),
|
|
582
|
+
import_count=len(imports[:10]) if imports else 0,
|
|
583
|
+
condition_count=len(conditions),
|
|
584
|
+
min_string_matches=min_string_matches,
|
|
585
|
+
format="yara",
|
|
586
|
+
description=f"Enhanced YARA rule '{rule_name}' with {len(conditions)} structural conditions",
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
# Note: SignatureToolsPlugin has been removed.
|
|
591
|
+
# The signature tools are now registered via AnalysisToolsPlugin in analysis/__init__.py.
|