iotsploit-core 0.0.6__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.
- iotsploit_core/__init__.py +7 -0
- iotsploit_core/context.py +27 -0
- iotsploit_core/core/__init__.py +0 -0
- iotsploit_core/core/base_plugin.py +290 -0
- iotsploit_core/core/device_config.py +105 -0
- iotsploit_core/core/device_manager.py +942 -0
- iotsploit_core/core/device_registry.py +118 -0
- iotsploit_core/core/device_scanner.py +74 -0
- iotsploit_core/core/device_spec.py +105 -0
- iotsploit_core/core/device_store.py +86 -0
- iotsploit_core/core/execution_backend.py +464 -0
- iotsploit_core/core/execution_queue.py +456 -0
- iotsploit_core/core/exploit_manager.py +728 -0
- iotsploit_core/core/exploit_spec.py +73 -0
- iotsploit_core/core/stream_manager.py +157 -0
- iotsploit_core/core/tool_config.py +430 -0
- iotsploit_core/core/tool_manager.py +1166 -0
- iotsploit_core/core/tool_service.py +936 -0
- iotsploit_core/domain/__init__.py +3 -0
- iotsploit_core/domain/device.py +70 -0
- iotsploit_core/domain/execution_plan.py +29 -0
- iotsploit_core/domain/plugin.py +17 -0
- iotsploit_core/domain/stream.py +63 -0
- iotsploit_core/domain/target.py +243 -0
- iotsploit_core/platforms/__init__.py +19 -0
- iotsploit_core/platforms/consts.py +25 -0
- iotsploit_core/ports/__init__.py +6 -0
- iotsploit_core/ports/driver_state_repo.py +15 -0
- iotsploit_core/ports/plugin_repo.py +26 -0
- iotsploit_core/ports/stream_backend.py +28 -0
- iotsploit_core/ports/task_runner.py +22 -0
- iotsploit_core/ports/wifi_backend.py +148 -0
- iotsploit_core/py.typed +2 -0
- iotsploit_core/utils/__init__.py +53 -0
- iotsploit_core/utils/exceptions.py +152 -0
- iotsploit_core/utils/helpers.py +65 -0
- iotsploit_core/utils/iots_logger.py +164 -0
- iotsploit_core/utils/result.py +41 -0
- iotsploit_core-0.0.6.dist-info/METADATA +117 -0
- iotsploit_core-0.0.6.dist-info/RECORD +41 -0
- iotsploit_core-0.0.6.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Execution Backend Abstraction for IoTSploit Tool Manager
|
|
4
|
+
========================================================
|
|
5
|
+
|
|
6
|
+
Provides multiple execution backends for tool execution:
|
|
7
|
+
- Subprocess Backend (default)
|
|
8
|
+
- Invoke Backend (task automation)
|
|
9
|
+
- sh Backend (pythonic shell)
|
|
10
|
+
- Sarge Backend (subprocess wrapper)
|
|
11
|
+
|
|
12
|
+
This allows plugins to choose the best execution method for their needs.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
import sys
|
|
17
|
+
import time
|
|
18
|
+
import logging
|
|
19
|
+
import subprocess
|
|
20
|
+
from abc import ABC, abstractmethod
|
|
21
|
+
from typing import Dict, List, Optional, Any, Union
|
|
22
|
+
from dataclasses import dataclass
|
|
23
|
+
from enum import Enum
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class ExecutionResult:
|
|
29
|
+
"""Standardized execution result across all backends"""
|
|
30
|
+
success: bool
|
|
31
|
+
return_code: int
|
|
32
|
+
stdout: str
|
|
33
|
+
stderr: str
|
|
34
|
+
execution_time: float
|
|
35
|
+
command: str
|
|
36
|
+
tool_path: str
|
|
37
|
+
backend: str
|
|
38
|
+
metadata: Dict[str, Any] = None
|
|
39
|
+
|
|
40
|
+
class ExecutionBackend(ABC):
|
|
41
|
+
"""Abstract base class for execution backends"""
|
|
42
|
+
|
|
43
|
+
def __init__(self, name: str):
|
|
44
|
+
self.name = name
|
|
45
|
+
self.logger = logging.getLogger(f"execution.{name}")
|
|
46
|
+
|
|
47
|
+
@abstractmethod
|
|
48
|
+
def execute(self, tool_path: str, args: List[str],
|
|
49
|
+
working_dir: Optional[str] = None,
|
|
50
|
+
env: Optional[Dict[str, str]] = None,
|
|
51
|
+
timeout: Optional[int] = None) -> ExecutionResult:
|
|
52
|
+
"""Execute a tool with given arguments"""
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
@abstractmethod
|
|
56
|
+
def is_available(self) -> bool:
|
|
57
|
+
"""Check if this backend is available"""
|
|
58
|
+
pass
|
|
59
|
+
|
|
60
|
+
def _prepare_environment(self, env: Optional[Dict[str, str]] = None) -> Dict[str, str]:
|
|
61
|
+
"""Prepare execution environment"""
|
|
62
|
+
exec_env = os.environ.copy()
|
|
63
|
+
if env:
|
|
64
|
+
exec_env.update(env)
|
|
65
|
+
return exec_env
|
|
66
|
+
|
|
67
|
+
class SubprocessBackend(ExecutionBackend):
|
|
68
|
+
"""Default subprocess-based execution backend"""
|
|
69
|
+
|
|
70
|
+
def __init__(self):
|
|
71
|
+
super().__init__("subprocess")
|
|
72
|
+
|
|
73
|
+
def execute(self, tool_path: str, args: List[str],
|
|
74
|
+
working_dir: Optional[str] = None,
|
|
75
|
+
env: Optional[Dict[str, str]] = None,
|
|
76
|
+
timeout: Optional[int] = None) -> ExecutionResult:
|
|
77
|
+
"""Execute using subprocess module"""
|
|
78
|
+
|
|
79
|
+
command = [tool_path] + args
|
|
80
|
+
command_str = ' '.join(command)
|
|
81
|
+
exec_env = self._prepare_environment(env)
|
|
82
|
+
|
|
83
|
+
self.logger.debug(f"Executing: {command_str}")
|
|
84
|
+
start_time = time.time()
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
result = subprocess.run(
|
|
88
|
+
command,
|
|
89
|
+
cwd=working_dir,
|
|
90
|
+
env=exec_env,
|
|
91
|
+
capture_output=True,
|
|
92
|
+
text=True,
|
|
93
|
+
timeout=timeout
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
execution_time = time.time() - start_time
|
|
97
|
+
|
|
98
|
+
return ExecutionResult(
|
|
99
|
+
success=result.returncode == 0,
|
|
100
|
+
return_code=result.returncode,
|
|
101
|
+
stdout=result.stdout,
|
|
102
|
+
stderr=result.stderr,
|
|
103
|
+
execution_time=execution_time,
|
|
104
|
+
command=command_str,
|
|
105
|
+
tool_path=tool_path,
|
|
106
|
+
backend=self.name
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
except subprocess.TimeoutExpired as e:
|
|
110
|
+
execution_time = time.time() - start_time
|
|
111
|
+
return ExecutionResult(
|
|
112
|
+
success=False,
|
|
113
|
+
return_code=-1,
|
|
114
|
+
stdout=e.stdout.decode() if e.stdout else "",
|
|
115
|
+
stderr=f"Command timed out after {timeout} seconds",
|
|
116
|
+
execution_time=execution_time,
|
|
117
|
+
command=command_str,
|
|
118
|
+
tool_path=tool_path,
|
|
119
|
+
backend=self.name
|
|
120
|
+
)
|
|
121
|
+
except Exception as e:
|
|
122
|
+
execution_time = time.time() - start_time
|
|
123
|
+
return ExecutionResult(
|
|
124
|
+
success=False,
|
|
125
|
+
return_code=-1,
|
|
126
|
+
stdout="",
|
|
127
|
+
stderr=str(e),
|
|
128
|
+
execution_time=execution_time,
|
|
129
|
+
command=command_str,
|
|
130
|
+
tool_path=tool_path,
|
|
131
|
+
backend=self.name
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
def is_available(self) -> bool:
|
|
135
|
+
"""Subprocess is always available"""
|
|
136
|
+
return True
|
|
137
|
+
|
|
138
|
+
class InvokeBackend(ExecutionBackend):
|
|
139
|
+
"""Invoke-based execution backend for task automation"""
|
|
140
|
+
|
|
141
|
+
def __init__(self):
|
|
142
|
+
super().__init__("invoke")
|
|
143
|
+
self._invoke_available = self._check_invoke()
|
|
144
|
+
|
|
145
|
+
def _check_invoke(self) -> bool:
|
|
146
|
+
"""Check if invoke is available"""
|
|
147
|
+
try:
|
|
148
|
+
import invoke
|
|
149
|
+
return True
|
|
150
|
+
except ImportError:
|
|
151
|
+
return False
|
|
152
|
+
|
|
153
|
+
def execute(self, tool_path: str, args: List[str],
|
|
154
|
+
working_dir: Optional[str] = None,
|
|
155
|
+
env: Optional[Dict[str, str]] = None,
|
|
156
|
+
timeout: Optional[int] = None) -> ExecutionResult:
|
|
157
|
+
"""Execute using invoke"""
|
|
158
|
+
|
|
159
|
+
if not self._invoke_available:
|
|
160
|
+
raise RuntimeError("Invoke backend not available")
|
|
161
|
+
|
|
162
|
+
import invoke
|
|
163
|
+
from invoke.exceptions import CommandTimedOut, UnexpectedExit
|
|
164
|
+
|
|
165
|
+
command_str = f"{tool_path} {' '.join(args)}"
|
|
166
|
+
exec_env = self._prepare_environment(env)
|
|
167
|
+
|
|
168
|
+
self.logger.debug(f"Executing with invoke: {command_str}")
|
|
169
|
+
start_time = time.time()
|
|
170
|
+
|
|
171
|
+
try:
|
|
172
|
+
# Create context with environment
|
|
173
|
+
ctx = invoke.Context()
|
|
174
|
+
if working_dir:
|
|
175
|
+
ctx.cd(working_dir)
|
|
176
|
+
|
|
177
|
+
# Execute command
|
|
178
|
+
result = ctx.run(
|
|
179
|
+
command_str,
|
|
180
|
+
env=exec_env,
|
|
181
|
+
hide=True,
|
|
182
|
+
warn=True,
|
|
183
|
+
timeout=timeout
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
execution_time = time.time() - start_time
|
|
187
|
+
|
|
188
|
+
return ExecutionResult(
|
|
189
|
+
success=result.ok,
|
|
190
|
+
return_code=result.return_code,
|
|
191
|
+
stdout=result.stdout,
|
|
192
|
+
stderr=result.stderr,
|
|
193
|
+
execution_time=execution_time,
|
|
194
|
+
command=command_str,
|
|
195
|
+
tool_path=tool_path,
|
|
196
|
+
backend=self.name,
|
|
197
|
+
metadata={'invoke_result': result}
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
except CommandTimedOut as e:
|
|
201
|
+
execution_time = time.time() - start_time
|
|
202
|
+
return ExecutionResult(
|
|
203
|
+
success=False,
|
|
204
|
+
return_code=-1,
|
|
205
|
+
stdout=e.result.stdout if e.result else "",
|
|
206
|
+
stderr=f"Command timed out after {timeout} seconds",
|
|
207
|
+
execution_time=execution_time,
|
|
208
|
+
command=command_str,
|
|
209
|
+
tool_path=tool_path,
|
|
210
|
+
backend=self.name
|
|
211
|
+
)
|
|
212
|
+
except Exception as e:
|
|
213
|
+
execution_time = time.time() - start_time
|
|
214
|
+
return ExecutionResult(
|
|
215
|
+
success=False,
|
|
216
|
+
return_code=-1,
|
|
217
|
+
stdout="",
|
|
218
|
+
stderr=str(e),
|
|
219
|
+
execution_time=execution_time,
|
|
220
|
+
command=command_str,
|
|
221
|
+
tool_path=tool_path,
|
|
222
|
+
backend=self.name
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
def is_available(self) -> bool:
|
|
226
|
+
"""Check if invoke is available"""
|
|
227
|
+
return self._invoke_available
|
|
228
|
+
|
|
229
|
+
class ShBackend(ExecutionBackend):
|
|
230
|
+
"""sh-based execution backend for pythonic shell operations"""
|
|
231
|
+
|
|
232
|
+
def __init__(self):
|
|
233
|
+
super().__init__("sh")
|
|
234
|
+
self._sh_available = self._check_sh()
|
|
235
|
+
|
|
236
|
+
def _check_sh(self) -> bool:
|
|
237
|
+
"""Check if sh is available"""
|
|
238
|
+
try:
|
|
239
|
+
import sh
|
|
240
|
+
return True
|
|
241
|
+
except ImportError:
|
|
242
|
+
return False
|
|
243
|
+
|
|
244
|
+
def execute(self, tool_path: str, args: List[str],
|
|
245
|
+
working_dir: Optional[str] = None,
|
|
246
|
+
env: Optional[Dict[str, str]] = None,
|
|
247
|
+
timeout: Optional[int] = None) -> ExecutionResult:
|
|
248
|
+
"""Execute using sh"""
|
|
249
|
+
|
|
250
|
+
if not self._sh_available:
|
|
251
|
+
raise RuntimeError("sh backend not available")
|
|
252
|
+
|
|
253
|
+
import sh
|
|
254
|
+
from sh import CommandNotFound, TimeoutException
|
|
255
|
+
|
|
256
|
+
command_str = f"{tool_path} {' '.join(args)}"
|
|
257
|
+
exec_env = self._prepare_environment(env)
|
|
258
|
+
|
|
259
|
+
self.logger.debug(f"Executing with sh: {command_str}")
|
|
260
|
+
start_time = time.time()
|
|
261
|
+
|
|
262
|
+
try:
|
|
263
|
+
# Get the command
|
|
264
|
+
tool_name = os.path.basename(tool_path)
|
|
265
|
+
if hasattr(sh, tool_name):
|
|
266
|
+
cmd = getattr(sh, tool_name)
|
|
267
|
+
else:
|
|
268
|
+
cmd = sh.Command(tool_path)
|
|
269
|
+
|
|
270
|
+
# Execute with sh
|
|
271
|
+
result = cmd(
|
|
272
|
+
*args,
|
|
273
|
+
_cwd=working_dir,
|
|
274
|
+
_env=exec_env,
|
|
275
|
+
_timeout=timeout,
|
|
276
|
+
_return_cmd=True
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
execution_time = time.time() - start_time
|
|
280
|
+
|
|
281
|
+
return ExecutionResult(
|
|
282
|
+
success=result.exit_code == 0,
|
|
283
|
+
return_code=result.exit_code,
|
|
284
|
+
stdout=str(result.stdout),
|
|
285
|
+
stderr=str(result.stderr),
|
|
286
|
+
execution_time=execution_time,
|
|
287
|
+
command=command_str,
|
|
288
|
+
tool_path=tool_path,
|
|
289
|
+
backend=self.name,
|
|
290
|
+
metadata={'sh_result': result}
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
except TimeoutException as e:
|
|
294
|
+
execution_time = time.time() - start_time
|
|
295
|
+
return ExecutionResult(
|
|
296
|
+
success=False,
|
|
297
|
+
return_code=-1,
|
|
298
|
+
stdout="",
|
|
299
|
+
stderr=f"Command timed out after {timeout} seconds",
|
|
300
|
+
execution_time=execution_time,
|
|
301
|
+
command=command_str,
|
|
302
|
+
tool_path=tool_path,
|
|
303
|
+
backend=self.name
|
|
304
|
+
)
|
|
305
|
+
except Exception as e:
|
|
306
|
+
execution_time = time.time() - start_time
|
|
307
|
+
return ExecutionResult(
|
|
308
|
+
success=False,
|
|
309
|
+
return_code=-1,
|
|
310
|
+
stdout="",
|
|
311
|
+
stderr=str(e),
|
|
312
|
+
execution_time=execution_time,
|
|
313
|
+
command=command_str,
|
|
314
|
+
tool_path=tool_path,
|
|
315
|
+
backend=self.name
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
def is_available(self) -> bool:
|
|
319
|
+
"""Check if sh is available"""
|
|
320
|
+
return self._sh_available
|
|
321
|
+
|
|
322
|
+
class SargeBackend(ExecutionBackend):
|
|
323
|
+
"""Sarge-based execution backend for advanced subprocess operations"""
|
|
324
|
+
|
|
325
|
+
def __init__(self):
|
|
326
|
+
super().__init__("sarge")
|
|
327
|
+
self._sarge_available = self._check_sarge()
|
|
328
|
+
|
|
329
|
+
def _check_sarge(self) -> bool:
|
|
330
|
+
"""Check if sarge is available"""
|
|
331
|
+
try:
|
|
332
|
+
import sarge
|
|
333
|
+
return True
|
|
334
|
+
except ImportError:
|
|
335
|
+
return False
|
|
336
|
+
|
|
337
|
+
def execute(self, tool_path: str, args: List[str],
|
|
338
|
+
working_dir: Optional[str] = None,
|
|
339
|
+
env: Optional[Dict[str, str]] = None,
|
|
340
|
+
timeout: Optional[int] = None) -> ExecutionResult:
|
|
341
|
+
"""Execute using sarge"""
|
|
342
|
+
|
|
343
|
+
if not self._sarge_available:
|
|
344
|
+
raise RuntimeError("Sarge backend not available")
|
|
345
|
+
|
|
346
|
+
import sarge
|
|
347
|
+
|
|
348
|
+
command_str = f"{tool_path} {' '.join(args)}"
|
|
349
|
+
exec_env = self._prepare_environment(env)
|
|
350
|
+
|
|
351
|
+
self.logger.debug(f"Executing with sarge: {command_str}")
|
|
352
|
+
start_time = time.time()
|
|
353
|
+
|
|
354
|
+
try:
|
|
355
|
+
# Execute with sarge
|
|
356
|
+
process = sarge.run(
|
|
357
|
+
command_str,
|
|
358
|
+
cwd=working_dir,
|
|
359
|
+
env=exec_env,
|
|
360
|
+
stdout=sarge.Capture(),
|
|
361
|
+
stderr=sarge.Capture(),
|
|
362
|
+
async_=False
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
# Wait for completion with timeout
|
|
366
|
+
if timeout:
|
|
367
|
+
process.wait(timeout=timeout)
|
|
368
|
+
else:
|
|
369
|
+
process.wait()
|
|
370
|
+
|
|
371
|
+
execution_time = time.time() - start_time
|
|
372
|
+
|
|
373
|
+
return ExecutionResult(
|
|
374
|
+
success=process.returncode == 0,
|
|
375
|
+
return_code=process.returncode,
|
|
376
|
+
stdout=process.stdout.text if process.stdout else "",
|
|
377
|
+
stderr=process.stderr.text if process.stderr else "",
|
|
378
|
+
execution_time=execution_time,
|
|
379
|
+
command=command_str,
|
|
380
|
+
tool_path=tool_path,
|
|
381
|
+
backend=self.name,
|
|
382
|
+
metadata={'sarge_process': process}
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
except Exception as e:
|
|
386
|
+
execution_time = time.time() - start_time
|
|
387
|
+
return ExecutionResult(
|
|
388
|
+
success=False,
|
|
389
|
+
return_code=-1,
|
|
390
|
+
stdout="",
|
|
391
|
+
stderr=str(e),
|
|
392
|
+
execution_time=execution_time,
|
|
393
|
+
command=command_str,
|
|
394
|
+
tool_path=tool_path,
|
|
395
|
+
backend=self.name
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
def is_available(self) -> bool:
|
|
399
|
+
"""Check if sarge is available"""
|
|
400
|
+
return self._sarge_available
|
|
401
|
+
|
|
402
|
+
class ExecutionBackendManager:
|
|
403
|
+
"""Manages multiple execution backends"""
|
|
404
|
+
|
|
405
|
+
def __init__(self):
|
|
406
|
+
self.backends: Dict[str, ExecutionBackend] = {}
|
|
407
|
+
self.default_backend = "subprocess"
|
|
408
|
+
self._initialize_backends()
|
|
409
|
+
|
|
410
|
+
def _initialize_backends(self):
|
|
411
|
+
"""Initialize all available backends"""
|
|
412
|
+
backends = [
|
|
413
|
+
SubprocessBackend(),
|
|
414
|
+
InvokeBackend(),
|
|
415
|
+
ShBackend(),
|
|
416
|
+
SargeBackend()
|
|
417
|
+
]
|
|
418
|
+
|
|
419
|
+
for backend in backends:
|
|
420
|
+
if backend.is_available():
|
|
421
|
+
self.backends[backend.name] = backend
|
|
422
|
+
logger.debug(f"Registered execution backend: {backend.name}")
|
|
423
|
+
else:
|
|
424
|
+
logger.debug(f"Backend not available: {backend.name}")
|
|
425
|
+
|
|
426
|
+
def get_backend(self, name: Optional[str] = None) -> ExecutionBackend:
|
|
427
|
+
"""Get execution backend by name"""
|
|
428
|
+
backend_name = name or self.default_backend
|
|
429
|
+
|
|
430
|
+
if backend_name not in self.backends:
|
|
431
|
+
if backend_name == self.default_backend:
|
|
432
|
+
raise RuntimeError(f"Default backend '{backend_name}' not available")
|
|
433
|
+
else:
|
|
434
|
+
logger.warning(f"Backend '{backend_name}' not available, falling back to default")
|
|
435
|
+
backend_name = self.default_backend
|
|
436
|
+
|
|
437
|
+
return self.backends[backend_name]
|
|
438
|
+
|
|
439
|
+
def list_available_backends(self) -> List[str]:
|
|
440
|
+
"""List all available backends"""
|
|
441
|
+
return list(self.backends.keys())
|
|
442
|
+
|
|
443
|
+
def set_default_backend(self, name: str):
|
|
444
|
+
"""Set default execution backend"""
|
|
445
|
+
if name not in self.backends:
|
|
446
|
+
raise ValueError(f"Backend '{name}' not available")
|
|
447
|
+
self.default_backend = name
|
|
448
|
+
logger.info(f"Default execution backend set to: {name}")
|
|
449
|
+
|
|
450
|
+
def execute(self, tool_path: str, args: List[str],
|
|
451
|
+
backend: Optional[str] = None, **kwargs) -> ExecutionResult:
|
|
452
|
+
"""Execute using specified or default backend"""
|
|
453
|
+
execution_backend = self.get_backend(backend)
|
|
454
|
+
return execution_backend.execute(tool_path, args, **kwargs)
|
|
455
|
+
|
|
456
|
+
# Singleton instance
|
|
457
|
+
_backend_manager = None
|
|
458
|
+
|
|
459
|
+
def get_execution_backend_manager() -> ExecutionBackendManager:
|
|
460
|
+
"""Get singleton execution backend manager"""
|
|
461
|
+
global _backend_manager
|
|
462
|
+
if _backend_manager is None:
|
|
463
|
+
_backend_manager = ExecutionBackendManager()
|
|
464
|
+
return _backend_manager
|