iflow-mcp_developermode-korea_reversecore-mcp 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. iflow_mcp_developermode_korea_reversecore_mcp-1.0.0.dist-info/METADATA +543 -0
  2. iflow_mcp_developermode_korea_reversecore_mcp-1.0.0.dist-info/RECORD +79 -0
  3. iflow_mcp_developermode_korea_reversecore_mcp-1.0.0.dist-info/WHEEL +5 -0
  4. iflow_mcp_developermode_korea_reversecore_mcp-1.0.0.dist-info/entry_points.txt +2 -0
  5. iflow_mcp_developermode_korea_reversecore_mcp-1.0.0.dist-info/licenses/LICENSE +21 -0
  6. iflow_mcp_developermode_korea_reversecore_mcp-1.0.0.dist-info/top_level.txt +1 -0
  7. reversecore_mcp/__init__.py +9 -0
  8. reversecore_mcp/core/__init__.py +78 -0
  9. reversecore_mcp/core/audit.py +101 -0
  10. reversecore_mcp/core/binary_cache.py +138 -0
  11. reversecore_mcp/core/command_spec.py +357 -0
  12. reversecore_mcp/core/config.py +432 -0
  13. reversecore_mcp/core/container.py +288 -0
  14. reversecore_mcp/core/decorators.py +152 -0
  15. reversecore_mcp/core/error_formatting.py +93 -0
  16. reversecore_mcp/core/error_handling.py +142 -0
  17. reversecore_mcp/core/evidence.py +229 -0
  18. reversecore_mcp/core/exceptions.py +296 -0
  19. reversecore_mcp/core/execution.py +240 -0
  20. reversecore_mcp/core/ghidra.py +642 -0
  21. reversecore_mcp/core/ghidra_helper.py +481 -0
  22. reversecore_mcp/core/ghidra_manager.py +234 -0
  23. reversecore_mcp/core/json_utils.py +131 -0
  24. reversecore_mcp/core/loader.py +73 -0
  25. reversecore_mcp/core/logging_config.py +206 -0
  26. reversecore_mcp/core/memory.py +721 -0
  27. reversecore_mcp/core/metrics.py +198 -0
  28. reversecore_mcp/core/mitre_mapper.py +365 -0
  29. reversecore_mcp/core/plugin.py +45 -0
  30. reversecore_mcp/core/r2_helpers.py +404 -0
  31. reversecore_mcp/core/r2_pool.py +403 -0
  32. reversecore_mcp/core/report_generator.py +268 -0
  33. reversecore_mcp/core/resilience.py +252 -0
  34. reversecore_mcp/core/resource_manager.py +169 -0
  35. reversecore_mcp/core/result.py +132 -0
  36. reversecore_mcp/core/security.py +213 -0
  37. reversecore_mcp/core/validators.py +238 -0
  38. reversecore_mcp/dashboard/__init__.py +221 -0
  39. reversecore_mcp/prompts/__init__.py +56 -0
  40. reversecore_mcp/prompts/common.py +24 -0
  41. reversecore_mcp/prompts/game.py +280 -0
  42. reversecore_mcp/prompts/malware.py +1219 -0
  43. reversecore_mcp/prompts/report.py +150 -0
  44. reversecore_mcp/prompts/security.py +136 -0
  45. reversecore_mcp/resources.py +329 -0
  46. reversecore_mcp/server.py +727 -0
  47. reversecore_mcp/tools/__init__.py +49 -0
  48. reversecore_mcp/tools/analysis/__init__.py +74 -0
  49. reversecore_mcp/tools/analysis/capa_tools.py +215 -0
  50. reversecore_mcp/tools/analysis/die_tools.py +180 -0
  51. reversecore_mcp/tools/analysis/diff_tools.py +643 -0
  52. reversecore_mcp/tools/analysis/lief_tools.py +272 -0
  53. reversecore_mcp/tools/analysis/signature_tools.py +591 -0
  54. reversecore_mcp/tools/analysis/static_analysis.py +479 -0
  55. reversecore_mcp/tools/common/__init__.py +58 -0
  56. reversecore_mcp/tools/common/file_operations.py +352 -0
  57. reversecore_mcp/tools/common/memory_tools.py +516 -0
  58. reversecore_mcp/tools/common/patch_explainer.py +230 -0
  59. reversecore_mcp/tools/common/server_tools.py +115 -0
  60. reversecore_mcp/tools/ghidra/__init__.py +19 -0
  61. reversecore_mcp/tools/ghidra/decompilation.py +975 -0
  62. reversecore_mcp/tools/ghidra/ghidra_tools.py +1052 -0
  63. reversecore_mcp/tools/malware/__init__.py +61 -0
  64. reversecore_mcp/tools/malware/adaptive_vaccine.py +579 -0
  65. reversecore_mcp/tools/malware/dormant_detector.py +756 -0
  66. reversecore_mcp/tools/malware/ioc_tools.py +228 -0
  67. reversecore_mcp/tools/malware/vulnerability_hunter.py +519 -0
  68. reversecore_mcp/tools/malware/yara_tools.py +214 -0
  69. reversecore_mcp/tools/patch_explainer.py +19 -0
  70. reversecore_mcp/tools/radare2/__init__.py +13 -0
  71. reversecore_mcp/tools/radare2/r2_analysis.py +972 -0
  72. reversecore_mcp/tools/radare2/r2_session.py +376 -0
  73. reversecore_mcp/tools/radare2/radare2_mcp_tools.py +1183 -0
  74. reversecore_mcp/tools/report/__init__.py +4 -0
  75. reversecore_mcp/tools/report/email.py +82 -0
  76. reversecore_mcp/tools/report/report_mcp_tools.py +344 -0
  77. reversecore_mcp/tools/report/report_tools.py +1076 -0
  78. reversecore_mcp/tools/report/session.py +194 -0
  79. reversecore_mcp/tools/report_tools.py +11 -0
@@ -0,0 +1,376 @@
1
+ """
2
+ Radare2 Session Management and Security Validators.
3
+
4
+ This module provides session management and security validation utilities
5
+ for radare2 analysis tools.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import os
11
+ import re
12
+ import uuid
13
+ from datetime import datetime
14
+ from functools import lru_cache
15
+ from typing import Any
16
+
17
+ # Lazy import for r2pipe to allow tests to run without it
18
+ try:
19
+ import r2pipe
20
+
21
+ R2PIPE_AVAILABLE = True
22
+ except ImportError:
23
+ r2pipe = None # type: ignore
24
+ R2PIPE_AVAILABLE = False
25
+
26
+ from reversecore_mcp.core.config import get_config
27
+ from reversecore_mcp.core.exceptions import ValidationError
28
+ from reversecore_mcp.core.logging_config import get_logger
29
+
30
+ logger = get_logger(__name__)
31
+
32
+ # Default configuration
33
+ DEFAULT_TIMEOUT = get_config().default_tool_timeout
34
+ DEFAULT_PAGE_SIZE = 1000
35
+ MAX_PAGE_SIZE = 10000
36
+
37
+ # =============================================================================
38
+ # Security Validators
39
+ # =============================================================================
40
+
41
+ # Pattern for safe identifiers (function names, class names, etc.)
42
+ _SAFE_IDENTIFIER_PATTERN = re.compile(r"^[a-zA-Z_][a-zA-Z0-9_.]*$")
43
+
44
+ # Pattern for safe math expressions (for calculate tool)
45
+ # Allows: hex (0x..), decimal, operators, symbols (sym.xxx), parentheses
46
+ _SAFE_EXPRESSION_PATTERN = re.compile(r"^[a-zA-Z0-9_.\s+\-*/%()[\]]+$")
47
+
48
+ # Dangerous r2 commands that should be blocked
49
+ _BLOCKED_R2_COMMANDS = frozenset(
50
+ {
51
+ "!", # Shell escape
52
+ "#!", # Alternative shell
53
+ "=!", # Remote shell
54
+ "=h", # HTTP server
55
+ "=H", # HTTP server (alt)
56
+ "o+", # Open for write
57
+ "w", # Write
58
+ "wa", # Write assembly
59
+ "wb", # Write bytes
60
+ "wc", # Write comment (file modification)
61
+ "wf", # Write file
62
+ "wo", # Write operations
63
+ "wx", # Write hex
64
+ "wv", # Write value
65
+ "wd", # Write dword
66
+ "Ps", # Project save (can overwrite)
67
+ "rm", # Remove (radare2 built-in)
68
+ "r2pm", # Package manager
69
+ "L", # Load plugin (potential code exec)
70
+ ".", # Interpret script
71
+ }
72
+ )
73
+
74
+
75
+ def _validate_identifier(value: str, param_name: str) -> None:
76
+ """
77
+ Validate that a value is a safe identifier (no injection).
78
+
79
+ Args:
80
+ value: The identifier to validate
81
+ param_name: Name of parameter for error messages
82
+
83
+ Raises:
84
+ ValidationError: If identifier is invalid
85
+ """
86
+ if not value:
87
+ raise ValidationError(f"{param_name} cannot be empty")
88
+
89
+ if not _SAFE_IDENTIFIER_PATTERN.match(value):
90
+ raise ValidationError(
91
+ f"{param_name} must contain only alphanumeric characters, "
92
+ "underscores, and dots (starting with letter or underscore)"
93
+ )
94
+
95
+
96
+ def _validate_expression(expression: str) -> None:
97
+ """
98
+ Validate math expression for calculate tool.
99
+
100
+ Args:
101
+ expression: Math expression to validate
102
+
103
+ Raises:
104
+ ValidationError: If expression contains dangerous characters
105
+ """
106
+ if not expression:
107
+ raise ValidationError("expression cannot be empty")
108
+
109
+ if not _SAFE_EXPRESSION_PATTERN.match(expression):
110
+ raise ValidationError(
111
+ "expression contains invalid characters. "
112
+ "Only alphanumeric, operators (+,-,*,/,%), parentheses, and symbols allowed."
113
+ )
114
+
115
+ # Additional check for shell escape attempts
116
+ if any(c in expression for c in ["`", "$", ";", "|", "&", ">", "<", "~"]):
117
+ raise ValidationError("expression contains forbidden shell characters")
118
+
119
+
120
+ def _validate_r2_command(command: str) -> None:
121
+ """
122
+ Validate radare2 command for safety.
123
+
124
+ Args:
125
+ command: r2 command to validate
126
+
127
+ Raises:
128
+ ValidationError: If command is blocked or dangerous
129
+ """
130
+ if not command:
131
+ raise ValidationError("command cannot be empty")
132
+
133
+ # Check for shell escape
134
+ cmd_start = command.strip().split()[0] if command.strip() else ""
135
+
136
+ # Block dangerous commands
137
+ for blocked in _BLOCKED_R2_COMMANDS:
138
+ if cmd_start.startswith(blocked):
139
+ raise ValidationError(
140
+ f"Command '{blocked}' is blocked for security reasons. "
141
+ "Only analysis commands are allowed."
142
+ )
143
+
144
+ # Block shell metacharacters in command
145
+ # SECURITY: `;` is critical - it allows command chaining in radare2
146
+ # Example attack: "px 10; !rm -rf /" would execute both commands
147
+ if any(c in command for c in ["`", "$", ";", "|", "&", ">", "<", "~", "\n", "\r"]):
148
+ raise ValidationError("Command contains forbidden shell metacharacters")
149
+
150
+
151
+ def _sanitize_for_r2_cmd(value: str) -> str:
152
+ """
153
+ Sanitize a value for safe use in r2 commands.
154
+
155
+ Removes/escapes dangerous characters while preserving functionality.
156
+
157
+ Args:
158
+ value: Value to sanitize
159
+
160
+ Returns:
161
+ Sanitized value safe for r2 commands
162
+ """
163
+ if not value:
164
+ return ""
165
+
166
+ # Remove shell metacharacters
167
+ dangerous_chars = "`$;|&><\n\r\t\\"
168
+ sanitized = value
169
+ for char in dangerous_chars:
170
+ sanitized = sanitized.replace(char, "")
171
+
172
+ # Remove quotes that could break command parsing
173
+ sanitized = sanitized.replace('"', "").replace("'", "")
174
+
175
+ return sanitized
176
+
177
+
178
+ class R2Session:
179
+ """
180
+ Manages a radare2 session with enhanced state tracking and diagnostics.
181
+
182
+ Includes asyncio.Lock for thread-safe command execution in async contexts.
183
+ """
184
+
185
+ def __init__(self, file_path: str | None = None):
186
+ self.session_id = str(uuid.uuid4())
187
+ self.file_path = file_path
188
+ self._r2: Any = None # r2pipe.open_sync when available
189
+ self._analyzed = False
190
+ self.created_at = datetime.now()
191
+ self.status = "initialized" # initialized, active, error, closed
192
+ self.last_error = None
193
+ self.retry_count = 0
194
+ # Async lock for safe concurrent command execution
195
+ import asyncio
196
+
197
+ self._command_lock = asyncio.Lock()
198
+
199
+ def open(self, file_path: str) -> bool:
200
+ """Open a binary file with radare2."""
201
+ if not R2PIPE_AVAILABLE:
202
+ self.status = "error"
203
+ self.last_error = "r2pipe module not installed"
204
+ logger.error("r2pipe module not available - install with: pip install r2pipe")
205
+ return False
206
+
207
+ try:
208
+ self.close()
209
+ # Verify file exists strictly before passing to r2
210
+ if not os.path.exists(file_path):
211
+ raise FileNotFoundError(f"File not found: {file_path}")
212
+
213
+ self._r2 = r2pipe.open(file_path)
214
+ if not self._r2:
215
+ raise RuntimeError("r2pipe.open returned None")
216
+
217
+ self.file_path = file_path
218
+ self.status = "active"
219
+ return True
220
+ except Exception as e:
221
+ self.status = "error"
222
+ self.last_error = str(e)
223
+ logger.error(f"Failed to open file {file_path}: {e}")
224
+ return False
225
+
226
+ def close(self) -> None:
227
+ """Close the current radare2 session."""
228
+ if self._r2:
229
+ try:
230
+ self._r2.quit()
231
+ except Exception:
232
+ pass
233
+ self._r2 = None
234
+ self.status = "closed"
235
+ self._analyzed = False
236
+
237
+ def cmd(self, command: str) -> str:
238
+ """Execute a radare2 command and return the output."""
239
+ if not self._r2:
240
+ return ""
241
+ try:
242
+ result = self._r2.cmd(command)
243
+ return result if result else ""
244
+ except Exception as e:
245
+ self.last_error = str(e)
246
+ logger.error(f"R2 command failed: {e}")
247
+ return f"Error: {e}"
248
+
249
+ def cmdj(self, command: str) -> Any:
250
+ """Execute a radare2 command and return JSON output."""
251
+ if not self._r2:
252
+ return None
253
+ try:
254
+ return self._r2.cmdj(command)
255
+ except Exception as e:
256
+ self.last_error = str(e)
257
+ logger.error(f"R2 JSON command failed: {e}")
258
+ return None
259
+
260
+ async def safe_cmd(self, command: str) -> str:
261
+ """
262
+ Execute a radare2 command with session-level locking.
263
+
264
+ This method is safe for concurrent async access and prevents
265
+ command interleaving when multiple coroutines use the same session.
266
+ """
267
+ import asyncio
268
+
269
+ async with self._command_lock:
270
+ return await asyncio.to_thread(self.cmd, command)
271
+
272
+ async def safe_cmdj(self, command: str) -> Any:
273
+ """
274
+ Execute a radare2 JSON command with session-level locking.
275
+
276
+ This method is safe for concurrent async access.
277
+ """
278
+ import asyncio
279
+
280
+ async with self._command_lock:
281
+ return await asyncio.to_thread(self.cmdj, command)
282
+
283
+ def analyze(self, level: int = 2) -> str:
284
+ """Run analysis with specified depth level."""
285
+ if self._analyzed and level <= 2:
286
+ return "Already analyzed"
287
+
288
+ analysis_cmds = {
289
+ 0: "aa", # Basic analysis
290
+ 1: "aaa", # Auto-analysis
291
+ 2: "aaaa", # Experimental analysis
292
+ 3: "aaaaa", # Deep analysis
293
+ 4: "aaaaaa", # Very deep analysis
294
+ }
295
+ cmd = analysis_cmds.get(level, "aaa")
296
+ result = self.cmd(cmd)
297
+ self._analyzed = True
298
+ return result
299
+
300
+ @property
301
+ def is_open(self) -> bool:
302
+ return self._r2 is not None and self.status == "active"
303
+
304
+
305
+ # =============================================================================
306
+ # Utility Functions
307
+ # =============================================================================
308
+
309
+
310
+ @lru_cache(maxsize=64)
311
+ def _compile_regex_cached(pattern: str) -> re.Pattern | None:
312
+ """Compile and cache regex pattern."""
313
+ try:
314
+ return re.compile(pattern)
315
+ except re.error:
316
+ return None
317
+
318
+
319
+ def _filter_lines_by_regex(text: str, pattern: str) -> str:
320
+ """Filter lines matching a regex pattern."""
321
+ if not pattern or not text:
322
+ return text
323
+
324
+ # Limit pattern length to prevent ReDoS
325
+ if len(pattern) > 500:
326
+ return "Error: Regex pattern too long (max 500 chars)"
327
+
328
+ regex = _compile_regex_cached(pattern)
329
+ if regex is None:
330
+ return f"Invalid regex pattern: {pattern}"
331
+
332
+ lines = text.split("\n")
333
+ filtered = [line for line in lines if regex.search(line)]
334
+ return "\n".join(filtered)
335
+
336
+
337
+ def _filter_named_functions(text: str) -> str:
338
+ """Filter out functions with numeric suffixes (e.g., sym.func.1000016c8)."""
339
+ if not text:
340
+ return text
341
+ lines = text.split("\n")
342
+ filtered = []
343
+ for line in lines:
344
+ # Check if last part after dot is a number (hex)
345
+ parts = line.split(".")
346
+ if parts:
347
+ last_part = parts[-1].split()[0] if parts[-1] else ""
348
+ # Skip if last part looks like a hex address
349
+ if last_part and last_part[0].isdigit():
350
+ continue
351
+ filtered.append(line)
352
+ return "\n".join(filtered)
353
+
354
+
355
+ def _paginate_text(text: str, cursor: str | None, page_size: int) -> tuple[str, bool, str | None]:
356
+ """
357
+ Paginate text by lines.
358
+
359
+ Returns: (paginated_text, has_more, next_cursor)
360
+ """
361
+ if not text:
362
+ return "", False, None
363
+
364
+ lines = text.split("\n")
365
+ start_index = int(cursor) if cursor and cursor.isdigit() else 0
366
+
367
+ if start_index < 0:
368
+ start_index = 0
369
+
370
+ end_index = start_index + page_size
371
+ paginated_lines = lines[start_index:end_index]
372
+
373
+ has_more = end_index < len(lines)
374
+ next_cursor = str(end_index) if has_more else None
375
+
376
+ return "\n".join(paginated_lines), has_more, next_cursor