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,240 @@
1
+ """
2
+ Safe subprocess execution with streaming and output limits.
3
+
4
+ This module provides functions to execute subprocess commands safely with:
5
+ - Streaming output to prevent OOM on large outputs
6
+ - Configurable output size limits
7
+ - Timeout handling
8
+ - Proper error handling and reporting
9
+ """
10
+
11
+ import asyncio
12
+ import subprocess
13
+ import threading
14
+ from collections.abc import Coroutine
15
+ from typing import Any
16
+
17
+ from reversecore_mcp.core.logging_config import get_logger
18
+
19
+ logger = get_logger(__name__)
20
+
21
+ from reversecore_mcp.core.exceptions import (
22
+ ExecutionTimeoutError,
23
+ ToolNotFoundError,
24
+ )
25
+
26
+
27
+ class _BackgroundLoopRunner:
28
+ """Run asyncio coroutines on a dedicated background event loop."""
29
+
30
+ def __init__(self) -> None:
31
+ self._loop = asyncio.new_event_loop()
32
+ self._thread = threading.Thread(
33
+ target=self._run_loop,
34
+ name="ReversecoreAsyncLoop",
35
+ daemon=True,
36
+ )
37
+ self._thread.start()
38
+
39
+ def _run_loop(self) -> None:
40
+ asyncio.set_event_loop(self._loop)
41
+ self._loop.run_forever()
42
+
43
+ def run(self, coro: Coroutine[Any, Any, tuple[str, int]]) -> tuple[str, int]:
44
+ future = asyncio.run_coroutine_threadsafe(coro, self._loop)
45
+ return future.result()
46
+
47
+
48
+ _BACKGROUND_LOOP_LOCK = threading.Lock()
49
+ _BACKGROUND_LOOP_RUNNER: _BackgroundLoopRunner | None = None
50
+
51
+
52
+ def _get_background_runner() -> _BackgroundLoopRunner:
53
+ global _BACKGROUND_LOOP_RUNNER
54
+ with _BACKGROUND_LOOP_LOCK:
55
+ if _BACKGROUND_LOOP_RUNNER is None:
56
+ _BACKGROUND_LOOP_RUNNER = _BackgroundLoopRunner()
57
+ return _BACKGROUND_LOOP_RUNNER
58
+
59
+
60
+ async def execute_subprocess_async(
61
+ cmd: list[str],
62
+ max_output_size: int = 10_000_000, # 10 MB default
63
+ timeout: int = 300, # 5 minutes default
64
+ encoding: str = "utf-8",
65
+ errors: str = "replace",
66
+ ) -> tuple[str, int]:
67
+ """
68
+ Execute a subprocess command asynchronously with streaming output and size limits.
69
+
70
+ This function uses asyncio.create_subprocess_exec to stream output in chunks,
71
+ preventing OOM issues when processing large files and avoiding CPU polling.
72
+ Output is truncated if it exceeds max_output_size.
73
+
74
+ Args:
75
+ cmd: Command and arguments as a list (e.g., ["r2", "-q", "-c", "pdf @ main", "file.exe"])
76
+ max_output_size: Maximum output size in bytes (default: 10MB)
77
+ timeout: Maximum execution time in seconds (default: 300)
78
+ encoding: Text encoding for output (default: "utf-8")
79
+ errors: Error handling for encoding (default: "replace")
80
+
81
+ Returns:
82
+ Tuple of (output_text, bytes_read)
83
+ - output_text: The captured output (truncated if limit exceeded)
84
+ - bytes_read: Total bytes read (may exceed max_output_size if truncated)
85
+
86
+ Raises:
87
+ ToolNotFoundError: If the command executable is not found
88
+ ExecutionTimeoutError: If the command exceeds the timeout
89
+ subprocess.CalledProcessError: If the command returns non-zero exit code
90
+ """
91
+ try:
92
+ # Start the process with piped stdout/stderr
93
+ process = await asyncio.create_subprocess_exec(
94
+ *cmd,
95
+ stdout=asyncio.subprocess.PIPE,
96
+ stderr=asyncio.subprocess.PIPE,
97
+ )
98
+
99
+ # Track PID for zombie cleanup in case of abnormal termination
100
+ from reversecore_mcp.core.resource_manager import resource_manager
101
+ resource_manager.track_pid(process.pid)
102
+
103
+ except FileNotFoundError:
104
+ # Extract command name from cmd list
105
+ tool_name = cmd[0] if cmd else "unknown"
106
+ raise ToolNotFoundError(tool_name)
107
+
108
+ # Read output in chunks
109
+ output_chunks = []
110
+ stderr_chunks = []
111
+ bytes_read = 0
112
+
113
+ try:
114
+ # Read output in chunks with timeout checking
115
+ async def read_stream():
116
+ """Read stdout in chunks until EOF or size limit."""
117
+ nonlocal bytes_read
118
+ chunk_size = 8192 # 8KB chunks
119
+
120
+ while True:
121
+ chunk = await process.stdout.read(chunk_size)
122
+ if not chunk:
123
+ break
124
+
125
+ # Decode chunk
126
+ decoded_chunk = chunk.decode(encoding, errors=errors)
127
+ chunk_bytes = len(chunk)
128
+ bytes_read += chunk_bytes
129
+
130
+ # Only append if we haven't exceeded the limit
131
+ if bytes_read <= max_output_size:
132
+ output_chunks.append(decoded_chunk)
133
+
134
+ # Wait for process to complete with timeout
135
+ # Wait for process to complete with timeout
136
+ try:
137
+ await asyncio.wait_for(read_stream(), timeout=timeout)
138
+ await asyncio.wait_for(process.wait(), timeout=1.0)
139
+ except asyncio.TimeoutError:
140
+ logger.warning(f"Command timed out after {timeout}s: {' '.join(cmd)}")
141
+ raise ExecutionTimeoutError(timeout)
142
+ finally:
143
+ # Critical: Ensure process is terminated to prevent zombies
144
+ if process.returncode is None:
145
+ try:
146
+ process.kill()
147
+ # Wait for process to die to reap the zombie
148
+ # We can't await indefinitely here, but usually kill is fast
149
+ try:
150
+ await asyncio.wait_for(process.wait(), timeout=2.0)
151
+ except asyncio.TimeoutError:
152
+ logger.error(f"Process {process.pid} refused to die after kill")
153
+ except Exception as e:
154
+ logger.error(f"Failed to kill process {process.pid}: {e}")
155
+
156
+ # Read any remaining stderr
157
+ stderr_data = await process.stderr.read()
158
+ if stderr_data:
159
+ stderr_chunks.append(stderr_data.decode(encoding, errors=errors))
160
+
161
+ # Combine output chunks
162
+ output_text = "".join(output_chunks)
163
+
164
+ # Check if output was truncated
165
+ if bytes_read > max_output_size:
166
+ truncation_warning = (
167
+ f"\n\n[WARNING: Output truncated at {max_output_size} bytes. "
168
+ f"Total output size: {bytes_read} bytes]"
169
+ )
170
+ output_text += truncation_warning
171
+
172
+ # If process failed, raise CalledProcessError with stderr
173
+ if process.returncode != 0:
174
+ stderr_text = "".join(stderr_chunks)
175
+ raise subprocess.CalledProcessError(
176
+ process.returncode, cmd, output=output_text, stderr=stderr_text
177
+ )
178
+
179
+ return output_text, bytes_read
180
+
181
+ except ImportError:
182
+ # Re-raise import errors (e.g. from missing dependencies)
183
+ raise
184
+ except Exception as e:
185
+ if not isinstance(e, (ToolNotFoundError, ExecutionTimeoutError, subprocess.CalledProcessError)):
186
+ logger.error(f"Command execution failed: {e}")
187
+ raise
188
+
189
+
190
+ def execute_subprocess_streaming(
191
+ cmd: list[str],
192
+ max_output_size: int = 10_000_000, # 10 MB default
193
+ timeout: int = 300, # 5 minutes default
194
+ encoding: str = "utf-8",
195
+ errors: str = "replace",
196
+ ) -> tuple[str, int]:
197
+ """
198
+ Execute a subprocess command with streaming output and size limits.
199
+
200
+ This is a synchronous wrapper around execute_subprocess_async that provides
201
+ backward compatibility. It uses asyncio.run() to execute the async version.
202
+
203
+ This function uses asyncio to stream output in chunks, preventing
204
+ OOM issues when processing large files and avoiding CPU polling.
205
+ Output is truncated if it exceeds max_output_size.
206
+
207
+ Args:
208
+ cmd: Command and arguments as a list (e.g., ["r2", "-q", "-c", "pdf @ main", "file.exe"])
209
+ max_output_size: Maximum output size in bytes (default: 10MB)
210
+ timeout: Maximum execution time in seconds (default: 300)
211
+ encoding: Text encoding for output (default: "utf-8")
212
+ errors: Error handling for encoding (default: "replace")
213
+
214
+ Returns:
215
+ Tuple of (output_text, bytes_read)
216
+ - output_text: The captured output (truncated if limit exceeded)
217
+ - bytes_read: Total bytes read (may exceed max_output_size if truncated)
218
+
219
+ Raises:
220
+ ToolNotFoundError: If the command executable is not found
221
+ ExecutionTimeoutError: If the command exceeds the timeout
222
+ subprocess.CalledProcessError: If the command returns non-zero exit code
223
+ """
224
+ coro = execute_subprocess_async(
225
+ cmd,
226
+ max_output_size=max_output_size,
227
+ timeout=timeout,
228
+ encoding=encoding,
229
+ errors=errors,
230
+ )
231
+
232
+ try:
233
+ running_loop = asyncio.get_running_loop()
234
+ except RuntimeError:
235
+ running_loop = None
236
+
237
+ if running_loop and running_loop.is_running():
238
+ return _get_background_runner().run(coro)
239
+
240
+ return asyncio.run(coro)