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,214 @@
|
|
|
1
|
+
"""YARA scanning tools for binary analysis with rule matching."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Protocol
|
|
4
|
+
|
|
5
|
+
from reversecore_mcp.core.decorators import log_execution
|
|
6
|
+
from reversecore_mcp.core.error_handling import handle_tool_errors
|
|
7
|
+
from reversecore_mcp.core.metrics import track_metrics
|
|
8
|
+
from reversecore_mcp.core.result import ToolResult, failure, success
|
|
9
|
+
from reversecore_mcp.core.security import validate_file_path
|
|
10
|
+
from reversecore_mcp.core.validators import validate_tool_parameters
|
|
11
|
+
|
|
12
|
+
# Global cache for compiled YARA rules: {file_path: (timestamp, compiled_rules)}
|
|
13
|
+
_YARA_RULES_CACHE: dict[str, tuple[float, Any]] = {}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class YaraStringMatchInstance(Protocol):
|
|
17
|
+
"""Subset of yara.StringMatchInstance used by our formatter."""
|
|
18
|
+
|
|
19
|
+
offset: int | None
|
|
20
|
+
matched_data: bytes | None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class YaraStringMatch(Protocol):
|
|
24
|
+
"""Subset of yara.StringMatch used by our formatter."""
|
|
25
|
+
|
|
26
|
+
identifier: str | None
|
|
27
|
+
instances: list[YaraStringMatchInstance] | None
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class YaraMatch(Protocol):
|
|
31
|
+
"""Subset of yara.Match used by our formatter."""
|
|
32
|
+
|
|
33
|
+
rule: str
|
|
34
|
+
namespace: str
|
|
35
|
+
tags: list[str]
|
|
36
|
+
meta: dict[str, Any]
|
|
37
|
+
strings: list[YaraStringMatch] | None
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _format_yara_match(match: YaraMatch) -> dict[str, Any]:
|
|
41
|
+
"""
|
|
42
|
+
Format a YARA match result as a dictionary.
|
|
43
|
+
|
|
44
|
+
This helper function extracts match information and formats it
|
|
45
|
+
consistently. Supports both modern and legacy yara-python APIs.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
match: YARA match object
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Dictionary with formatted match information
|
|
52
|
+
"""
|
|
53
|
+
formatted_strings = []
|
|
54
|
+
|
|
55
|
+
# Check if match has strings attribute
|
|
56
|
+
match_strings = getattr(match, "strings", None)
|
|
57
|
+
if match_strings:
|
|
58
|
+
try:
|
|
59
|
+
# OPTIMIZATION: Cache isinstance check and reduce getattr calls
|
|
60
|
+
# Try modern API first (more common case)
|
|
61
|
+
for sm in match_strings:
|
|
62
|
+
identifier = getattr(sm, "identifier", None)
|
|
63
|
+
instances = getattr(sm, "instances", None)
|
|
64
|
+
if instances:
|
|
65
|
+
# Pre-cache the formatted string for reuse
|
|
66
|
+
for inst in instances:
|
|
67
|
+
offset = getattr(inst, "offset", None)
|
|
68
|
+
matched_data = getattr(inst, "matched_data", None)
|
|
69
|
+
# Convert matched_data to string (optimized with early check)
|
|
70
|
+
if matched_data is None:
|
|
71
|
+
data_str = None
|
|
72
|
+
else:
|
|
73
|
+
# Single isinstance check instead of repeated checks
|
|
74
|
+
data_str = (
|
|
75
|
+
matched_data.hex()
|
|
76
|
+
if isinstance(matched_data, bytes)
|
|
77
|
+
else str(matched_data)
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
formatted_strings.append(
|
|
81
|
+
{
|
|
82
|
+
"identifier": identifier,
|
|
83
|
+
"offset": int(offset) if offset is not None else None,
|
|
84
|
+
"matched_data": data_str,
|
|
85
|
+
}
|
|
86
|
+
)
|
|
87
|
+
except (AttributeError, TypeError):
|
|
88
|
+
# Fallback: older API may return tuples (offset, identifier, data)
|
|
89
|
+
formatted_strings = []
|
|
90
|
+
for t in match_strings:
|
|
91
|
+
if isinstance(t, (list, tuple)) and len(t) >= 3:
|
|
92
|
+
off, ident, data = t[0], t[1], t[2]
|
|
93
|
+
data_str = data.hex() if isinstance(data, bytes) else str(data)
|
|
94
|
+
formatted_strings.append(
|
|
95
|
+
{
|
|
96
|
+
"identifier": ident,
|
|
97
|
+
"offset": int(off) if off is not None else None,
|
|
98
|
+
"matched_data": data_str,
|
|
99
|
+
}
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
"rule": match.rule,
|
|
104
|
+
"namespace": match.namespace,
|
|
105
|
+
"tags": match.tags,
|
|
106
|
+
"meta": match.meta,
|
|
107
|
+
"strings": formatted_strings,
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@log_execution(tool_name="run_yara")
|
|
112
|
+
@track_metrics("run_yara")
|
|
113
|
+
@handle_tool_errors
|
|
114
|
+
def run_yara(
|
|
115
|
+
file_path: str,
|
|
116
|
+
rule_file: str,
|
|
117
|
+
timeout: int = 300,
|
|
118
|
+
) -> ToolResult:
|
|
119
|
+
"""Scan binaries against YARA rules via ``yara-python``."""
|
|
120
|
+
|
|
121
|
+
validate_tool_parameters(
|
|
122
|
+
"run_yara",
|
|
123
|
+
{"rule_file": rule_file, "timeout": timeout},
|
|
124
|
+
)
|
|
125
|
+
validated_file = validate_file_path(file_path)
|
|
126
|
+
validated_rule = validate_file_path(rule_file, read_only=True)
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
import yara
|
|
130
|
+
except ImportError:
|
|
131
|
+
return failure(
|
|
132
|
+
"DEPENDENCY_MISSING",
|
|
133
|
+
"yara-python library is not installed",
|
|
134
|
+
hint="Install with: pip install yara-python",
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
timeout_error = getattr(yara, "TimeoutError", None)
|
|
138
|
+
generic_error = getattr(yara, "Error", None)
|
|
139
|
+
|
|
140
|
+
# Check cache for compiled rules
|
|
141
|
+
rule_path_str = str(validated_rule)
|
|
142
|
+
current_mtime = validated_rule.stat().st_mtime
|
|
143
|
+
|
|
144
|
+
rules = None
|
|
145
|
+
if rule_path_str in _YARA_RULES_CACHE:
|
|
146
|
+
cached_mtime, cached_rules = _YARA_RULES_CACHE[rule_path_str]
|
|
147
|
+
if cached_mtime == current_mtime:
|
|
148
|
+
rules = cached_rules
|
|
149
|
+
|
|
150
|
+
if rules is None:
|
|
151
|
+
try:
|
|
152
|
+
rules = yara.compile(filepath=rule_path_str)
|
|
153
|
+
# Update cache with size limit
|
|
154
|
+
if len(_YARA_RULES_CACHE) >= 100:
|
|
155
|
+
# Remove oldest entry (FIFO) to prevent unbounded growth
|
|
156
|
+
_YARA_RULES_CACHE.pop(next(iter(_YARA_RULES_CACHE)))
|
|
157
|
+
_YARA_RULES_CACHE[rule_path_str] = (current_mtime, rules)
|
|
158
|
+
except Exception as exc: # noqa: BLE001 - need yara-specific surface area
|
|
159
|
+
# Try fallback for non-ASCII paths on Windows
|
|
160
|
+
try:
|
|
161
|
+
# Read rule content and compile from source
|
|
162
|
+
rule_content = validated_rule.read_text(encoding="utf-8")
|
|
163
|
+
rules = yara.compile(source=rule_content)
|
|
164
|
+
if len(_YARA_RULES_CACHE) >= 100:
|
|
165
|
+
_YARA_RULES_CACHE.pop(next(iter(_YARA_RULES_CACHE)))
|
|
166
|
+
_YARA_RULES_CACHE[rule_path_str] = (current_mtime, rules)
|
|
167
|
+
except Exception:
|
|
168
|
+
# If fallback fails, report original error
|
|
169
|
+
if generic_error and isinstance(exc, generic_error):
|
|
170
|
+
return failure("YARA_ERROR", f"YARA error: {exc}")
|
|
171
|
+
raise
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
matches = rules.match(str(validated_file), timeout=timeout)
|
|
175
|
+
except Exception as exc: # noqa: BLE001 - need to inspect yara-specific errors
|
|
176
|
+
# Check for timeout first
|
|
177
|
+
if timeout_error and isinstance(exc, timeout_error):
|
|
178
|
+
return failure(
|
|
179
|
+
"TIMEOUT",
|
|
180
|
+
f"YARA scan timed out after {timeout} seconds",
|
|
181
|
+
timeout_seconds=timeout,
|
|
182
|
+
details={"error": str(exc)},
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
# For any other error (including "Illegal byte sequence" on Windows),
|
|
186
|
+
# try fallback to memory scan if file size permits
|
|
187
|
+
file_size = 0
|
|
188
|
+
try:
|
|
189
|
+
file_size = validated_file.stat().st_size
|
|
190
|
+
except Exception:
|
|
191
|
+
pass
|
|
192
|
+
|
|
193
|
+
if file_size < 100 * 1024 * 1024:
|
|
194
|
+
try:
|
|
195
|
+
data = validated_file.read_bytes()
|
|
196
|
+
matches = rules.match(data=data, timeout=timeout)
|
|
197
|
+
except Exception as fallback_exc:
|
|
198
|
+
# If fallback fails, return the original error
|
|
199
|
+
if generic_error and isinstance(exc, generic_error):
|
|
200
|
+
return failure(
|
|
201
|
+
"YARA_ERROR",
|
|
202
|
+
f"Fallback failed: {fallback_exc}. Original: {exc}",
|
|
203
|
+
)
|
|
204
|
+
raise
|
|
205
|
+
else:
|
|
206
|
+
if generic_error and isinstance(exc, generic_error):
|
|
207
|
+
return failure("YARA_ERROR", f"YARA error: {exc}")
|
|
208
|
+
raise
|
|
209
|
+
|
|
210
|
+
if not matches:
|
|
211
|
+
return success({"matches": [], "match_count": 0})
|
|
212
|
+
|
|
213
|
+
results = [_format_yara_match(match) for match in matches]
|
|
214
|
+
return success({"matches": results, "match_count": len(matches)})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Patch Explainer module - Backward compatibility alias.
|
|
3
|
+
|
|
4
|
+
This module was moved to common/patch_explainer.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# Re-export everything from common.patch_explainer
|
|
8
|
+
from reversecore_mcp.tools.common.patch_explainer import *
|
|
9
|
+
from reversecore_mcp.tools.common.patch_explainer import (
|
|
10
|
+
explain_patch,
|
|
11
|
+
_generate_explanation,
|
|
12
|
+
_generate_diff_snippet,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"explain_patch",
|
|
17
|
+
"_generate_explanation",
|
|
18
|
+
"_generate_diff_snippet",
|
|
19
|
+
]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Radare2 tools package."""
|
|
2
|
+
from reversecore_mcp.tools.radare2.radare2_mcp_tools import Radare2ToolsPlugin
|
|
3
|
+
from reversecore_mcp.tools.radare2 import r2_analysis
|
|
4
|
+
|
|
5
|
+
# Backward compatibility re-exports for legacy imports
|
|
6
|
+
from reversecore_mcp.tools.analysis import static_analysis
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"Radare2ToolsPlugin",
|
|
10
|
+
"r2_analysis",
|
|
11
|
+
# Backward compatibility
|
|
12
|
+
"static_analysis",
|
|
13
|
+
]
|