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,519 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Vulnerability Hunter: Automated Vulnerability Discovery Tool.
|
|
3
|
+
|
|
4
|
+
This tool combines multiple analysis techniques to automatically discover
|
|
5
|
+
potential security vulnerabilities in binary files:
|
|
6
|
+
|
|
7
|
+
1. Dangerous API Detection - Finds calls to unsafe functions
|
|
8
|
+
2. Backtrace Analysis - Traces execution paths to dangerous calls
|
|
9
|
+
3. Light Taint Analysis - Identifies user-controllable inputs
|
|
10
|
+
4. Report Generation - Creates vulnerability reports with YARA rules
|
|
11
|
+
|
|
12
|
+
This is a signature tool that combines: decompilation, xrefs, signature_tools, dormant_detector
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import re
|
|
18
|
+
from functools import lru_cache
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
from async_lru import alru_cache
|
|
23
|
+
from fastmcp import Context, FastMCP
|
|
24
|
+
|
|
25
|
+
from reversecore_mcp.core import json_utils as json
|
|
26
|
+
from reversecore_mcp.core.config import get_config
|
|
27
|
+
from reversecore_mcp.core.decorators import log_execution
|
|
28
|
+
from reversecore_mcp.core.error_handling import handle_tool_errors
|
|
29
|
+
from reversecore_mcp.core.execution import execute_subprocess_async
|
|
30
|
+
from reversecore_mcp.core.logging_config import get_logger
|
|
31
|
+
from reversecore_mcp.core.metrics import track_metrics
|
|
32
|
+
from reversecore_mcp.core.r2_helpers import calculate_dynamic_timeout
|
|
33
|
+
from reversecore_mcp.core.result import ToolResult, failure, success
|
|
34
|
+
from reversecore_mcp.core.security import validate_file_path
|
|
35
|
+
|
|
36
|
+
logger = get_logger(__name__)
|
|
37
|
+
|
|
38
|
+
DEFAULT_TIMEOUT = get_config().default_tool_timeout
|
|
39
|
+
|
|
40
|
+
# =============================================================================
|
|
41
|
+
# Dangerous API Database
|
|
42
|
+
# =============================================================================
|
|
43
|
+
|
|
44
|
+
# Categorized dangerous APIs with severity and description
|
|
45
|
+
DANGEROUS_APIS: dict[str, dict[str, Any]] = {
|
|
46
|
+
# Memory Corruption - Critical
|
|
47
|
+
"strcpy": {"severity": "critical", "category": "buffer_overflow", "description": "Unbounded string copy"},
|
|
48
|
+
"strcat": {"severity": "critical", "category": "buffer_overflow", "description": "Unbounded string concatenation"},
|
|
49
|
+
"sprintf": {"severity": "critical", "category": "buffer_overflow", "description": "Unbounded formatted string"},
|
|
50
|
+
"gets": {"severity": "critical", "category": "buffer_overflow", "description": "Unbounded input read"},
|
|
51
|
+
"scanf": {"severity": "high", "category": "buffer_overflow", "description": "Potentially unbounded input"},
|
|
52
|
+
"vsprintf": {"severity": "critical", "category": "buffer_overflow", "description": "Unbounded formatted string (va)"},
|
|
53
|
+
|
|
54
|
+
# Format String
|
|
55
|
+
"printf": {"severity": "medium", "category": "format_string", "description": "Format string if user-controlled"},
|
|
56
|
+
"fprintf": {"severity": "medium", "category": "format_string", "description": "Format string if user-controlled"},
|
|
57
|
+
"syslog": {"severity": "medium", "category": "format_string", "description": "Format string in logging"},
|
|
58
|
+
|
|
59
|
+
# Command Injection - Critical
|
|
60
|
+
"system": {"severity": "critical", "category": "command_injection", "description": "Shell command execution"},
|
|
61
|
+
"popen": {"severity": "critical", "category": "command_injection", "description": "Shell command with pipe"},
|
|
62
|
+
"execve": {"severity": "critical", "category": "command_injection", "description": "Process execution"},
|
|
63
|
+
"execl": {"severity": "critical", "category": "command_injection", "description": "Process execution"},
|
|
64
|
+
"execlp": {"severity": "critical", "category": "command_injection", "description": "Process execution (PATH)"},
|
|
65
|
+
"execvp": {"severity": "critical", "category": "command_injection", "description": "Process execution (PATH)"},
|
|
66
|
+
"ShellExecute": {"severity": "critical", "category": "command_injection", "description": "Windows shell execution"},
|
|
67
|
+
"ShellExecuteA": {"severity": "critical", "category": "command_injection", "description": "Windows shell execution"},
|
|
68
|
+
"ShellExecuteW": {"severity": "critical", "category": "command_injection", "description": "Windows shell execution"},
|
|
69
|
+
"WinExec": {"severity": "critical", "category": "command_injection", "description": "Windows process execution"},
|
|
70
|
+
"CreateProcess": {"severity": "high", "category": "command_injection", "description": "Windows process creation"},
|
|
71
|
+
"CreateProcessA": {"severity": "high", "category": "command_injection", "description": "Windows process creation"},
|
|
72
|
+
"CreateProcessW": {"severity": "high", "category": "command_injection", "description": "Windows process creation"},
|
|
73
|
+
|
|
74
|
+
# Memory Management
|
|
75
|
+
"malloc": {"severity": "low", "category": "memory", "description": "Dynamic allocation (check for integer overflow)"},
|
|
76
|
+
"realloc": {"severity": "medium", "category": "memory", "description": "Reallocation (check for size)"},
|
|
77
|
+
"free": {"severity": "medium", "category": "memory", "description": "Free (check for double-free/UAF)"},
|
|
78
|
+
"VirtualAlloc": {"severity": "low", "category": "memory", "description": "Windows memory allocation"},
|
|
79
|
+
"HeapAlloc": {"severity": "low", "category": "memory", "description": "Windows heap allocation"},
|
|
80
|
+
|
|
81
|
+
# File Operations
|
|
82
|
+
"fopen": {"severity": "low", "category": "file_access", "description": "File open (check path)"},
|
|
83
|
+
"open": {"severity": "low", "category": "file_access", "description": "File open (check path)"},
|
|
84
|
+
"CreateFile": {"severity": "low", "category": "file_access", "description": "Windows file open"},
|
|
85
|
+
"CreateFileA": {"severity": "low", "category": "file_access", "description": "Windows file open"},
|
|
86
|
+
"CreateFileW": {"severity": "low", "category": "file_access", "description": "Windows file open"},
|
|
87
|
+
"unlink": {"severity": "medium", "category": "file_access", "description": "File deletion"},
|
|
88
|
+
"remove": {"severity": "medium", "category": "file_access", "description": "File deletion"},
|
|
89
|
+
"DeleteFile": {"severity": "medium", "category": "file_access", "description": "Windows file deletion"},
|
|
90
|
+
|
|
91
|
+
# Network
|
|
92
|
+
"recv": {"severity": "high", "category": "network_input", "description": "Network receive (user input source)"},
|
|
93
|
+
"recvfrom": {"severity": "high", "category": "network_input", "description": "Network receive (user input source)"},
|
|
94
|
+
"read": {"severity": "medium", "category": "input", "description": "Read (potential user input)"},
|
|
95
|
+
"fread": {"severity": "medium", "category": "input", "description": "File read (potential user input)"},
|
|
96
|
+
"fgets": {"severity": "low", "category": "input", "description": "Bounded string read"},
|
|
97
|
+
"getenv": {"severity": "medium", "category": "input", "description": "Environment variable (user-controlled)"},
|
|
98
|
+
|
|
99
|
+
# Dangerous Windows APIs
|
|
100
|
+
"LoadLibrary": {"severity": "high", "category": "code_execution", "description": "DLL loading"},
|
|
101
|
+
"LoadLibraryA": {"severity": "high", "category": "code_execution", "description": "DLL loading"},
|
|
102
|
+
"LoadLibraryW": {"severity": "high", "category": "code_execution", "description": "DLL loading"},
|
|
103
|
+
"GetProcAddress": {"severity": "medium", "category": "code_execution", "description": "Dynamic function resolution"},
|
|
104
|
+
"WriteProcessMemory": {"severity": "critical", "category": "code_injection", "description": "Process memory write"},
|
|
105
|
+
"VirtualAllocEx": {"severity": "high", "category": "code_injection", "description": "Remote memory allocation"},
|
|
106
|
+
"CreateRemoteThread": {"severity": "critical", "category": "code_injection", "description": "Remote thread creation"},
|
|
107
|
+
|
|
108
|
+
# Crypto (weak)
|
|
109
|
+
"rand": {"severity": "medium", "category": "weak_crypto", "description": "Weak random number generator"},
|
|
110
|
+
"srand": {"severity": "low", "category": "weak_crypto", "description": "Weak RNG seed"},
|
|
111
|
+
"MD5": {"severity": "medium", "category": "weak_crypto", "description": "Weak hash algorithm"},
|
|
112
|
+
"SHA1": {"severity": "low", "category": "weak_crypto", "description": "Deprecated hash algorithm"},
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
# User input sources for taint analysis
|
|
116
|
+
USER_INPUT_SOURCES = frozenset({
|
|
117
|
+
"recv", "recvfrom", "read", "fread", "fgets", "gets", "scanf",
|
|
118
|
+
"getenv", "argv", "fopen", "open", "accept", "listen",
|
|
119
|
+
"ReadFile", "InternetReadFile", "HttpQueryInfo",
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
# Pre-compiled pattern for hex addresses
|
|
123
|
+
_HEX_PATTERN = re.compile(r"0x[0-9a-fA-F]+")
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _extract_json_safely(output: str) -> Any | None:
|
|
127
|
+
"""Extract JSON from radare2 output using robust state-machine parser.
|
|
128
|
+
|
|
129
|
+
Delegates to r2_helpers.parse_json_output which uses O(n) algorithm
|
|
130
|
+
instead of fragile regex patterns.
|
|
131
|
+
"""
|
|
132
|
+
if not output or not output.strip():
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
try:
|
|
136
|
+
from reversecore_mcp.core.r2_helpers import parse_json_output
|
|
137
|
+
return parse_json_output(output)
|
|
138
|
+
except Exception:
|
|
139
|
+
return None
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
async def _run_r2_cmd(file_path: str | Path, cmd: str, timeout: int | None = None) -> str:
|
|
143
|
+
"""Execute radare2 command."""
|
|
144
|
+
effective_timeout = timeout or calculate_dynamic_timeout(str(file_path), base_timeout=30)
|
|
145
|
+
full_cmd = ["radare2", "-q", "-c", cmd, str(file_path)]
|
|
146
|
+
output, _ = await execute_subprocess_async(full_cmd, timeout=effective_timeout)
|
|
147
|
+
return output
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
@log_execution(tool_name="vulnerability_hunter")
|
|
151
|
+
@track_metrics("vulnerability_hunter")
|
|
152
|
+
@handle_tool_errors
|
|
153
|
+
async def vulnerability_hunter(
|
|
154
|
+
file_path: str,
|
|
155
|
+
max_depth: int = 3,
|
|
156
|
+
severity_filter: str = "all",
|
|
157
|
+
generate_yara: bool = True,
|
|
158
|
+
timeout: int = 300,
|
|
159
|
+
ctx: Context = None,
|
|
160
|
+
) -> ToolResult:
|
|
161
|
+
"""
|
|
162
|
+
Automated vulnerability discovery combining multiple analysis techniques.
|
|
163
|
+
|
|
164
|
+
This tool performs a comprehensive security analysis:
|
|
165
|
+
1. Scans for dangerous API calls (buffer overflow, command injection, etc.)
|
|
166
|
+
2. Traces back to find how each dangerous call is reached
|
|
167
|
+
3. Identifies if inputs are user-controllable (light taint analysis)
|
|
168
|
+
4. Generates vulnerability report with YARA detection rules
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
file_path: Path to the binary file to analyze
|
|
172
|
+
max_depth: Maximum backtrace depth for call chain analysis (default: 3)
|
|
173
|
+
severity_filter: Filter by severity - "all", "critical", "high", "medium" (default: "all")
|
|
174
|
+
generate_yara: Generate YARA rules for detected vulnerabilities (default: True)
|
|
175
|
+
timeout: Analysis timeout in seconds (default: 300)
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
ToolResult containing:
|
|
179
|
+
- vulnerabilities: List of detected vulnerabilities with details
|
|
180
|
+
- call_chains: Execution paths leading to vulnerabilities
|
|
181
|
+
- taint_sources: Identified user input sources
|
|
182
|
+
- yara_rules: Generated YARA detection rules (if enabled)
|
|
183
|
+
- summary: Statistics and risk assessment
|
|
184
|
+
|
|
185
|
+
Example:
|
|
186
|
+
vulnerability_hunter("/path/to/binary.exe", severity_filter="critical")
|
|
187
|
+
"""
|
|
188
|
+
validated_path = validate_file_path(file_path)
|
|
189
|
+
|
|
190
|
+
if ctx:
|
|
191
|
+
await ctx.info("🔍 Vulnerability Hunter: Starting comprehensive analysis...")
|
|
192
|
+
|
|
193
|
+
# Phase 1: Analyze and get function list
|
|
194
|
+
if ctx:
|
|
195
|
+
await ctx.report_progress(10, 100)
|
|
196
|
+
await ctx.info("📊 Phase 1: Analyzing binary and enumerating functions...")
|
|
197
|
+
|
|
198
|
+
file_size = validated_path.stat().st_size
|
|
199
|
+
analysis_cmd = "aa" if file_size > 5_000_000 else "aaa"
|
|
200
|
+
|
|
201
|
+
output = await _run_r2_cmd(validated_path, f"{analysis_cmd}; aflj", timeout=timeout)
|
|
202
|
+
functions = _extract_json_safely(output)
|
|
203
|
+
|
|
204
|
+
if not functions or not isinstance(functions, list):
|
|
205
|
+
return failure(
|
|
206
|
+
"ANALYSIS_ERROR",
|
|
207
|
+
"Failed to analyze binary or extract function list",
|
|
208
|
+
hint="Try increasing timeout or check if the binary is valid"
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
# Phase 2: Find dangerous API calls
|
|
212
|
+
if ctx:
|
|
213
|
+
await ctx.report_progress(25, 100)
|
|
214
|
+
await ctx.info("⚠️ Phase 2: Scanning for dangerous API calls...")
|
|
215
|
+
|
|
216
|
+
vulnerabilities = []
|
|
217
|
+
dangerous_calls = []
|
|
218
|
+
|
|
219
|
+
# Build function name index for faster lookup
|
|
220
|
+
func_by_name = {f.get("name", ""): f for f in functions}
|
|
221
|
+
func_by_addr = {f.get("offset", 0): f for f in functions}
|
|
222
|
+
|
|
223
|
+
# Find imports that match dangerous APIs
|
|
224
|
+
imports_output = await _run_r2_cmd(validated_path, "iij", timeout=60)
|
|
225
|
+
imports = _extract_json_safely(imports_output) or []
|
|
226
|
+
|
|
227
|
+
for imp in imports:
|
|
228
|
+
imp_name = imp.get("name", "")
|
|
229
|
+
# Extract base function name (remove sym.imp. prefix and library suffix)
|
|
230
|
+
base_name = imp_name.replace("sym.imp.", "").split("@")[0]
|
|
231
|
+
|
|
232
|
+
if base_name in DANGEROUS_APIS:
|
|
233
|
+
api_info = DANGEROUS_APIS[base_name]
|
|
234
|
+
|
|
235
|
+
# Apply severity filter
|
|
236
|
+
if severity_filter != "all":
|
|
237
|
+
severity_order = {"critical": 0, "high": 1, "medium": 2, "low": 3}
|
|
238
|
+
filter_level = severity_order.get(severity_filter, 3)
|
|
239
|
+
api_level = severity_order.get(api_info["severity"], 3)
|
|
240
|
+
if api_level > filter_level:
|
|
241
|
+
continue
|
|
242
|
+
|
|
243
|
+
dangerous_calls.append({
|
|
244
|
+
"dangerous_api_name": base_name,
|
|
245
|
+
"import_symbol_full_name": imp_name,
|
|
246
|
+
"plt_address": hex(imp.get("plt", 0) or imp.get("vaddr", 0)),
|
|
247
|
+
"vulnerability_category": api_info["category"],
|
|
248
|
+
"severity_level": api_info["severity"],
|
|
249
|
+
"risk_description": api_info["description"],
|
|
250
|
+
})
|
|
251
|
+
|
|
252
|
+
if ctx:
|
|
253
|
+
await ctx.info(f" Found {len(dangerous_calls)} dangerous API calls")
|
|
254
|
+
|
|
255
|
+
# Phase 3: Backtrace analysis for each dangerous call
|
|
256
|
+
if ctx:
|
|
257
|
+
await ctx.report_progress(40, 100)
|
|
258
|
+
await ctx.info("🔙 Phase 3: Analyzing call chains (backtrace)...")
|
|
259
|
+
|
|
260
|
+
call_chains = []
|
|
261
|
+
taint_sources = []
|
|
262
|
+
|
|
263
|
+
for i, dangerous in enumerate(dangerous_calls[:20]): # Limit for performance
|
|
264
|
+
if ctx and i % 5 == 0:
|
|
265
|
+
await ctx.report_progress(40 + int((i / min(len(dangerous_calls), 20)) * 30), 100)
|
|
266
|
+
|
|
267
|
+
api_plt_address = dangerous["plt_address"]
|
|
268
|
+
|
|
269
|
+
# Get cross-references TO this function
|
|
270
|
+
xrefs_output = await _run_r2_cmd(validated_path, f"axtj @ {api_plt_address}", timeout=30)
|
|
271
|
+
xrefs = _extract_json_safely(xrefs_output) or []
|
|
272
|
+
|
|
273
|
+
chain_results = []
|
|
274
|
+
for xref in xrefs[:10]: # Limit xrefs per function
|
|
275
|
+
caller_addr = xref.get("from", 0)
|
|
276
|
+
caller_func = None
|
|
277
|
+
|
|
278
|
+
# Find which function contains this caller
|
|
279
|
+
for func in functions:
|
|
280
|
+
func_start = func.get("offset", 0)
|
|
281
|
+
func_size = func.get("size", 0)
|
|
282
|
+
if func_start <= caller_addr < func_start + func_size:
|
|
283
|
+
caller_func = func
|
|
284
|
+
break
|
|
285
|
+
|
|
286
|
+
if caller_func:
|
|
287
|
+
func_name = caller_func.get("name", "unknown")
|
|
288
|
+
|
|
289
|
+
# Check if this function receives user input (light taint analysis)
|
|
290
|
+
is_tainted = False
|
|
291
|
+
taint_source = None
|
|
292
|
+
|
|
293
|
+
# Get function disassembly to check for input sources
|
|
294
|
+
func_addr = caller_func.get("offset", 0)
|
|
295
|
+
disasm_output = await _run_r2_cmd(
|
|
296
|
+
validated_path, f"pdfj @ {hex(func_addr)}", timeout=30
|
|
297
|
+
)
|
|
298
|
+
func_disasm = _extract_json_safely(disasm_output)
|
|
299
|
+
|
|
300
|
+
if func_disasm and "ops" in func_disasm:
|
|
301
|
+
for op in func_disasm.get("ops", []):
|
|
302
|
+
disasm = op.get("disasm", "")
|
|
303
|
+
for source in USER_INPUT_SOURCES:
|
|
304
|
+
# Use word boundary to avoid false positives
|
|
305
|
+
# e.g., "pthread" should not match "read"
|
|
306
|
+
if re.search(rf"\b{re.escape(source)}\b", disasm):
|
|
307
|
+
is_tainted = True
|
|
308
|
+
taint_source = source
|
|
309
|
+
if source not in [t["input_source_api"] for t in taint_sources]:
|
|
310
|
+
taint_sources.append({
|
|
311
|
+
"input_source_api": source,
|
|
312
|
+
"found_in_function": func_name,
|
|
313
|
+
"instruction_address": hex(op.get("offset", 0))
|
|
314
|
+
})
|
|
315
|
+
break
|
|
316
|
+
|
|
317
|
+
chain_results.append({
|
|
318
|
+
"calling_function_name": func_name,
|
|
319
|
+
"call_instruction_address": hex(caller_addr),
|
|
320
|
+
"user_input_reaches_here": is_tainted,
|
|
321
|
+
"input_source_if_tainted": taint_source,
|
|
322
|
+
"reference_type": xref.get("type", "unknown")
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
call_chains.append({
|
|
326
|
+
"dangerous_api_name": dangerous["dangerous_api_name"],
|
|
327
|
+
"api_plt_address": dangerous["plt_address"],
|
|
328
|
+
"severity_level": dangerous["severity_level"],
|
|
329
|
+
"vulnerability_category": dangerous["vulnerability_category"],
|
|
330
|
+
"functions_that_call_this_api": chain_results,
|
|
331
|
+
"total_call_sites_found": len(xrefs)
|
|
332
|
+
})
|
|
333
|
+
|
|
334
|
+
# Create vulnerability entry for tainted paths
|
|
335
|
+
tainted_callers = [c for c in chain_results if c["user_input_reaches_here"]]
|
|
336
|
+
if tainted_callers:
|
|
337
|
+
vulnerabilities.append({
|
|
338
|
+
"vulnerability_type": dangerous["vulnerability_category"],
|
|
339
|
+
"severity_level": dangerous["severity_level"],
|
|
340
|
+
"dangerous_api_name": dangerous["dangerous_api_name"],
|
|
341
|
+
"api_plt_address": dangerous["plt_address"],
|
|
342
|
+
"risk_description": dangerous["risk_description"],
|
|
343
|
+
"is_exploitable": True,
|
|
344
|
+
"exploitation_reason": f"User input from {tainted_callers[0]['input_source_if_tainted']} reaches {dangerous['dangerous_api_name']}",
|
|
345
|
+
"vulnerable_functions": [c["calling_function_name"] for c in tainted_callers],
|
|
346
|
+
"fix_recommendation": _get_recommendation(dangerous["vulnerability_category"])
|
|
347
|
+
})
|
|
348
|
+
elif chain_results:
|
|
349
|
+
vulnerabilities.append({
|
|
350
|
+
"vulnerability_type": dangerous["vulnerability_category"],
|
|
351
|
+
"severity_level": dangerous["severity_level"],
|
|
352
|
+
"dangerous_api_name": dangerous["dangerous_api_name"],
|
|
353
|
+
"api_plt_address": dangerous["plt_address"],
|
|
354
|
+
"risk_description": dangerous["risk_description"],
|
|
355
|
+
"is_exploitable": "needs_verification",
|
|
356
|
+
"exploitation_reason": f"Dangerous API {dangerous['dangerous_api_name']} called from {len(chain_results)} locations (taint not confirmed)",
|
|
357
|
+
"vulnerable_functions": [c["calling_function_name"] for c in chain_results[:5]],
|
|
358
|
+
"fix_recommendation": _get_recommendation(dangerous["vulnerability_category"])
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
# Phase 4: Generate YARA rules
|
|
362
|
+
yara_rules = []
|
|
363
|
+
if generate_yara and vulnerabilities:
|
|
364
|
+
if ctx:
|
|
365
|
+
await ctx.report_progress(85, 100)
|
|
366
|
+
await ctx.info("📝 Phase 4: Generating YARA detection rules...")
|
|
367
|
+
|
|
368
|
+
yara_rules = _generate_vulnerability_yara_rules(
|
|
369
|
+
vulnerabilities,
|
|
370
|
+
validated_path.name,
|
|
371
|
+
dangerous_calls
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
# Phase 5: Generate summary
|
|
375
|
+
if ctx:
|
|
376
|
+
await ctx.report_progress(95, 100)
|
|
377
|
+
await ctx.info("📋 Generating vulnerability report...")
|
|
378
|
+
|
|
379
|
+
severity_counts = {"critical": 0, "high": 0, "medium": 0, "low": 0}
|
|
380
|
+
for vuln in vulnerabilities:
|
|
381
|
+
sev = vuln.get("severity_level", "low")
|
|
382
|
+
severity_counts[sev] = severity_counts.get(sev, 0) + 1
|
|
383
|
+
|
|
384
|
+
exploitable_count = sum(1 for v in vulnerabilities if v.get("is_exploitable") is True)
|
|
385
|
+
|
|
386
|
+
risk_score = (
|
|
387
|
+
severity_counts["critical"] * 10 +
|
|
388
|
+
severity_counts["high"] * 5 +
|
|
389
|
+
severity_counts["medium"] * 2 +
|
|
390
|
+
severity_counts["low"] * 1
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
if risk_score >= 20:
|
|
394
|
+
risk_level = "CRITICAL"
|
|
395
|
+
elif risk_score >= 10:
|
|
396
|
+
risk_level = "HIGH"
|
|
397
|
+
elif risk_score >= 5:
|
|
398
|
+
risk_level = "MEDIUM"
|
|
399
|
+
else:
|
|
400
|
+
risk_level = "LOW"
|
|
401
|
+
|
|
402
|
+
if ctx:
|
|
403
|
+
await ctx.report_progress(100, 100)
|
|
404
|
+
await ctx.info(f"✅ Analysis complete. Risk Level: {risk_level}")
|
|
405
|
+
|
|
406
|
+
return success({
|
|
407
|
+
"analysis_summary": {
|
|
408
|
+
"total_vulnerabilities_found": len(vulnerabilities),
|
|
409
|
+
"confirmed_exploitable_count": exploitable_count,
|
|
410
|
+
"overall_risk_level": risk_level,
|
|
411
|
+
"risk_score_0_to_100": risk_score,
|
|
412
|
+
"vulnerabilities_by_severity": severity_counts,
|
|
413
|
+
"dangerous_imports_detected": len(dangerous_calls),
|
|
414
|
+
"user_input_sources_found": len(taint_sources),
|
|
415
|
+
},
|
|
416
|
+
"detected_vulnerabilities": vulnerabilities,
|
|
417
|
+
"call_chain_analysis": call_chains,
|
|
418
|
+
"user_input_sources": taint_sources,
|
|
419
|
+
"generated_yara_rules": yara_rules if generate_yara else None,
|
|
420
|
+
"prioritized_recommendations": _generate_recommendations(vulnerabilities),
|
|
421
|
+
})
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def _get_recommendation(category: str) -> str:
|
|
425
|
+
"""Get security recommendation for vulnerability category."""
|
|
426
|
+
recommendations = {
|
|
427
|
+
"buffer_overflow": "Use bounded alternatives (strncpy, snprintf) and validate buffer sizes",
|
|
428
|
+
"format_string": "Never use user input as format string. Use printf(\"%s\", user_input)",
|
|
429
|
+
"command_injection": "Avoid shell commands. Use exec* with explicit arguments, sanitize inputs",
|
|
430
|
+
"memory": "Check allocation sizes for integer overflow, use RAII/smart pointers",
|
|
431
|
+
"file_access": "Validate and sanitize file paths, use allowlists",
|
|
432
|
+
"network_input": "Validate all network input, use bounded reads with size limits",
|
|
433
|
+
"input": "Validate and sanitize all user input before use",
|
|
434
|
+
"code_execution": "Validate DLL/library paths, use signed code where possible",
|
|
435
|
+
"code_injection": "This is typical malware behavior - investigate thoroughly",
|
|
436
|
+
"weak_crypto": "Use cryptographically secure random (getrandom, CryptGenRandom)",
|
|
437
|
+
}
|
|
438
|
+
return recommendations.get(category, "Review and validate all inputs to this function")
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def _generate_recommendations(vulnerabilities: list[dict]) -> list[str]:
|
|
442
|
+
"""Generate prioritized recommendations based on vulnerabilities."""
|
|
443
|
+
recommendations = []
|
|
444
|
+
categories_seen = set()
|
|
445
|
+
|
|
446
|
+
# Sort by severity
|
|
447
|
+
severity_order = {"critical": 0, "high": 1, "medium": 2, "low": 3}
|
|
448
|
+
sorted_vulns = sorted(vulnerabilities, key=lambda v: severity_order.get(v.get("severity", "low"), 3))
|
|
449
|
+
|
|
450
|
+
for vuln in sorted_vulns:
|
|
451
|
+
cat = vuln.get("vulnerability_type", "unknown")
|
|
452
|
+
if cat not in categories_seen:
|
|
453
|
+
categories_seen.add(cat)
|
|
454
|
+
rec = _get_recommendation(cat)
|
|
455
|
+
severity = vuln.get("severity_level", "medium").upper()
|
|
456
|
+
recommendations.append(f"[{severity}] {cat}: {rec}")
|
|
457
|
+
|
|
458
|
+
if not recommendations:
|
|
459
|
+
recommendations.append("No critical vulnerabilities detected. Continue regular security review.")
|
|
460
|
+
|
|
461
|
+
return recommendations[:10] # Limit to top 10
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def _generate_vulnerability_yara_rules(
|
|
465
|
+
vulnerabilities: list[dict],
|
|
466
|
+
filename: str,
|
|
467
|
+
dangerous_calls: list[dict]
|
|
468
|
+
) -> list[str]:
|
|
469
|
+
"""Generate YARA rules for detected vulnerabilities."""
|
|
470
|
+
rules = []
|
|
471
|
+
|
|
472
|
+
# Sanitize filename for rule name
|
|
473
|
+
safe_name = re.sub(r"[^a-zA-Z0-9]", "_", filename)
|
|
474
|
+
|
|
475
|
+
# Group by category
|
|
476
|
+
categories = {}
|
|
477
|
+
for vuln in vulnerabilities:
|
|
478
|
+
cat = vuln.get("vulnerability_type", "unknown")
|
|
479
|
+
if cat not in categories:
|
|
480
|
+
categories[cat] = []
|
|
481
|
+
categories[cat].append(vuln)
|
|
482
|
+
|
|
483
|
+
for category, vulns in categories.items():
|
|
484
|
+
if category in ["buffer_overflow", "command_injection", "code_injection"]:
|
|
485
|
+
apis = list(set(v.get("dangerous_api_name", "") for v in vulns))
|
|
486
|
+
|
|
487
|
+
rule = f'''rule vuln_{safe_name}_{category} {{
|
|
488
|
+
meta:
|
|
489
|
+
description = "Detects {category} vulnerability pattern in {filename}"
|
|
490
|
+
severity = "{vulns[0].get('severity', 'medium')}"
|
|
491
|
+
author = "Vulnerability Hunter (auto-generated)"
|
|
492
|
+
category = "{category}"
|
|
493
|
+
|
|
494
|
+
strings:
|
|
495
|
+
{_generate_yara_strings(apis)}
|
|
496
|
+
|
|
497
|
+
condition:
|
|
498
|
+
uint16(0) == 0x5A4D or uint32(0) == 0x464c457f and any of them
|
|
499
|
+
}}'''
|
|
500
|
+
rules.append(rule)
|
|
501
|
+
|
|
502
|
+
return rules
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def _generate_yara_strings(apis: list[str]) -> str:
|
|
506
|
+
"""Generate YARA string definitions for APIs."""
|
|
507
|
+
strings = []
|
|
508
|
+
for i, api in enumerate(apis[:10]): # Limit to 10
|
|
509
|
+
# ASCII version
|
|
510
|
+
strings.append(f'$api{i}_a = "{api}" ascii')
|
|
511
|
+
# Wide version (Windows)
|
|
512
|
+
strings.append(f'$api{i}_w = "{api}" wide')
|
|
513
|
+
|
|
514
|
+
return "\n ".join(strings)
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
# Note: VulnerabilityHunterPlugin has been removed.
|
|
518
|
+
# The vulnerability_hunter tool is now registered via MalwareToolsPlugin in malware/__init__.py.
|
|
519
|
+
|