iotsploit-cli 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_cli/__init__.py +1 -0
- iotsploit_cli/commands/__init__.py +1 -0
- iotsploit_cli/commands/base_commands.py +5 -0
- iotsploit_cli/commands/device_commands.py +536 -0
- iotsploit_cli/commands/django_commands.py +254 -0
- iotsploit_cli/commands/firmware_commands.py +213 -0
- iotsploit_cli/commands/linux_commands.py +35 -0
- iotsploit_cli/commands/network_commands.py +36 -0
- iotsploit_cli/commands/plugin_commands.py +249 -0
- iotsploit_cli/commands/system_commands.py +77 -0
- iotsploit_cli/commands/target_commands.py +246 -0
- iotsploit_cli/console.py +493 -0
- iotsploit_cli-0.0.6.dist-info/METADATA +67 -0
- iotsploit_cli-0.0.6.dist-info/RECORD +16 -0
- iotsploit_cli-0.0.6.dist-info/WHEEL +4 -0
- iotsploit_cli-0.0.6.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
import cmd2
|
|
4
|
+
from cmd2 import ansi
|
|
5
|
+
import sys
|
|
6
|
+
import subprocess
|
|
7
|
+
import time
|
|
8
|
+
from .base_commands import BaseCommands
|
|
9
|
+
from iotsploit_core.utils import iots_logger
|
|
10
|
+
import os
|
|
11
|
+
import signal
|
|
12
|
+
import socket
|
|
13
|
+
from typing import Tuple
|
|
14
|
+
try:
|
|
15
|
+
import redis # type: ignore
|
|
16
|
+
except Exception: # pragma: no cover
|
|
17
|
+
redis = None
|
|
18
|
+
|
|
19
|
+
from django.conf import settings
|
|
20
|
+
|
|
21
|
+
logger = iots_logger.get_logger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class DjangoCommands(BaseCommands):
|
|
25
|
+
"""Django-related commands for the SAT Shell"""
|
|
26
|
+
|
|
27
|
+
def _check_redis_available(self) -> Tuple[bool, str]:
|
|
28
|
+
"""
|
|
29
|
+
Preflight check for Redis reachability using Django settings.
|
|
30
|
+
|
|
31
|
+
Returns (ok, message). If ok is False, message contains human-friendly guidance.
|
|
32
|
+
"""
|
|
33
|
+
redis_host = getattr(settings, 'REDIS_HOST', '127.0.0.1')
|
|
34
|
+
redis_port = int(getattr(settings, 'REDIS_PORT', 6379))
|
|
35
|
+
redis_db = int(getattr(settings, 'REDIS_DB', 0))
|
|
36
|
+
|
|
37
|
+
# Quick TCP reachability check first to avoid long client timeouts
|
|
38
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
|
39
|
+
sock.settimeout(0.5)
|
|
40
|
+
try:
|
|
41
|
+
sock.connect((redis_host, redis_port))
|
|
42
|
+
except Exception:
|
|
43
|
+
guide = (
|
|
44
|
+
f"Redis is not reachable at {redis_host}:{redis_port}.\n"
|
|
45
|
+
"Celery and WebSocket features require Redis.\n\n"
|
|
46
|
+
"How to start Redis:\n"
|
|
47
|
+
" - If you use Docker: docker run --name redis -p 6379:6379 -d redis:latest\n"
|
|
48
|
+
" - On Ubuntu/Debian: sudo apt install redis-server && sudo systemctl start redis-server\n"
|
|
49
|
+
" - Or inside project (docker-compose): enable the redis service\n\n"
|
|
50
|
+
"Please start Redis and try again."
|
|
51
|
+
)
|
|
52
|
+
return False, guide
|
|
53
|
+
|
|
54
|
+
# If redis-py is available, ping for certainty
|
|
55
|
+
if redis is not None:
|
|
56
|
+
try:
|
|
57
|
+
client = redis.Redis(host=redis_host, port=redis_port, db=redis_db)
|
|
58
|
+
client.ping()
|
|
59
|
+
except Exception as e: # pragma: no cover
|
|
60
|
+
return False, (
|
|
61
|
+
f"Connected to {redis_host}:{redis_port} but Redis ping failed: {e}\n"
|
|
62
|
+
"Please ensure Redis is healthy and try again."
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
return True, "Redis is available"
|
|
66
|
+
|
|
67
|
+
@cmd2.with_category('Django Commands')
|
|
68
|
+
def do_runserver(self, arg):
|
|
69
|
+
'Start Django development server, Daphne WebSocket server, MCP WebSocket bridge, and Celery worker in the background'
|
|
70
|
+
if self.django_server_process or self.daphne_server_process:
|
|
71
|
+
self.poutput("Servers are already running.")
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
logger.info("Attempting to start Django, Daphne, MCP WebSocket bridge, and Celery servers in background...")
|
|
76
|
+
|
|
77
|
+
# Preflight: check Redis and fail fast if unavailable
|
|
78
|
+
redis_ok, redis_msg = self._check_redis_available()
|
|
79
|
+
if not redis_ok:
|
|
80
|
+
self.poutput(ansi.style("\n[ERROR] Redis is unavailable. Cannot start services that depend on it.", fg=ansi.Fg.RED, bold=True))
|
|
81
|
+
self.poutput(redis_msg)
|
|
82
|
+
return False
|
|
83
|
+
|
|
84
|
+
# Prepare the commands
|
|
85
|
+
django_cmd = [sys.executable, '-m', 'django', 'runserver', '--noreload', '0.0.0.0:8888']
|
|
86
|
+
daphne_cmd = [
|
|
87
|
+
sys.executable,
|
|
88
|
+
'-m',
|
|
89
|
+
'daphne',
|
|
90
|
+
'-b',
|
|
91
|
+
'0.0.0.0',
|
|
92
|
+
'-p',
|
|
93
|
+
'9999',
|
|
94
|
+
'iotsploit_django.asgi:application'
|
|
95
|
+
]
|
|
96
|
+
mcp_bridge_cmd = [
|
|
97
|
+
sys.executable,
|
|
98
|
+
'-m',
|
|
99
|
+
'iotsploit_mcp.cli',
|
|
100
|
+
'ws',
|
|
101
|
+
'--host',
|
|
102
|
+
'0.0.0.0',
|
|
103
|
+
'--port',
|
|
104
|
+
'9998',
|
|
105
|
+
]
|
|
106
|
+
celery_cmd = [
|
|
107
|
+
sys.executable,
|
|
108
|
+
'-m',
|
|
109
|
+
'celery',
|
|
110
|
+
'-A',
|
|
111
|
+
'iotsploit_django.tasks.celery_app:app',
|
|
112
|
+
'worker',
|
|
113
|
+
'--loglevel=info'
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
logger.info(f"Running Django command: {' '.join(django_cmd)}")
|
|
117
|
+
logger.info(f"Running Daphne command: {' '.join(daphne_cmd)}")
|
|
118
|
+
logger.info(f"Running MCP WebSocket Bridge command: {' '.join(mcp_bridge_cmd)}")
|
|
119
|
+
logger.info(f"Running Celery command: {' '.join(celery_cmd)}")
|
|
120
|
+
|
|
121
|
+
# Start the processes with direct output to stdout/stderr
|
|
122
|
+
self.django_server_process = subprocess.Popen(
|
|
123
|
+
django_cmd,
|
|
124
|
+
stdout=sys.stdout, # 直接输出到控制台
|
|
125
|
+
stderr=sys.stderr,
|
|
126
|
+
universal_newlines=True
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
self.daphne_server_process = subprocess.Popen(
|
|
130
|
+
daphne_cmd,
|
|
131
|
+
stdout=sys.stdout, # 直接输出到控制台
|
|
132
|
+
stderr=sys.stderr,
|
|
133
|
+
universal_newlines=True
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
# Start the WebSocket bridge in its own process group so that we can
|
|
137
|
+
# later terminate the entire group (bridge + sat_fastmcp child)
|
|
138
|
+
# Set up environment variables for MCP bridge (Django API URL)
|
|
139
|
+
mcp_env = os.environ.copy()
|
|
140
|
+
mcp_env.setdefault('IOTSPLOIT_DJANGO_API_BASE_URL', 'http://127.0.0.1:8888')
|
|
141
|
+
|
|
142
|
+
self.mcp_bridge_process = subprocess.Popen(
|
|
143
|
+
mcp_bridge_cmd,
|
|
144
|
+
stdout=sys.stdout, # 直接输出到控制台
|
|
145
|
+
stderr=sys.stderr,
|
|
146
|
+
universal_newlines=True,
|
|
147
|
+
start_new_session=True, # create new session = new PGID on POSIX
|
|
148
|
+
env=mcp_env
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
self.celery_worker_process = subprocess.Popen(
|
|
152
|
+
celery_cmd,
|
|
153
|
+
stdout=sys.stdout, # 直接输出到控制台
|
|
154
|
+
stderr=sys.stderr,
|
|
155
|
+
universal_newlines=True
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
logger.info("All servers started successfully in the background.")
|
|
159
|
+
logger.info("Services running on:")
|
|
160
|
+
logger.info(" - Django HTTP API: http://localhost:8888")
|
|
161
|
+
logger.info(" - Daphne WebSocket: ws://localhost:9999")
|
|
162
|
+
logger.info(" - MCP WebSocket Bridge: ws://localhost:9998")
|
|
163
|
+
logger.info(" - Celery Worker: background task processing")
|
|
164
|
+
|
|
165
|
+
# Wait for HTTP server to be available and initialize devices
|
|
166
|
+
import requests
|
|
167
|
+
max_retries = 30
|
|
168
|
+
retry_interval = 1
|
|
169
|
+
|
|
170
|
+
logger.info("Waiting for HTTP server to be available...")
|
|
171
|
+
for i in range(max_retries):
|
|
172
|
+
try:
|
|
173
|
+
# Try to initialize devices using the HTTP endpoint (GET method)
|
|
174
|
+
response = requests.get('http://127.0.0.1:8888/api/initialize_devices/')
|
|
175
|
+
if response.status_code == 200:
|
|
176
|
+
logger.info("Devices initialized successfully via HTTP API")
|
|
177
|
+
break
|
|
178
|
+
else:
|
|
179
|
+
logger.error(f"Failed to initialize devices: {response.text}")
|
|
180
|
+
break
|
|
181
|
+
except requests.exceptions.ConnectionError:
|
|
182
|
+
if i < max_retries - 1:
|
|
183
|
+
time.sleep(retry_interval)
|
|
184
|
+
else:
|
|
185
|
+
logger.error("HTTP server did not become available in time")
|
|
186
|
+
continue
|
|
187
|
+
except Exception as e:
|
|
188
|
+
logger.error(f"Error initializing devices: {str(e)}")
|
|
189
|
+
break
|
|
190
|
+
|
|
191
|
+
return None
|
|
192
|
+
|
|
193
|
+
except Exception as e:
|
|
194
|
+
logger.error(f"Failed to start servers: {str(e)}")
|
|
195
|
+
logger.debug("Detailed traceback:", exc_info=True)
|
|
196
|
+
return False
|
|
197
|
+
|
|
198
|
+
@cmd2.with_category('Django Commands')
|
|
199
|
+
def do_stop_server(self, arg):
|
|
200
|
+
'Stop Django development server, Daphne WebSocket server, MCP WebSocket bridge, and Celery worker'
|
|
201
|
+
try:
|
|
202
|
+
# Cleanup devices using HTTP endpoint (GET method)
|
|
203
|
+
# Only attempt HTTP cleanup if the Django server process is still alive
|
|
204
|
+
if self.django_server_process and self.django_server_process.poll() is None:
|
|
205
|
+
import requests
|
|
206
|
+
try:
|
|
207
|
+
response = requests.get('http://127.0.0.1:8888/api/cleanup_devices/')
|
|
208
|
+
if response.status_code == 200:
|
|
209
|
+
logger.info("Devices cleaned up successfully via HTTP API")
|
|
210
|
+
else:
|
|
211
|
+
logger.error(f"Failed to cleanup devices: {response.text}")
|
|
212
|
+
except requests.exceptions.ConnectionError:
|
|
213
|
+
logger.warning("Could not reach HTTP server for device cleanup")
|
|
214
|
+
except Exception as e:
|
|
215
|
+
logger.error(f"Error during device cleanup: {str(e)}")
|
|
216
|
+
else:
|
|
217
|
+
logger.debug("Django server not running, skipping HTTP device cleanup")
|
|
218
|
+
|
|
219
|
+
# Stop the servers
|
|
220
|
+
if self.django_server_process:
|
|
221
|
+
self.django_server_process.terminate()
|
|
222
|
+
self.django_server_process = None
|
|
223
|
+
|
|
224
|
+
if self.daphne_server_process:
|
|
225
|
+
self.daphne_server_process.terminate()
|
|
226
|
+
self.daphne_server_process = None
|
|
227
|
+
|
|
228
|
+
if self.mcp_bridge_process:
|
|
229
|
+
try:
|
|
230
|
+
# Terminate the entire process group so that child processes
|
|
231
|
+
# such as sat_fastmcp are also stopped.
|
|
232
|
+
os.killpg(os.getpgid(self.mcp_bridge_process.pid), signal.SIGTERM)
|
|
233
|
+
except ProcessLookupError:
|
|
234
|
+
# Process already gone
|
|
235
|
+
pass
|
|
236
|
+
except Exception as e:
|
|
237
|
+
logger.error(f"Failed to terminate MCP bridge process group: {e}")
|
|
238
|
+
finally:
|
|
239
|
+
self.mcp_bridge_process = None
|
|
240
|
+
|
|
241
|
+
if self.celery_worker_process:
|
|
242
|
+
self.celery_worker_process.terminate()
|
|
243
|
+
self.celery_worker_process = None
|
|
244
|
+
|
|
245
|
+
if not any([self.django_server_process, self.daphne_server_process,
|
|
246
|
+
getattr(self, 'mcp_bridge_process', None),
|
|
247
|
+
getattr(self, 'celery_worker_process', None)]):
|
|
248
|
+
logger.info("All servers stopped.")
|
|
249
|
+
else:
|
|
250
|
+
logger.error("No servers were running.")
|
|
251
|
+
|
|
252
|
+
except Exception as e:
|
|
253
|
+
logger.error(f"Error stopping servers: {str(e)}")
|
|
254
|
+
logger.debug("Detailed error:", exc_info=True)
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
import cmd2
|
|
4
|
+
from cmd2 import ansi
|
|
5
|
+
from .base_commands import BaseCommands
|
|
6
|
+
from iotsploit_django.tools.input_mgr import Input_Mgr
|
|
7
|
+
from iotsploit_core.core.tool_service import get_firmware_service
|
|
8
|
+
from iotsploit_core.utils import iots_logger
|
|
9
|
+
|
|
10
|
+
logger = iots_logger.get_logger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class FirmwareCommands(BaseCommands):
|
|
14
|
+
"""Firmware-related commands for the SAT Shell"""
|
|
15
|
+
|
|
16
|
+
@cmd2.with_category('Firmware Commands')
|
|
17
|
+
def do_list_firmware(self, arg):
|
|
18
|
+
'List all available firmware'
|
|
19
|
+
try:
|
|
20
|
+
firmware_service = get_firmware_service()
|
|
21
|
+
firmware_list = firmware_service.list_firmware()
|
|
22
|
+
|
|
23
|
+
if not firmware_list:
|
|
24
|
+
self.poutput(ansi.style("No firmware found in registry.", fg=ansi.Fg.YELLOW))
|
|
25
|
+
return
|
|
26
|
+
|
|
27
|
+
self.poutput(ansi.style(f"\nFound {len(firmware_list)} firmware(s):", fg=ansi.Fg.GREEN))
|
|
28
|
+
self.poutput("-" * 80)
|
|
29
|
+
|
|
30
|
+
for firmware in firmware_list:
|
|
31
|
+
name = firmware['name']
|
|
32
|
+
device_type = firmware.get('device_type', 'unknown')
|
|
33
|
+
version = firmware.get('version', 'unknown')
|
|
34
|
+
path = firmware.get('path', 'unknown')
|
|
35
|
+
|
|
36
|
+
# Check if file exists
|
|
37
|
+
from pathlib import Path
|
|
38
|
+
file_exists = Path(path).exists() if path != 'unknown' else False
|
|
39
|
+
status_color = ansi.Fg.GREEN if file_exists else ansi.Fg.RED
|
|
40
|
+
status_text = "✓" if file_exists else "✗"
|
|
41
|
+
|
|
42
|
+
self.poutput(ansi.style(f" {name}:", fg=ansi.Fg.CYAN, bold=True))
|
|
43
|
+
self.poutput(f" Device Type: {device_type}")
|
|
44
|
+
self.poutput(f" Version: {version}")
|
|
45
|
+
self.poutput(f" Path: {path}")
|
|
46
|
+
self.poutput(f" Status: {ansi.style(status_text, fg=status_color)} {'(File exists)' if file_exists else '(File missing)'}")
|
|
47
|
+
|
|
48
|
+
# Show flash options if available
|
|
49
|
+
flash_options = firmware.get('flash_options', {})
|
|
50
|
+
if flash_options:
|
|
51
|
+
self.poutput(f" Flash Options: {flash_options}")
|
|
52
|
+
|
|
53
|
+
self.poutput("")
|
|
54
|
+
|
|
55
|
+
except Exception as e:
|
|
56
|
+
logger.error(f"Error listing firmware: {str(e)}")
|
|
57
|
+
self.poutput(ansi.style(f"Error listing firmware: {str(e)}", fg=ansi.Fg.RED))
|
|
58
|
+
|
|
59
|
+
do_lsfw = do_list_firmware
|
|
60
|
+
|
|
61
|
+
@cmd2.with_category('Firmware Commands')
|
|
62
|
+
def do_add_firmware(self, arg):
|
|
63
|
+
'Add new firmware to the system'
|
|
64
|
+
try:
|
|
65
|
+
# Parse arguments
|
|
66
|
+
args = arg.split()
|
|
67
|
+
if len(args) < 4:
|
|
68
|
+
self.poutput(ansi.style("Usage: add_firmware <name> <path> <device_type> <version> [flash_options_json]", fg=ansi.Fg.YELLOW))
|
|
69
|
+
self.poutput("Example: add_firmware my_esp32 /path/to/firmware.bin esp32 1.0.0")
|
|
70
|
+
return
|
|
71
|
+
|
|
72
|
+
name = args[0]
|
|
73
|
+
path = args[1]
|
|
74
|
+
device_type = args[2]
|
|
75
|
+
version = args[3]
|
|
76
|
+
|
|
77
|
+
# Parse optional flash options
|
|
78
|
+
flash_options = None
|
|
79
|
+
if len(args) > 4:
|
|
80
|
+
import json
|
|
81
|
+
try:
|
|
82
|
+
flash_options = json.loads(' '.join(args[4:]))
|
|
83
|
+
except json.JSONDecodeError:
|
|
84
|
+
self.poutput(ansi.style("Invalid JSON format for flash options", fg=ansi.Fg.RED))
|
|
85
|
+
return
|
|
86
|
+
|
|
87
|
+
firmware_service = get_firmware_service()
|
|
88
|
+
success = firmware_service.add_firmware(name, path, device_type, version, flash_options)
|
|
89
|
+
|
|
90
|
+
if success:
|
|
91
|
+
self.poutput(ansi.style(f"Successfully added firmware: {name}", fg=ansi.Fg.GREEN))
|
|
92
|
+
else:
|
|
93
|
+
self.poutput(ansi.style(f"Failed to add firmware: {name}", fg=ansi.Fg.RED))
|
|
94
|
+
|
|
95
|
+
except Exception as e:
|
|
96
|
+
logger.error(f"Error adding firmware: {str(e)}")
|
|
97
|
+
self.poutput(ansi.style(f"Error adding firmware: {str(e)}", fg=ansi.Fg.RED))
|
|
98
|
+
|
|
99
|
+
do_addfw = do_add_firmware
|
|
100
|
+
|
|
101
|
+
@cmd2.with_category('Firmware Commands')
|
|
102
|
+
def do_flash_firmware(self, arg):
|
|
103
|
+
'Flash firmware to a device'
|
|
104
|
+
try:
|
|
105
|
+
# Parse arguments
|
|
106
|
+
args = arg.split()
|
|
107
|
+
if len(args) < 2:
|
|
108
|
+
self.poutput(ansi.style("Usage: flash_firmware <firmware_name> <device_name> [options_json]", fg=ansi.Fg.YELLOW))
|
|
109
|
+
self.poutput("Example: flash_firmware esp32s3_wifi_penetration_tool esp32")
|
|
110
|
+
return
|
|
111
|
+
|
|
112
|
+
firmware_name = args[0]
|
|
113
|
+
device_name = args[1]
|
|
114
|
+
|
|
115
|
+
# Parse optional options
|
|
116
|
+
options = None
|
|
117
|
+
if len(args) > 2:
|
|
118
|
+
import json
|
|
119
|
+
try:
|
|
120
|
+
options = json.loads(' '.join(args[2:]))
|
|
121
|
+
except json.JSONDecodeError:
|
|
122
|
+
self.poutput(ansi.style("Invalid JSON format for options", fg=ansi.Fg.RED))
|
|
123
|
+
return
|
|
124
|
+
|
|
125
|
+
firmware_service = get_firmware_service()
|
|
126
|
+
|
|
127
|
+
# Check if firmware exists
|
|
128
|
+
firmware_info = firmware_service.get_firmware_info(firmware_name)
|
|
129
|
+
if not firmware_info:
|
|
130
|
+
self.poutput(ansi.style(f"Firmware '{firmware_name}' not found", fg=ansi.Fg.RED))
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
self.poutput(ansi.style(f"Flashing firmware '{firmware_name}' to device '{device_name}'...", fg=ansi.Fg.YELLOW))
|
|
134
|
+
|
|
135
|
+
# Flash the firmware
|
|
136
|
+
success = firmware_service.flash_registered_firmware(firmware_name, options)
|
|
137
|
+
|
|
138
|
+
if success:
|
|
139
|
+
self.poutput(ansi.style(f"Successfully flashed firmware: {firmware_name}", fg=ansi.Fg.GREEN))
|
|
140
|
+
else:
|
|
141
|
+
self.poutput(ansi.style(f"Failed to flash firmware: {firmware_name}", fg=ansi.Fg.RED))
|
|
142
|
+
|
|
143
|
+
except Exception as e:
|
|
144
|
+
logger.error(f"Error flashing firmware: {str(e)}")
|
|
145
|
+
self.poutput(ansi.style(f"Error flashing firmware: {str(e)}", fg=ansi.Fg.RED))
|
|
146
|
+
|
|
147
|
+
do_flashfw = do_flash_firmware
|
|
148
|
+
|
|
149
|
+
@cmd2.with_category('Firmware Commands')
|
|
150
|
+
def do_remove_firmware(self, arg):
|
|
151
|
+
'Remove firmware from the system'
|
|
152
|
+
try:
|
|
153
|
+
if not arg.strip():
|
|
154
|
+
self.poutput(ansi.style("Usage: remove_firmware <firmware_name>", fg=ansi.Fg.YELLOW))
|
|
155
|
+
self.poutput("Example: remove_firmware esp32_blink")
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
firmware_name = arg.strip()
|
|
159
|
+
firmware_service = get_firmware_service()
|
|
160
|
+
|
|
161
|
+
# Check if firmware exists
|
|
162
|
+
firmware_info = firmware_service.get_firmware_info(firmware_name)
|
|
163
|
+
if not firmware_info:
|
|
164
|
+
self.poutput(ansi.style(f"Firmware '{firmware_name}' not found", fg=ansi.Fg.RED))
|
|
165
|
+
return
|
|
166
|
+
|
|
167
|
+
# Confirm removal
|
|
168
|
+
confirm = input(f"Are you sure you want to remove firmware '{firmware_name}'? (y/N): ")
|
|
169
|
+
if confirm.lower() != 'y':
|
|
170
|
+
self.poutput(ansi.style("Firmware removal cancelled", fg=ansi.Fg.YELLOW))
|
|
171
|
+
return
|
|
172
|
+
|
|
173
|
+
success = firmware_service.remove_firmware(firmware_name)
|
|
174
|
+
|
|
175
|
+
if success:
|
|
176
|
+
self.poutput(ansi.style(f"Successfully removed firmware: {firmware_name}", fg=ansi.Fg.GREEN))
|
|
177
|
+
else:
|
|
178
|
+
self.poutput(ansi.style(f"Failed to remove firmware: {firmware_name}", fg=ansi.Fg.RED))
|
|
179
|
+
|
|
180
|
+
except Exception as e:
|
|
181
|
+
logger.error(f"Error removing firmware: {str(e)}")
|
|
182
|
+
self.poutput(ansi.style(f"Error removing firmware: {str(e)}", fg=ansi.Fg.RED))
|
|
183
|
+
|
|
184
|
+
do_rmfw = do_remove_firmware
|
|
185
|
+
|
|
186
|
+
@cmd2.with_category('Firmware Commands')
|
|
187
|
+
def do_download_firmware(self, arg):
|
|
188
|
+
'Download firmware from URL'
|
|
189
|
+
try:
|
|
190
|
+
if not arg.strip():
|
|
191
|
+
self.poutput(ansi.style("Usage: download_firmware <url> [output_path]", fg=ansi.Fg.YELLOW))
|
|
192
|
+
self.poutput("Example: download_firmware https://example.com/firmware.bin")
|
|
193
|
+
return
|
|
194
|
+
|
|
195
|
+
args = arg.split()
|
|
196
|
+
url = args[0]
|
|
197
|
+
output_path = args[1] if len(args) > 1 else None
|
|
198
|
+
|
|
199
|
+
firmware_service = get_firmware_service()
|
|
200
|
+
self.poutput(ansi.style(f"Downloading firmware from: {url}", fg=ansi.Fg.YELLOW))
|
|
201
|
+
|
|
202
|
+
result_path = firmware_service.download_firmware(url, output_path)
|
|
203
|
+
|
|
204
|
+
if result_path:
|
|
205
|
+
self.poutput(ansi.style(f"Successfully downloaded firmware to: {result_path}", fg=ansi.Fg.GREEN))
|
|
206
|
+
else:
|
|
207
|
+
self.poutput(ansi.style("Failed to download firmware", fg=ansi.Fg.RED))
|
|
208
|
+
|
|
209
|
+
except Exception as e:
|
|
210
|
+
logger.error(f"Error downloading firmware: {str(e)}")
|
|
211
|
+
self.poutput(ansi.style(f"Error downloading firmware: {str(e)}", fg=ansi.Fg.RED))
|
|
212
|
+
|
|
213
|
+
do_dlfw = do_download_firmware
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
import cmd2
|
|
4
|
+
from cmd2 import ansi
|
|
5
|
+
import subprocess
|
|
6
|
+
from .base_commands import BaseCommands
|
|
7
|
+
from iotsploit_core.utils import iots_logger
|
|
8
|
+
|
|
9
|
+
logger = iots_logger.get_logger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class LinuxCommands(BaseCommands):
|
|
13
|
+
"""Linux system commands for the SAT Shell"""
|
|
14
|
+
|
|
15
|
+
@cmd2.with_category('Linux Commands')
|
|
16
|
+
def do_ls(self, arg):
|
|
17
|
+
'List directory contents'
|
|
18
|
+
try:
|
|
19
|
+
result = subprocess.run(['ls'] + arg.split(), capture_output=True, text=True)
|
|
20
|
+
self.poutput(result.stdout)
|
|
21
|
+
if result.stderr:
|
|
22
|
+
self.poutput(result.stderr)
|
|
23
|
+
except Exception as e:
|
|
24
|
+
logger.error(f"Error executing ls: {str(e)}")
|
|
25
|
+
|
|
26
|
+
@cmd2.with_category('Linux Commands')
|
|
27
|
+
def do_lsusb(self, arg):
|
|
28
|
+
'List USB devices'
|
|
29
|
+
try:
|
|
30
|
+
result = subprocess.run(['lsusb'] + arg.split(), capture_output=True, text=True)
|
|
31
|
+
self.poutput(result.stdout)
|
|
32
|
+
if result.stderr:
|
|
33
|
+
self.poutput(result.stderr)
|
|
34
|
+
except Exception as e:
|
|
35
|
+
logger.error(f"Error executing lsusb: {str(e)}")
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
|
|
3
|
+
import cmd2
|
|
4
|
+
from cmd2 import ansi
|
|
5
|
+
import time
|
|
6
|
+
from .base_commands import BaseCommands
|
|
7
|
+
from iotsploit_django.tools.wifi_mgr import WiFi_Mgr
|
|
8
|
+
from iotsploit_django.tools.input_mgr import Input_Mgr
|
|
9
|
+
from iotsploit_core.utils import iots_logger
|
|
10
|
+
|
|
11
|
+
logger = iots_logger.get_logger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class NetworkCommands(BaseCommands):
|
|
15
|
+
"""Network-related commands for the SAT Shell"""
|
|
16
|
+
|
|
17
|
+
@cmd2.with_category('Network Commands')
|
|
18
|
+
def do_connect_wifi(self, arg):
|
|
19
|
+
'Connect to WiFi network by providing SSID and password'
|
|
20
|
+
logger.info(ansi.style("WiFi Connection Setup", fg=ansi.Fg.CYAN))
|
|
21
|
+
|
|
22
|
+
# Get SSID from user
|
|
23
|
+
ssid = Input_Mgr.Instance().string_input("Enter WiFi SSID")
|
|
24
|
+
|
|
25
|
+
# Get password from user
|
|
26
|
+
password = Input_Mgr.Instance().string_input("Enter WiFi password")
|
|
27
|
+
|
|
28
|
+
# Attempt to connect
|
|
29
|
+
logger.info(ansi.style(f"Attempting to connect to {ssid}...", fg=ansi.Fg.CYAN))
|
|
30
|
+
WiFi_Mgr.Instance().sta_connect_wifi(ssid, password)
|
|
31
|
+
|
|
32
|
+
# Wait for connection to establish
|
|
33
|
+
time.sleep(2)
|
|
34
|
+
|
|
35
|
+
# Show connection status
|
|
36
|
+
WiFi_Mgr.Instance().status()
|