iotsploit-core 0.0.7__tar.gz → 0.0.8__tar.gz
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-0.0.7 → iotsploit_core-0.0.8}/PKG-INFO +3 -3
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/README.md +2 -2
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/pyproject.toml +1 -1
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/core/device_manager.py +2 -3
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/core/device_registry.py +1 -1
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/core/device_scanner.py +1 -1
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/core/execution_backend.py +9 -22
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/core/execution_queue.py +1 -2
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/core/exploit_manager.py +1 -4
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/core/tool_config.py +1 -2
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/core/tool_manager.py +4 -293
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/core/tool_service.py +4 -75
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/domain/target.py +15 -0
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/ports/wifi_backend.py +0 -1
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/utils/iots_logger.py +48 -8
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/__init__.py +0 -0
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/context.py +0 -0
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/core/__init__.py +0 -0
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/core/base_plugin.py +0 -0
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/core/device_config.py +0 -0
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/core/device_spec.py +0 -0
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/core/device_store.py +0 -0
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/core/exploit_spec.py +0 -0
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/core/stream_manager.py +0 -0
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/domain/__init__.py +0 -0
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/domain/device.py +0 -0
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/domain/execution_plan.py +0 -0
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/domain/plugin.py +0 -0
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/domain/stream.py +0 -0
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/platforms/__init__.py +0 -0
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/platforms/consts.py +0 -0
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/ports/__init__.py +0 -0
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/ports/driver_state_repo.py +0 -0
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/ports/plugin_repo.py +0 -0
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/ports/stream_backend.py +0 -0
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/ports/task_runner.py +0 -0
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/py.typed +0 -0
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/utils/__init__.py +0 -0
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/utils/exceptions.py +0 -0
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/utils/helpers.py +0 -0
- {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/utils/result.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: iotsploit-core
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.8
|
|
4
4
|
Summary: IoTSploit core library (domain + ports + core) - IoT security testing framework
|
|
5
5
|
License: GPL-3.0-or-later
|
|
6
6
|
Keywords: iot,security,testing,pentest,vulnerability
|
|
@@ -75,8 +75,8 @@ print(mgr.list_plugins())
|
|
|
75
75
|
```
|
|
76
76
|
|
|
77
77
|
Default exploit plugin dir:
|
|
78
|
-
- `IOTSPLOIT_EXPLOIT_PLUGINS_DIR`
|
|
79
|
-
- fallback:
|
|
78
|
+
- `IOTSPLOIT_EXPLOIT_PLUGINS_DIR`
|
|
79
|
+
- fallback: `None` (entry-point discovery only)
|
|
80
80
|
|
|
81
81
|
### Device drivers (manager)
|
|
82
82
|
|
|
@@ -47,8 +47,8 @@ print(mgr.list_plugins())
|
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
Default exploit plugin dir:
|
|
50
|
-
- `IOTSPLOIT_EXPLOIT_PLUGINS_DIR`
|
|
51
|
-
- fallback:
|
|
50
|
+
- `IOTSPLOIT_EXPLOIT_PLUGINS_DIR`
|
|
51
|
+
- fallback: `None` (entry-point discovery only)
|
|
52
52
|
|
|
53
53
|
### Device drivers (manager)
|
|
54
54
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import datetime
|
|
4
3
|
from importlib import metadata as importlib_metadata
|
|
5
4
|
import importlib.util
|
|
6
5
|
import json
|
|
@@ -8,10 +7,10 @@ import logging
|
|
|
8
7
|
import os
|
|
9
8
|
import threading
|
|
10
9
|
from pathlib import Path
|
|
11
|
-
from typing import
|
|
10
|
+
from typing import Dict, List, Optional
|
|
12
11
|
|
|
13
12
|
from iotsploit_core.core.base_plugin import BaseDeviceDriver
|
|
14
|
-
from iotsploit_core.core.device_spec import
|
|
13
|
+
from iotsploit_core.core.device_spec import DeviceState
|
|
15
14
|
from iotsploit_core.domain.device import Device
|
|
16
15
|
from iotsploit_core.ports.driver_state_repo import DriverStateRepository
|
|
17
16
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
|
-
from typing import Dict, List
|
|
4
|
+
from typing import Dict, List
|
|
5
5
|
|
|
6
6
|
from iotsploit_core.core.device_config import DeviceConfigManager
|
|
7
7
|
from iotsploit_core.core.device_scanner import CompositeDeviceScanner, PluginDeviceScanner
|
|
@@ -13,14 +13,13 @@ This allows plugins to choose the best execution method for their needs.
|
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
15
|
import os
|
|
16
|
-
import sys
|
|
17
16
|
import time
|
|
18
17
|
import logging
|
|
19
18
|
import subprocess
|
|
19
|
+
import importlib.util
|
|
20
20
|
from abc import ABC, abstractmethod
|
|
21
|
-
from typing import Dict, List, Optional, Any
|
|
21
|
+
from typing import Dict, List, Optional, Any
|
|
22
22
|
from dataclasses import dataclass
|
|
23
|
-
from enum import Enum
|
|
24
23
|
|
|
25
24
|
logger = logging.getLogger(__name__)
|
|
26
25
|
|
|
@@ -144,11 +143,7 @@ class InvokeBackend(ExecutionBackend):
|
|
|
144
143
|
|
|
145
144
|
def _check_invoke(self) -> bool:
|
|
146
145
|
"""Check if invoke is available"""
|
|
147
|
-
|
|
148
|
-
import invoke
|
|
149
|
-
return True
|
|
150
|
-
except ImportError:
|
|
151
|
-
return False
|
|
146
|
+
return importlib.util.find_spec("invoke") is not None
|
|
152
147
|
|
|
153
148
|
def execute(self, tool_path: str, args: List[str],
|
|
154
149
|
working_dir: Optional[str] = None,
|
|
@@ -160,7 +155,7 @@ class InvokeBackend(ExecutionBackend):
|
|
|
160
155
|
raise RuntimeError("Invoke backend not available")
|
|
161
156
|
|
|
162
157
|
import invoke
|
|
163
|
-
from invoke.exceptions import CommandTimedOut
|
|
158
|
+
from invoke.exceptions import CommandTimedOut
|
|
164
159
|
|
|
165
160
|
command_str = f"{tool_path} {' '.join(args)}"
|
|
166
161
|
exec_env = self._prepare_environment(env)
|
|
@@ -235,11 +230,7 @@ class ShBackend(ExecutionBackend):
|
|
|
235
230
|
|
|
236
231
|
def _check_sh(self) -> bool:
|
|
237
232
|
"""Check if sh is available"""
|
|
238
|
-
|
|
239
|
-
import sh
|
|
240
|
-
return True
|
|
241
|
-
except ImportError:
|
|
242
|
-
return False
|
|
233
|
+
return importlib.util.find_spec("sh") is not None
|
|
243
234
|
|
|
244
235
|
def execute(self, tool_path: str, args: List[str],
|
|
245
236
|
working_dir: Optional[str] = None,
|
|
@@ -251,7 +242,7 @@ class ShBackend(ExecutionBackend):
|
|
|
251
242
|
raise RuntimeError("sh backend not available")
|
|
252
243
|
|
|
253
244
|
import sh
|
|
254
|
-
from sh import
|
|
245
|
+
from sh import TimeoutException
|
|
255
246
|
|
|
256
247
|
command_str = f"{tool_path} {' '.join(args)}"
|
|
257
248
|
exec_env = self._prepare_environment(env)
|
|
@@ -290,7 +281,7 @@ class ShBackend(ExecutionBackend):
|
|
|
290
281
|
metadata={'sh_result': result}
|
|
291
282
|
)
|
|
292
283
|
|
|
293
|
-
except TimeoutException
|
|
284
|
+
except TimeoutException:
|
|
294
285
|
execution_time = time.time() - start_time
|
|
295
286
|
return ExecutionResult(
|
|
296
287
|
success=False,
|
|
@@ -328,11 +319,7 @@ class SargeBackend(ExecutionBackend):
|
|
|
328
319
|
|
|
329
320
|
def _check_sarge(self) -> bool:
|
|
330
321
|
"""Check if sarge is available"""
|
|
331
|
-
|
|
332
|
-
import sarge
|
|
333
|
-
return True
|
|
334
|
-
except ImportError:
|
|
335
|
-
return False
|
|
322
|
+
return importlib.util.find_spec("sarge") is not None
|
|
336
323
|
|
|
337
324
|
def execute(self, tool_path: str, args: List[str],
|
|
338
325
|
working_dir: Optional[str] = None,
|
|
@@ -469,4 +456,4 @@ def get_execution_backend_manager() -> ExecutionBackendManager:
|
|
|
469
456
|
global _backend_manager
|
|
470
457
|
if _backend_manager is None:
|
|
471
458
|
_backend_manager = ExecutionBackendManager()
|
|
472
|
-
return _backend_manager
|
|
459
|
+
return _backend_manager
|
|
@@ -5,8 +5,7 @@ import time
|
|
|
5
5
|
import threading
|
|
6
6
|
import queue
|
|
7
7
|
import logging
|
|
8
|
-
from
|
|
9
|
-
from typing import Dict, List, Optional, Any, Callable, Union
|
|
8
|
+
from typing import Dict, List, Optional, Any, Callable
|
|
10
9
|
from dataclasses import dataclass, field
|
|
11
10
|
from enum import Enum, IntEnum
|
|
12
11
|
from concurrent.futures import ThreadPoolExecutor, Future
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
-
# Disable pwntools terminal mode before any plugin imports to prevent
|
|
5
|
-
# "argument must be an int, or have a fileno() method" errors in non-TTY environments
|
|
6
|
-
os.environ.setdefault("PWNLIB_NOTERM", "1")
|
|
7
4
|
|
|
8
5
|
import glob
|
|
9
6
|
import importlib
|
|
@@ -50,7 +47,7 @@ class ExploitPluginManager:
|
|
|
50
47
|
|
|
51
48
|
@staticmethod
|
|
52
49
|
def _default_plugins_dir() -> Path | None:
|
|
53
|
-
env = os.getenv("IOTSPLOIT_EXPLOIT_PLUGINS_DIR")
|
|
50
|
+
env = os.getenv("IOTSPLOIT_EXPLOIT_PLUGINS_DIR")
|
|
54
51
|
if env:
|
|
55
52
|
return Path(env)
|
|
56
53
|
return None
|
|
@@ -12,7 +12,6 @@ import logging
|
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
from typing import Dict, List, Optional, Any, Union
|
|
14
14
|
from dataclasses import dataclass, field, asdict
|
|
15
|
-
from enum import Enum
|
|
16
15
|
|
|
17
16
|
logger = logging.getLogger(__name__)
|
|
18
17
|
|
|
@@ -369,7 +368,7 @@ class ToolConfigManager:
|
|
|
369
368
|
|
|
370
369
|
# Check required fields
|
|
371
370
|
if not config.name:
|
|
372
|
-
issues['missing_required_fields'].append(
|
|
371
|
+
issues['missing_required_fields'].append("Tool missing name")
|
|
373
372
|
|
|
374
373
|
# Check for duplicates
|
|
375
374
|
if config.name in seen_names:
|
|
@@ -2,25 +2,22 @@
|
|
|
2
2
|
"""Tool management: discovery, validation, and cross-platform execution."""
|
|
3
3
|
|
|
4
4
|
import os
|
|
5
|
-
import sys
|
|
6
5
|
import shutil
|
|
7
6
|
import subprocess
|
|
8
7
|
import platform
|
|
9
8
|
import json
|
|
10
9
|
import logging
|
|
11
|
-
from abc import ABC, abstractmethod
|
|
12
10
|
from pathlib import Path
|
|
13
|
-
from typing import Dict, List, Optional,
|
|
11
|
+
from typing import Dict, List, Optional, Any
|
|
14
12
|
from dataclasses import dataclass, field, asdict
|
|
15
13
|
from enum import Enum
|
|
16
14
|
import threading
|
|
17
15
|
import time
|
|
18
|
-
from contextlib import contextmanager
|
|
19
16
|
|
|
20
17
|
# Import ExecutionResult from execution_backend to avoid duplication
|
|
21
18
|
from .execution_backend import ExecutionResult, get_execution_backend_manager
|
|
22
19
|
from .tool_config import get_tool_config_manager, ToolConfig
|
|
23
|
-
from .execution_queue import get_execution_queue
|
|
20
|
+
from .execution_queue import get_execution_queue
|
|
24
21
|
|
|
25
22
|
logger = logging.getLogger(__name__)
|
|
26
23
|
|
|
@@ -771,88 +768,6 @@ class ToolManager:
|
|
|
771
768
|
"""Get statistics about tool configurations"""
|
|
772
769
|
return self.config_manager.get_stats()
|
|
773
770
|
|
|
774
|
-
# Convenience methods for common operations
|
|
775
|
-
def flash_esp32(self, port: str, firmware_path: str, **kwargs) -> Dict[str, Any]:
|
|
776
|
-
"""Convenience method for ESP32 flashing"""
|
|
777
|
-
if not self.is_available('esptool'):
|
|
778
|
-
return {"status": "error", "message": "esptool not available"}
|
|
779
|
-
|
|
780
|
-
args = [
|
|
781
|
-
'--chip', kwargs.get('chip', 'esp32s3'),
|
|
782
|
-
'--port', port,
|
|
783
|
-
'--baud', kwargs.get('baud', '460800'),
|
|
784
|
-
'write_flash', kwargs.get('address', '0x10000'), firmware_path
|
|
785
|
-
]
|
|
786
|
-
|
|
787
|
-
result = self.execute('esptool', args, timeout=300)
|
|
788
|
-
return {
|
|
789
|
-
"status": "success" if result.success else "error",
|
|
790
|
-
"message": "Flash completed" if result.success else result.stderr,
|
|
791
|
-
"execution_time": result.execution_time
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
def port_scan(self, target: str, ports: str = "1-1000",
|
|
795
|
-
scan_type: str = "syn") -> Dict[str, Any]:
|
|
796
|
-
"""Convenience method for port scanning"""
|
|
797
|
-
if not self.is_available('nmap'):
|
|
798
|
-
return {"status": "error", "message": "nmap not available"}
|
|
799
|
-
|
|
800
|
-
scan_args = {
|
|
801
|
-
'syn': ['-sS'],
|
|
802
|
-
'tcp': ['-sT'],
|
|
803
|
-
'udp': ['-sU'],
|
|
804
|
-
'ping': ['-sn']
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
args = scan_args.get(scan_type, ['-sS'])
|
|
808
|
-
args.extend(['-p', ports, target])
|
|
809
|
-
|
|
810
|
-
result = self.execute('nmap', args, timeout=300)
|
|
811
|
-
return {
|
|
812
|
-
"status": "success" if result.success else "error",
|
|
813
|
-
"message": "Scan completed" if result.success else result.stderr,
|
|
814
|
-
"output": result.stdout,
|
|
815
|
-
"execution_time": result.execution_time
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
def extract_strings(self, file_path: str, min_length: int = 4) -> Dict[str, Any]:
|
|
819
|
-
"""Convenience method for string extraction"""
|
|
820
|
-
if not self.is_available('strings'):
|
|
821
|
-
return {"status": "error", "message": "strings tool not available"}
|
|
822
|
-
|
|
823
|
-
args = [f'-n{min_length}', file_path]
|
|
824
|
-
result = self.execute('strings', args)
|
|
825
|
-
|
|
826
|
-
return {
|
|
827
|
-
"status": "success" if result.success else "error",
|
|
828
|
-
"message": "Strings extracted" if result.success else result.stderr,
|
|
829
|
-
"strings": result.stdout.split('\n') if result.success else [],
|
|
830
|
-
"execution_time": result.execution_time
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
def adb_devices(self) -> Dict[str, Any]:
|
|
834
|
-
"""Convenience method for listing ADB devices"""
|
|
835
|
-
if not self.is_available('adb'):
|
|
836
|
-
return {"status": "error", "message": "adb not available"}
|
|
837
|
-
|
|
838
|
-
result = self.execute('adb', ['devices'])
|
|
839
|
-
|
|
840
|
-
devices = []
|
|
841
|
-
if result.success:
|
|
842
|
-
lines = result.stdout.split('\n')[1:] # Skip header
|
|
843
|
-
for line in lines:
|
|
844
|
-
line = line.strip()
|
|
845
|
-
if line and '\t' in line:
|
|
846
|
-
device_id, status = line.split('\t')
|
|
847
|
-
devices.append({'id': device_id, 'status': status})
|
|
848
|
-
|
|
849
|
-
return {
|
|
850
|
-
"status": "success" if result.success else "error",
|
|
851
|
-
"message": "Devices listed" if result.success else result.stderr,
|
|
852
|
-
"devices": devices,
|
|
853
|
-
"execution_time": result.execution_time
|
|
854
|
-
}
|
|
855
|
-
|
|
856
771
|
# ========== Methods from CentralizedToolManager ==========
|
|
857
772
|
|
|
858
773
|
def get_system_status(self) -> SystemStatus:
|
|
@@ -912,44 +827,6 @@ class ToolManager:
|
|
|
912
827
|
|
|
913
828
|
return report
|
|
914
829
|
|
|
915
|
-
def get_installation_guide(self) -> Dict[str, Any]:
|
|
916
|
-
"""Get installation recommendations with hints"""
|
|
917
|
-
missing_tools = self.get_missing_tools()
|
|
918
|
-
install_hints = self.get_install_hints()
|
|
919
|
-
required_tools = self.get_required_tools()
|
|
920
|
-
optional_tools = self.get_optional_tools()
|
|
921
|
-
|
|
922
|
-
# Separate required and optional missing tools
|
|
923
|
-
required_missing = [t for t in missing_tools if t in required_tools]
|
|
924
|
-
optional_missing = [t for t in missing_tools if t in optional_tools]
|
|
925
|
-
|
|
926
|
-
guide = {
|
|
927
|
-
'required_tools': {},
|
|
928
|
-
'optional_tools': {},
|
|
929
|
-
'priority_order': required_missing + optional_missing[:5] # Top 5 optional
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
for tool in required_missing:
|
|
933
|
-
guide['required_tools'][tool] = {
|
|
934
|
-
'description': self._get_tool_description(tool),
|
|
935
|
-
'install_hint': install_hints.get(tool, "No installation hint available"),
|
|
936
|
-
'priority': 'HIGH'
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
for tool in optional_missing:
|
|
940
|
-
guide['optional_tools'][tool] = {
|
|
941
|
-
'description': self._get_tool_description(tool),
|
|
942
|
-
'install_hint': install_hints.get(tool, "No installation hint available"),
|
|
943
|
-
'priority': 'MEDIUM'
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
return guide
|
|
947
|
-
|
|
948
|
-
def _get_tool_description(self, tool_name: str) -> str:
|
|
949
|
-
"""Get tool description from config"""
|
|
950
|
-
tool_config = self.get_tool_config(tool_name)
|
|
951
|
-
return tool_config.description if tool_config else "No description available"
|
|
952
|
-
|
|
953
830
|
def _get_timestamp(self) -> str:
|
|
954
831
|
"""Get current timestamp"""
|
|
955
832
|
from datetime import datetime
|
|
@@ -995,81 +872,7 @@ class ToolManager:
|
|
|
995
872
|
}
|
|
996
873
|
}
|
|
997
874
|
|
|
998
|
-
#
|
|
999
|
-
def submit_task(self, tool_name: str, args: List[str],
|
|
1000
|
-
priority: Union[str, TaskPriority] = "normal", **kwargs) -> str:
|
|
1001
|
-
"""Submit a tool execution task to the queue"""
|
|
1002
|
-
# Handle both string and enum priority inputs
|
|
1003
|
-
if isinstance(priority, TaskPriority):
|
|
1004
|
-
task_priority = priority
|
|
1005
|
-
else:
|
|
1006
|
-
# Convert priority string to enum
|
|
1007
|
-
priority_map = {
|
|
1008
|
-
'critical': TaskPriority.CRITICAL,
|
|
1009
|
-
'high': TaskPriority.HIGH,
|
|
1010
|
-
'normal': TaskPriority.NORMAL,
|
|
1011
|
-
'low': TaskPriority.LOW,
|
|
1012
|
-
'background': TaskPriority.BACKGROUND
|
|
1013
|
-
}
|
|
1014
|
-
task_priority = priority_map.get(priority.lower(), TaskPriority.NORMAL)
|
|
1015
|
-
|
|
1016
|
-
# Get tool path
|
|
1017
|
-
tool_info = self.registry.get_tool(tool_name)
|
|
1018
|
-
if not tool_info:
|
|
1019
|
-
raise ValueError(f"Tool '{tool_name}' not found")
|
|
1020
|
-
|
|
1021
|
-
return self.queue_manager.submit_task(
|
|
1022
|
-
tool_name=tool_name,
|
|
1023
|
-
tool_path=tool_info.path,
|
|
1024
|
-
args=args,
|
|
1025
|
-
priority=task_priority,
|
|
1026
|
-
**kwargs
|
|
1027
|
-
)
|
|
1028
|
-
|
|
1029
|
-
def get_task(self, task_id: str):
|
|
1030
|
-
"""Get task by ID"""
|
|
1031
|
-
return self.queue_manager.get_task(task_id)
|
|
1032
|
-
|
|
1033
|
-
def wait_for_task(self, task_id: str, timeout: Optional[float] = None):
|
|
1034
|
-
"""Wait for task completion"""
|
|
1035
|
-
return self.queue_manager.wait_for_task(task_id, timeout)
|
|
1036
|
-
|
|
1037
|
-
def get_queue_stats(self):
|
|
1038
|
-
"""Get queue statistics"""
|
|
1039
|
-
return self.queue_manager.get_stats()
|
|
1040
|
-
|
|
1041
|
-
# Backend delegation methods
|
|
1042
|
-
def list_execution_backends(self) -> List[str]:
|
|
1043
|
-
"""List available execution backends"""
|
|
1044
|
-
return self.backend_manager.list_available_backends()
|
|
1045
|
-
|
|
1046
|
-
def set_default_backend(self, backend_name: str):
|
|
1047
|
-
"""Set default execution backend"""
|
|
1048
|
-
self.backend_manager.set_default_backend(backend_name)
|
|
1049
|
-
|
|
1050
|
-
# System management
|
|
1051
|
-
def initialize(self) -> bool:
|
|
1052
|
-
"""Initialize the system"""
|
|
1053
|
-
try:
|
|
1054
|
-
logger.info("Initializing tool manager...")
|
|
1055
|
-
|
|
1056
|
-
# Discover tools
|
|
1057
|
-
discovery_results = self.discover_tools()
|
|
1058
|
-
logger.info(f"Discovered {len(discovery_results)} tools")
|
|
1059
|
-
|
|
1060
|
-
# Check system health
|
|
1061
|
-
status = self.get_system_status()
|
|
1062
|
-
logger.info(f"System health: {status.health.value}")
|
|
1063
|
-
|
|
1064
|
-
if status.health == SystemHealth.CRITICAL:
|
|
1065
|
-
logger.warning(f"System is in CRITICAL state - {status.required_missing} required tools missing")
|
|
1066
|
-
|
|
1067
|
-
return True
|
|
1068
|
-
|
|
1069
|
-
except Exception as e:
|
|
1070
|
-
logger.error(f"Failed to initialize system: {e}")
|
|
1071
|
-
return False
|
|
1072
|
-
|
|
875
|
+
# Lifecycle management
|
|
1073
876
|
def cleanup(self):
|
|
1074
877
|
"""Cleanup resources"""
|
|
1075
878
|
try:
|
|
@@ -1081,15 +884,6 @@ class ToolManager:
|
|
|
1081
884
|
def shutdown(self, wait: bool = True):
|
|
1082
885
|
"""Shutdown the system"""
|
|
1083
886
|
self.cleanup()
|
|
1084
|
-
|
|
1085
|
-
def __enter__(self):
|
|
1086
|
-
"""Context manager entry"""
|
|
1087
|
-
self.initialize()
|
|
1088
|
-
return self
|
|
1089
|
-
|
|
1090
|
-
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
1091
|
-
"""Context manager exit"""
|
|
1092
|
-
self.cleanup()
|
|
1093
887
|
|
|
1094
888
|
# Convenience
|
|
1095
889
|
def get_tool_manager() -> ToolManager:
|
|
@@ -1104,87 +898,4 @@ def execute_tool(tool_name: str, args: List[str], **kwargs) -> ExecutionResult:
|
|
|
1104
898
|
|
|
1105
899
|
def is_tool_available(tool_name: str) -> bool:
|
|
1106
900
|
"""Check if a tool is available"""
|
|
1107
|
-
return get_tool_manager().is_available(tool_name)
|
|
1108
|
-
|
|
1109
|
-
def print_system_report():
|
|
1110
|
-
"""Print a comprehensive system report"""
|
|
1111
|
-
manager = get_tool_manager()
|
|
1112
|
-
|
|
1113
|
-
print("\n" + "="*60)
|
|
1114
|
-
print("🔧 IOTSPLOIT TOOL MANAGEMENT SYSTEM REPORT")
|
|
1115
|
-
print("="*60)
|
|
1116
|
-
|
|
1117
|
-
# Get system status
|
|
1118
|
-
status = manager.get_system_status()
|
|
1119
|
-
health_icon = {
|
|
1120
|
-
"healthy": "✅",
|
|
1121
|
-
"degraded": "⚠️",
|
|
1122
|
-
"critical": "❌"
|
|
1123
|
-
}.get(status.health.value, "❓")
|
|
1124
|
-
|
|
1125
|
-
print(f"\n📊 System Health: {health_icon} {status.health.value.upper()}")
|
|
1126
|
-
print(f"🔧 Total Tools: {status.total_tools}")
|
|
1127
|
-
print(f"✅ Available: {status.available_tools}")
|
|
1128
|
-
print(f"❌ Missing: {status.missing_tools}")
|
|
1129
|
-
print(f"🔴 Required Missing: {status.required_missing}")
|
|
1130
|
-
print(f"🚀 Can Operate: {'Yes' if status.can_operate else 'No'}")
|
|
1131
|
-
|
|
1132
|
-
# Show recommendations
|
|
1133
|
-
if status.recommendations:
|
|
1134
|
-
print(f"\n💡 Recommendations:")
|
|
1135
|
-
for rec in status.recommendations:
|
|
1136
|
-
print(f" • {rec}")
|
|
1137
|
-
|
|
1138
|
-
# Show queue stats
|
|
1139
|
-
try:
|
|
1140
|
-
queue_stats = manager.get_queue_stats()
|
|
1141
|
-
print(f"\n📋 Queue Status:")
|
|
1142
|
-
print(f" • Total Tasks: {queue_stats.total_tasks}")
|
|
1143
|
-
print(f" • Running: {queue_stats.running_tasks}")
|
|
1144
|
-
print(f" • Pending: {queue_stats.pending_tasks}")
|
|
1145
|
-
print(f" • Completed: {queue_stats.completed_tasks}")
|
|
1146
|
-
print(f" • Failed: {queue_stats.failed_tasks}")
|
|
1147
|
-
if queue_stats.average_execution_time > 0:
|
|
1148
|
-
print(f" • Avg Execution Time: {queue_stats.average_execution_time:.2f}s")
|
|
1149
|
-
except Exception as e:
|
|
1150
|
-
print(f"\n📋 Queue Status: Error - {e}")
|
|
1151
|
-
|
|
1152
|
-
# Show category info
|
|
1153
|
-
try:
|
|
1154
|
-
category_info = manager.get_category_info()
|
|
1155
|
-
print(f"\n📂 Tool Category: {category_info.name}")
|
|
1156
|
-
print(f" Description: {category_info.description}")
|
|
1157
|
-
print(f" Tools: {len(category_info.tools)}")
|
|
1158
|
-
|
|
1159
|
-
# Show available vs missing
|
|
1160
|
-
available = manager.get_available_tools()
|
|
1161
|
-
missing = manager.get_missing_tools()
|
|
1162
|
-
required = manager.get_required_tools()
|
|
1163
|
-
|
|
1164
|
-
print(f"\n🔧 Tool Breakdown:")
|
|
1165
|
-
print(f" ✅ Available: {len(available)}")
|
|
1166
|
-
if available[:5]: # Show first 5
|
|
1167
|
-
for tool in available[:5]:
|
|
1168
|
-
print(f" • {tool}")
|
|
1169
|
-
if len(available) > 5:
|
|
1170
|
-
print(f" ... and {len(available) - 5} more")
|
|
1171
|
-
|
|
1172
|
-
if missing:
|
|
1173
|
-
print(f" ❌ Missing: {len(missing)}")
|
|
1174
|
-
required_missing = [t for t in missing if t in required]
|
|
1175
|
-
optional_missing = [t for t in missing if t not in required]
|
|
1176
|
-
|
|
1177
|
-
if required_missing:
|
|
1178
|
-
print(f" 🔴 Required: {', '.join(required_missing[:3])}")
|
|
1179
|
-
if len(required_missing) > 3:
|
|
1180
|
-
print(f" ... and {len(required_missing) - 3} more")
|
|
1181
|
-
|
|
1182
|
-
if optional_missing:
|
|
1183
|
-
print(f" 🟡 Optional: {', '.join(optional_missing[:3])}")
|
|
1184
|
-
if len(optional_missing) > 3:
|
|
1185
|
-
print(f" ... and {len(optional_missing) - 3} more")
|
|
1186
|
-
|
|
1187
|
-
except Exception as e:
|
|
1188
|
-
print(f"\n📂 Category Status: Error - {e}")
|
|
1189
|
-
|
|
1190
|
-
print("\n" + "="*60)
|
|
901
|
+
return get_tool_manager().is_available(tool_name)
|
|
@@ -3,12 +3,9 @@
|
|
|
3
3
|
|
|
4
4
|
import json
|
|
5
5
|
import logging
|
|
6
|
-
import
|
|
7
|
-
from typing import Dict, List, Optional, Any, Union
|
|
8
|
-
from dataclasses import dataclass, field
|
|
9
|
-
from enum import Enum
|
|
6
|
+
from typing import Dict, List, Optional, Any
|
|
10
7
|
|
|
11
|
-
from .tool_manager import get_tool_manager, ExecutionResult
|
|
8
|
+
from .tool_manager import get_tool_manager, ExecutionResult
|
|
12
9
|
|
|
13
10
|
logger = logging.getLogger(__name__)
|
|
14
11
|
|
|
@@ -387,7 +384,7 @@ exit
|
|
|
387
384
|
result = self.execute_tool('openocd', args, timeout=300)
|
|
388
385
|
os.unlink(script_path)
|
|
389
386
|
return result
|
|
390
|
-
except Exception
|
|
387
|
+
except Exception:
|
|
391
388
|
if os.path.exists(script_path):
|
|
392
389
|
os.unlink(script_path)
|
|
393
390
|
raise
|
|
@@ -741,7 +738,6 @@ class FirmwareToolService(ToolService):
|
|
|
741
738
|
manifest.
|
|
742
739
|
"""
|
|
743
740
|
from contextlib import contextmanager, ExitStack
|
|
744
|
-
from pathlib import Path
|
|
745
741
|
|
|
746
742
|
info = self.manifests.get(name)
|
|
747
743
|
if info is None:
|
|
@@ -1019,68 +1015,8 @@ class NetworkToolService(ToolService):
|
|
|
1019
1015
|
return self.execute_tool('nmap', args, timeout=300)
|
|
1020
1016
|
|
|
1021
1017
|
|
|
1022
|
-
|
|
1023
|
-
class BinaryToolService(ToolService):
|
|
1024
|
-
"""Tool service specialized for binary analysis"""
|
|
1025
|
-
|
|
1026
|
-
def __init__(self):
|
|
1027
|
-
super().__init__()
|
|
1028
|
-
# Pre-register common binary analysis tools
|
|
1029
|
-
self.register_tool('strings', required=False)
|
|
1030
|
-
self.register_tool('binwalk', required=False)
|
|
1031
|
-
self.register_tool('objdump', required=False)
|
|
1032
|
-
self.register_tool('readelf', required=False)
|
|
1033
|
-
self.register_tool('file', required=False)
|
|
1034
|
-
|
|
1035
|
-
def extract_strings(self, file_path: str, min_length: int = 4) -> ExecutionResult:
|
|
1036
|
-
"""
|
|
1037
|
-
Extract strings from binary file.
|
|
1038
|
-
|
|
1039
|
-
Args:
|
|
1040
|
-
file_path: Path to binary file
|
|
1041
|
-
min_length: Minimum string length
|
|
1042
|
-
|
|
1043
|
-
Returns:
|
|
1044
|
-
ExecutionResult: Result containing extracted strings
|
|
1045
|
-
"""
|
|
1046
|
-
if not self.is_tool_available('strings'):
|
|
1047
|
-
raise RuntimeError("strings tool is not available")
|
|
1048
|
-
|
|
1049
|
-
args = [f'-n{min_length}', file_path]
|
|
1050
|
-
return self.execute_tool('strings', args)
|
|
1051
|
-
|
|
1052
|
-
def analyze_firmware(self, file_path: str, extract: bool = False) -> ExecutionResult:
|
|
1053
|
-
"""
|
|
1054
|
-
Analyze firmware file with binwalk.
|
|
1055
|
-
|
|
1056
|
-
Args:
|
|
1057
|
-
file_path: Path to firmware file
|
|
1058
|
-
extract: Whether to extract found files
|
|
1059
|
-
|
|
1060
|
-
Returns:
|
|
1061
|
-
ExecutionResult: Result of the analysis
|
|
1062
|
-
"""
|
|
1063
|
-
if not self.is_tool_available('binwalk'):
|
|
1064
|
-
raise RuntimeError("binwalk is not available")
|
|
1065
|
-
|
|
1066
|
-
args = []
|
|
1067
|
-
if extract:
|
|
1068
|
-
args.append('-e')
|
|
1069
|
-
args.append(file_path)
|
|
1070
|
-
|
|
1071
|
-
return self.execute_tool('binwalk', args)
|
|
1072
|
-
|
|
1073
|
-
_tool_service_instance = None
|
|
1074
1018
|
_firmware_service_instance = None
|
|
1075
1019
|
_network_service_instance = None
|
|
1076
|
-
_binary_service_instance = None
|
|
1077
|
-
|
|
1078
|
-
def get_tool_service() -> ToolService:
|
|
1079
|
-
"""Get singleton ToolService instance"""
|
|
1080
|
-
global _tool_service_instance
|
|
1081
|
-
if _tool_service_instance is None:
|
|
1082
|
-
_tool_service_instance = ToolService()
|
|
1083
|
-
return _tool_service_instance
|
|
1084
1020
|
|
|
1085
1021
|
def get_firmware_service() -> FirmwareToolService:
|
|
1086
1022
|
"""Get singleton FirmwareToolService instance"""
|
|
@@ -1094,11 +1030,4 @@ def get_network_service() -> NetworkToolService:
|
|
|
1094
1030
|
global _network_service_instance
|
|
1095
1031
|
if _network_service_instance is None:
|
|
1096
1032
|
_network_service_instance = NetworkToolService()
|
|
1097
|
-
return _network_service_instance
|
|
1098
|
-
|
|
1099
|
-
def get_binary_service() -> BinaryToolService:
|
|
1100
|
-
"""Get singleton BinaryToolService instance"""
|
|
1101
|
-
global _binary_service_instance
|
|
1102
|
-
if _binary_service_instance is None:
|
|
1103
|
-
_binary_service_instance = BinaryToolService()
|
|
1104
|
-
return _binary_service_instance
|
|
1033
|
+
return _network_service_instance
|
|
@@ -21,6 +21,21 @@ class Target(BaseModel, ABC):
|
|
|
21
21
|
def get_info(self) -> Dict[str, Any]:
|
|
22
22
|
raise NotImplementedError
|
|
23
23
|
|
|
24
|
+
def get_ecu_ip(self, ecu: str) -> Optional[str]:
|
|
25
|
+
"""Return the configured IP of the ECU component whose name matches `ecu`.
|
|
26
|
+
|
|
27
|
+
ECUs are modeled as components selected by name ("TCAM"/"DHU"/"VGM").
|
|
28
|
+
The IP is read from the component's free-form ``properties.ip_address``,
|
|
29
|
+
falling back to a typed ``ip_address`` field (e.g. NetworkComponent).
|
|
30
|
+
Returns None when the ECU has no configured IP.
|
|
31
|
+
"""
|
|
32
|
+
name = (ecu or "").upper()
|
|
33
|
+
for comp in self.components:
|
|
34
|
+
if comp.name.upper() == name:
|
|
35
|
+
ip = comp.properties.get("ip_address") if isinstance(comp.properties, dict) else None
|
|
36
|
+
return ip or getattr(comp, "ip_address", None)
|
|
37
|
+
return None
|
|
38
|
+
|
|
24
39
|
|
|
25
40
|
class Component(BaseModel):
|
|
26
41
|
component_id: str
|
|
@@ -25,9 +25,17 @@ class IotsLogger:
|
|
|
25
25
|
_instance: Optional["IotsLogger"] = None
|
|
26
26
|
_lock = threading.Lock()
|
|
27
27
|
|
|
28
|
-
LOG_FORMAT = "%(asctime)s | %(levelname)s | %(name)s | %(message)s"
|
|
29
28
|
LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
|
|
30
|
-
|
|
29
|
+
LOG_FORMATS = {
|
|
30
|
+
"standard": "%(asctime)s | %(levelname)s | %(name)s | %(message)s",
|
|
31
|
+
"compact": "%(levelname)s | %(message)s",
|
|
32
|
+
"plain": "%(message)s",
|
|
33
|
+
}
|
|
34
|
+
COLOR_LOG_FORMATS = {
|
|
35
|
+
"standard": "%(log_color)s%(asctime)s | %(levelname)s | %(name)s | %(message)s",
|
|
36
|
+
"compact": "%(log_color)s%(levelname)s | %(message)s",
|
|
37
|
+
"plain": "%(log_color)s%(message)s",
|
|
38
|
+
}
|
|
31
39
|
|
|
32
40
|
LOG_COLORS = {
|
|
33
41
|
"DEBUG": "cyan",
|
|
@@ -50,6 +58,7 @@ class IotsLogger:
|
|
|
50
58
|
self._extra_handlers: List[logging.Handler] = []
|
|
51
59
|
self._use_color = _HAS_COLORLOG and self._is_tty()
|
|
52
60
|
self._default_level = self._get_env_level()
|
|
61
|
+
self._default_format = self._get_env_format()
|
|
53
62
|
|
|
54
63
|
@staticmethod
|
|
55
64
|
def _is_tty() -> bool:
|
|
@@ -63,6 +72,13 @@ class IotsLogger:
|
|
|
63
72
|
level_str = os.getenv("IOTSPLOIT_LOG_LEVEL", "INFO").upper()
|
|
64
73
|
return getattr(logging, level_str, logging.INFO)
|
|
65
74
|
|
|
75
|
+
@classmethod
|
|
76
|
+
def _get_env_format(cls) -> str:
|
|
77
|
+
fmt = os.getenv("IOTSPLOIT_LOG_FORMAT", "standard").strip().lower()
|
|
78
|
+
if fmt not in cls.LOG_FORMATS:
|
|
79
|
+
return "standard"
|
|
80
|
+
return fmt
|
|
81
|
+
|
|
66
82
|
def get_logger(self, name: str = "iotsploit", level: Optional[int] = None) -> logging.Logger:
|
|
67
83
|
"""
|
|
68
84
|
获取(或创建)一个已配置好的 logger。
|
|
@@ -93,18 +109,27 @@ class IotsLogger:
|
|
|
93
109
|
def _create_console_handler(self) -> logging.Handler:
|
|
94
110
|
if self._use_color and _HAS_COLORLOG:
|
|
95
111
|
handler = colorlog.StreamHandler(sys.stderr) # type: ignore[name-defined]
|
|
112
|
+
self._set_console_formatter(handler)
|
|
113
|
+
return handler
|
|
114
|
+
|
|
115
|
+
handler = logging.StreamHandler(sys.stderr)
|
|
116
|
+
self._set_console_formatter(handler)
|
|
117
|
+
return handler
|
|
118
|
+
|
|
119
|
+
def _set_console_formatter(self, handler: logging.Handler) -> None:
|
|
120
|
+
if self._use_color and _HAS_COLORLOG:
|
|
96
121
|
handler.setFormatter(
|
|
97
122
|
colorlog.ColoredFormatter( # type: ignore[name-defined]
|
|
98
|
-
self.
|
|
123
|
+
self.COLOR_LOG_FORMATS[self._default_format],
|
|
99
124
|
datefmt=self.LOG_DATE_FORMAT,
|
|
100
125
|
log_colors=self.LOG_COLORS,
|
|
101
126
|
)
|
|
102
127
|
)
|
|
103
|
-
return
|
|
128
|
+
return
|
|
104
129
|
|
|
105
|
-
handler
|
|
106
|
-
|
|
107
|
-
|
|
130
|
+
handler.setFormatter(
|
|
131
|
+
logging.Formatter(self.LOG_FORMATS[self._default_format], datefmt=self.LOG_DATE_FORMAT)
|
|
132
|
+
)
|
|
108
133
|
|
|
109
134
|
def add_handler(self, handler: logging.Handler) -> None:
|
|
110
135
|
"""为所有已创建的 logger 注入额外 handler。"""
|
|
@@ -140,6 +165,21 @@ class IotsLogger:
|
|
|
140
165
|
for logger in self._loggers.values():
|
|
141
166
|
logger.setLevel(level_value)
|
|
142
167
|
|
|
168
|
+
def set_format(self, fmt: str) -> None:
|
|
169
|
+
"""设置控制台日志格式:standard、compact 或 plain。额外 handler 不受影响。"""
|
|
170
|
+
fmt = fmt.strip().lower()
|
|
171
|
+
if fmt not in self.LOG_FORMATS:
|
|
172
|
+
return
|
|
173
|
+
|
|
174
|
+
with self._lock:
|
|
175
|
+
self._default_format = fmt
|
|
176
|
+
for logger in self._loggers.values():
|
|
177
|
+
for handler in logger.handlers:
|
|
178
|
+
if handler in self._extra_handlers:
|
|
179
|
+
continue
|
|
180
|
+
if isinstance(handler, logging.StreamHandler):
|
|
181
|
+
self._set_console_formatter(handler)
|
|
182
|
+
|
|
143
183
|
def debug(self, msg: str, name: str = "iotsploit", **kwargs: Any) -> None:
|
|
144
184
|
self.get_logger(name).debug(msg, **kwargs)
|
|
145
185
|
|
|
@@ -161,4 +201,4 @@ iots_logger = IotsLogger()
|
|
|
161
201
|
|
|
162
202
|
def get_logger(name: str = "iotsploit") -> logging.Logger:
|
|
163
203
|
"""便捷函数:`get_logger("plugin_foo")` 等价于 `iots_logger.get_logger("plugin_foo")`。"""
|
|
164
|
-
return iots_logger.get_logger(name)
|
|
204
|
+
return iots_logger.get_logger(name)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|