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.
Files changed (41) hide show
  1. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/PKG-INFO +3 -3
  2. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/README.md +2 -2
  3. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/pyproject.toml +1 -1
  4. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/core/device_manager.py +2 -3
  5. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/core/device_registry.py +1 -1
  6. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/core/device_scanner.py +1 -1
  7. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/core/execution_backend.py +9 -22
  8. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/core/execution_queue.py +1 -2
  9. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/core/exploit_manager.py +1 -4
  10. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/core/tool_config.py +1 -2
  11. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/core/tool_manager.py +4 -293
  12. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/core/tool_service.py +4 -75
  13. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/domain/target.py +15 -0
  14. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/ports/wifi_backend.py +0 -1
  15. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/utils/iots_logger.py +48 -8
  16. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/__init__.py +0 -0
  17. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/context.py +0 -0
  18. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/core/__init__.py +0 -0
  19. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/core/base_plugin.py +0 -0
  20. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/core/device_config.py +0 -0
  21. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/core/device_spec.py +0 -0
  22. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/core/device_store.py +0 -0
  23. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/core/exploit_spec.py +0 -0
  24. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/core/stream_manager.py +0 -0
  25. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/domain/__init__.py +0 -0
  26. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/domain/device.py +0 -0
  27. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/domain/execution_plan.py +0 -0
  28. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/domain/plugin.py +0 -0
  29. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/domain/stream.py +0 -0
  30. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/platforms/__init__.py +0 -0
  31. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/platforms/consts.py +0 -0
  32. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/ports/__init__.py +0 -0
  33. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/ports/driver_state_repo.py +0 -0
  34. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/ports/plugin_repo.py +0 -0
  35. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/ports/stream_backend.py +0 -0
  36. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/ports/task_runner.py +0 -0
  37. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/py.typed +0 -0
  38. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/utils/__init__.py +0 -0
  39. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/utils/exceptions.py +0 -0
  40. {iotsploit_core-0.0.7 → iotsploit_core-0.0.8}/src/iotsploit_core/utils/helpers.py +0 -0
  41. {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.7
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` (or legacy `SAT_EXPLOIT_PLUGINS_DIR`)
79
- - fallback: `./plugins/exploits`
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` (or legacy `SAT_EXPLOIT_PLUGINS_DIR`)
51
- - fallback: `./plugins/exploits`
50
+ - `IOTSPLOIT_EXPLOIT_PLUGINS_DIR`
51
+ - fallback: `None` (entry-point discovery only)
52
52
 
53
53
  ### Device drivers (manager)
54
54
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "iotsploit-core"
3
- version = "0.0.7"
3
+ version = "0.0.8"
4
4
  description = "IoTSploit core library (domain + ports + core) - IoT security testing framework"
5
5
  authors = ["IoTSploit Team <support@iotsploit.org>"]
6
6
  readme = "README.md"
@@ -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 Any, Dict, List, Optional
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 DevicePluginSpec, DeviceState
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, Optional
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
@@ -1,4 +1,4 @@
1
- from typing import List, Dict, Type
1
+ from typing import List
2
2
  import logging
3
3
  from iotsploit_core.domain.device import Device
4
4
  from iotsploit_core.core.device_store import DeviceStore
@@ -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, Union
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
- try:
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, UnexpectedExit
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
- try:
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 CommandNotFound, TimeoutException
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 as e:
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
- try:
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 abc import ABC, abstractmethod
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") or os.getenv("SAT_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(f"Tool missing name")
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, Union, Any, Tuple
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, TaskPriority
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
- # Queue delegation methods
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 time
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, ToolStatus
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 as e:
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
@@ -8,7 +8,6 @@ Platform-specific implementations should be provided in adapter modules.
8
8
  from abc import ABC, abstractmethod
9
9
  from typing import List, Optional, Dict, Any, Tuple
10
10
 
11
- from iotsploit_core.utils.exceptions import NotSupportedError
12
11
 
13
12
 
14
13
  class WifiBackend(ABC):
@@ -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
- COLOR_FORMAT = "%(log_color)s%(asctime)s | %(levelname)s | %(name)s | %(message)s"
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.COLOR_FORMAT,
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 handler
128
+ return
104
129
 
105
- handler = logging.StreamHandler(sys.stderr)
106
- handler.setFormatter(logging.Formatter(self.LOG_FORMAT, datefmt=self.LOG_DATE_FORMAT))
107
- return handler
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)