iotsploit-platforms 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_platforms/__init__.py +10 -0
- iotsploit_platforms/adapters/__init__.py +5 -0
- iotsploit_platforms/adapters/platforms/__init__.py +5 -0
- iotsploit_platforms/adapters/platforms/darwin/__init__.py +7 -0
- iotsploit_platforms/adapters/platforms/darwin/wifi_backend.py +93 -0
- iotsploit_platforms/adapters/platforms/linux/__init__.py +7 -0
- iotsploit_platforms/adapters/platforms/linux/wifi_backend.py +640 -0
- iotsploit_platforms/adapters/platforms/windows/__init__.py +7 -0
- iotsploit_platforms/adapters/platforms/windows/wifi_backend.py +93 -0
- iotsploit_platforms/platforms/__init__.py +21 -0
- iotsploit_platforms/selector.py +132 -0
- iotsploit_platforms-0.0.6.dist-info/METADATA +77 -0
- iotsploit_platforms-0.0.6.dist-info/RECORD +14 -0
- iotsploit_platforms-0.0.6.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""
|
|
2
|
+
IoTSploit Platform Adapters Package.
|
|
3
|
+
|
|
4
|
+
This package provides platform-specific implementations for WiFi, Input, and SSH backends.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from iotsploit_platforms.selector import build_context, get_wifi_backend
|
|
8
|
+
|
|
9
|
+
__version__ = "0.1.0"
|
|
10
|
+
__all__ = ["get_wifi_backend", "build_context"]
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Darwin (macOS) WiFi Backend Implementation.
|
|
3
|
+
|
|
4
|
+
This module provides macOS-specific WiFi backend implementation.
|
|
5
|
+
Currently a placeholder implementation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from typing import List, Optional, Dict, Any, Tuple
|
|
10
|
+
|
|
11
|
+
from iotsploit_core.ports.wifi_backend import WifiBackend
|
|
12
|
+
from iotsploit_core.utils.exceptions import NotSupportedError
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class DarwinWifiBackend(WifiBackend):
|
|
18
|
+
"""
|
|
19
|
+
Darwin (macOS) WiFi backend implementation.
|
|
20
|
+
|
|
21
|
+
This is a placeholder implementation. Full implementation would use
|
|
22
|
+
macOS-specific APIs (e.g., airport, CoreWLAN framework).
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, wifi_iface_name: Optional[str] = None):
|
|
26
|
+
"""
|
|
27
|
+
Initialize Darwin WiFi backend.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
wifi_iface_name: WiFi interface name (optional on macOS)
|
|
31
|
+
"""
|
|
32
|
+
self.wifi_iface_name = wifi_iface_name
|
|
33
|
+
self._wifi_mode = "IDLE"
|
|
34
|
+
|
|
35
|
+
def scan(self) -> List[Dict[str, Any]]:
|
|
36
|
+
"""
|
|
37
|
+
Scan for available WiFi networks.
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
NotSupportedError: macOS WiFi scanning not yet implemented
|
|
41
|
+
"""
|
|
42
|
+
raise NotSupportedError("WiFi scanning is not yet implemented on macOS")
|
|
43
|
+
|
|
44
|
+
def sta_connect(self, ssid: str, passwd: str) -> None:
|
|
45
|
+
"""
|
|
46
|
+
Connect to a WiFi network in station (STA) mode.
|
|
47
|
+
|
|
48
|
+
Raises:
|
|
49
|
+
NotSupportedError: macOS WiFi connection not yet implemented
|
|
50
|
+
"""
|
|
51
|
+
raise NotSupportedError("WiFi station mode connection is not yet implemented on macOS")
|
|
52
|
+
|
|
53
|
+
def sta_disconnect(self) -> None:
|
|
54
|
+
"""
|
|
55
|
+
Disconnect from the current WiFi network in station mode.
|
|
56
|
+
|
|
57
|
+
Raises:
|
|
58
|
+
NotSupportedError: macOS WiFi disconnection not yet implemented
|
|
59
|
+
"""
|
|
60
|
+
raise NotSupportedError("WiFi station mode disconnection is not yet implemented on macOS")
|
|
61
|
+
|
|
62
|
+
def ap_start(self, ssid: Optional[str] = None, passwd: Optional[str] = None, wpa_mode: int = 2) -> Tuple[str, str]:
|
|
63
|
+
"""
|
|
64
|
+
Start WiFi access point (AP) mode.
|
|
65
|
+
|
|
66
|
+
Raises:
|
|
67
|
+
NotSupportedError: macOS does not support WiFi AP mode
|
|
68
|
+
"""
|
|
69
|
+
raise NotSupportedError("WiFi access point mode is not supported on macOS")
|
|
70
|
+
|
|
71
|
+
def ap_stop(self) -> None:
|
|
72
|
+
"""
|
|
73
|
+
Stop WiFi access point (AP) mode.
|
|
74
|
+
|
|
75
|
+
Raises:
|
|
76
|
+
NotSupportedError: macOS does not support WiFi AP mode
|
|
77
|
+
"""
|
|
78
|
+
raise NotSupportedError("WiFi access point mode is not supported on macOS")
|
|
79
|
+
|
|
80
|
+
def status(self) -> Dict[str, Any]:
|
|
81
|
+
"""
|
|
82
|
+
Get current WiFi status.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Dictionary containing current WiFi status information.
|
|
86
|
+
"""
|
|
87
|
+
return {
|
|
88
|
+
"wifi_mode": self._wifi_mode,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# Export the backend class
|
|
93
|
+
wifi_backend = DarwinWifiBackend
|
|
@@ -0,0 +1,640 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Linux WiFi Backend Implementation.
|
|
3
|
+
|
|
4
|
+
This module provides Linux-specific WiFi backend implementation using
|
|
5
|
+
NetworkManager's native libnm API via GObject Introspection.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
import time
|
|
10
|
+
import uuid
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import List, Optional, Dict, Any, Tuple
|
|
13
|
+
|
|
14
|
+
import gi
|
|
15
|
+
gi.require_version('NM', '1.0')
|
|
16
|
+
from gi.repository import NM, GLib
|
|
17
|
+
|
|
18
|
+
from iotsploit_core.ports.wifi_backend import WifiBackend
|
|
19
|
+
from iotsploit_core.utils.exceptions import NotSupportedError
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
# NetworkManager DBus constants (from NM headers / docs)
|
|
24
|
+
_NM_ACTIVE_CONNECTION_STATE_ACTIVATED = 2
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class LinuxWifiBackend(WifiBackend):
|
|
28
|
+
"""
|
|
29
|
+
Linux WiFi backend implementation using NetworkManager.
|
|
30
|
+
|
|
31
|
+
Uses libnm (NetworkManager's native library) for all WiFi operations.
|
|
32
|
+
Supports station mode, access point mode, and network scanning.
|
|
33
|
+
"""
|
|
34
|
+
# Keep consistent with upstream DBus hotspot example
|
|
35
|
+
_HOTSPOT_UUID = "2b0d0f1d-b79d-43af-bde1-71744625642e"
|
|
36
|
+
|
|
37
|
+
def __init__(self, wifi_iface_name: str = "wlan0", forward_eth_name: Optional[str] = None):
|
|
38
|
+
"""
|
|
39
|
+
Initialize NetworkManager WiFi backend.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
wifi_iface_name: WiFi interface name (e.g., "wlan0", "wlp0s20f3")
|
|
43
|
+
forward_eth_name: Ethernet interface for forwarding (used for AP mode NAT)
|
|
44
|
+
"""
|
|
45
|
+
self.wifi_iface_name = wifi_iface_name
|
|
46
|
+
self.forward_eth_name = forward_eth_name
|
|
47
|
+
|
|
48
|
+
self._wifi_mode = "IDLE"
|
|
49
|
+
self._sta_conn_wifi_ssid = ""
|
|
50
|
+
self._sta_conn_wifi_passwd = ""
|
|
51
|
+
self._ap_ssid = ""
|
|
52
|
+
self._ap_passwd = ""
|
|
53
|
+
|
|
54
|
+
# Connection IDs for tracking
|
|
55
|
+
self._active_connection_id: Optional[str] = None
|
|
56
|
+
self._hotspot_connection_id: Optional[str] = None
|
|
57
|
+
# Hotspot connection UUID/path (DBus profile reuse)
|
|
58
|
+
self._hotspot_connection_uuid: Optional[str] = None
|
|
59
|
+
self._hotspot_connection_path: Optional[str] = None
|
|
60
|
+
self._hotspot_active_connection_path: Optional[str] = None
|
|
61
|
+
|
|
62
|
+
# Initialize NetworkManager client
|
|
63
|
+
self._client: Optional[NM.Client] = None
|
|
64
|
+
self._device: Optional[NM.DeviceWifi] = None
|
|
65
|
+
self._init_client()
|
|
66
|
+
|
|
67
|
+
def _init_client(self):
|
|
68
|
+
"""Initialize NetworkManager client and find WiFi device."""
|
|
69
|
+
try:
|
|
70
|
+
self._client = NM.Client.new(None)
|
|
71
|
+
self._device = self._find_wifi_device()
|
|
72
|
+
logger.info(f"NetworkManager client initialized for {self.wifi_iface_name}")
|
|
73
|
+
except Exception as e:
|
|
74
|
+
logger.error(f"Failed to initialize NetworkManager client: {e}")
|
|
75
|
+
raise NotSupportedError(f"NetworkManager initialization failed: {e}")
|
|
76
|
+
|
|
77
|
+
def _find_wifi_device(self) -> NM.DeviceWifi:
|
|
78
|
+
"""Find the WiFi device by interface name."""
|
|
79
|
+
devices = self._client.get_devices()
|
|
80
|
+
for device in devices:
|
|
81
|
+
if device.get_iface() == self.wifi_iface_name:
|
|
82
|
+
if isinstance(device, NM.DeviceWifi):
|
|
83
|
+
return device
|
|
84
|
+
else:
|
|
85
|
+
raise NotSupportedError(f"{self.wifi_iface_name} is not a WiFi device")
|
|
86
|
+
|
|
87
|
+
# List available WiFi devices for debugging
|
|
88
|
+
wifi_devices = [d.get_iface() for d in devices if isinstance(d, NM.DeviceWifi)]
|
|
89
|
+
raise NotSupportedError(
|
|
90
|
+
f"WiFi device {self.wifi_iface_name} not found. "
|
|
91
|
+
f"Available WiFi devices: {wifi_devices}"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
def _get_security_flags_string(self, ap: NM.AccessPoint) -> str:
|
|
95
|
+
"""Convert AP security flags to string."""
|
|
96
|
+
wpa_flags = ap.get_wpa_flags()
|
|
97
|
+
rsn_flags = ap.get_rsn_flags()
|
|
98
|
+
|
|
99
|
+
# Use integer constants instead of enum attributes for compatibility
|
|
100
|
+
# KEY_MGMT_PSK = 0x00000002 (from NetworkManager headers)
|
|
101
|
+
KEY_MGMT_PSK = 0x00000002
|
|
102
|
+
|
|
103
|
+
if rsn_flags & KEY_MGMT_PSK:
|
|
104
|
+
return "WPA2"
|
|
105
|
+
elif wpa_flags & KEY_MGMT_PSK:
|
|
106
|
+
return "WPA"
|
|
107
|
+
elif ap.get_flags() & 0x0001: # PRIVACY flag
|
|
108
|
+
return "WEP"
|
|
109
|
+
else:
|
|
110
|
+
return "OPEN"
|
|
111
|
+
|
|
112
|
+
def _wait_for_connection(self, timeout: int = 30) -> bool:
|
|
113
|
+
"""Wait for connection to be established."""
|
|
114
|
+
start_time = time.time()
|
|
115
|
+
while time.time() - start_time < timeout:
|
|
116
|
+
state = self._device.get_state()
|
|
117
|
+
if state == NM.DeviceState.ACTIVATED:
|
|
118
|
+
return True
|
|
119
|
+
elif state in (NM.DeviceState.FAILED, NM.DeviceState.DISCONNECTED):
|
|
120
|
+
return False
|
|
121
|
+
time.sleep(0.5)
|
|
122
|
+
return False
|
|
123
|
+
|
|
124
|
+
def _find_connection_by_id(self, conn_id: str) -> Optional[NM.RemoteConnection]:
|
|
125
|
+
"""Find a connection profile by ID."""
|
|
126
|
+
connections = self._client.get_connections()
|
|
127
|
+
for conn in connections:
|
|
128
|
+
if conn.get_id() == conn_id:
|
|
129
|
+
return conn
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
def _delete_connection_by_id(self, conn_id: str) -> bool:
|
|
133
|
+
"""Delete a connection profile by ID."""
|
|
134
|
+
conn = self._find_connection_by_id(conn_id)
|
|
135
|
+
if conn:
|
|
136
|
+
try:
|
|
137
|
+
conn.delete(None)
|
|
138
|
+
logger.info(f"Deleted connection: {conn_id}")
|
|
139
|
+
return True
|
|
140
|
+
except Exception as e:
|
|
141
|
+
logger.error(f"Failed to delete connection {conn_id}: {e}")
|
|
142
|
+
return False
|
|
143
|
+
|
|
144
|
+
def _get_or_make_hotspot_uuid(self) -> str:
|
|
145
|
+
"""
|
|
146
|
+
Return a stable UUID for the hotspot profile.
|
|
147
|
+
|
|
148
|
+
We intentionally keep the UUID stable across runs so we can reuse the
|
|
149
|
+
same NetworkManager connection profile (per the upstream DBus example).
|
|
150
|
+
"""
|
|
151
|
+
# Force using the same UUID as the provided example for consistency.
|
|
152
|
+
self._hotspot_connection_uuid = self._HOTSPOT_UUID
|
|
153
|
+
return self._hotspot_connection_uuid
|
|
154
|
+
|
|
155
|
+
def _ensure_hotspot_profile_dbus(
|
|
156
|
+
self,
|
|
157
|
+
ssid: str,
|
|
158
|
+
passwd: str,
|
|
159
|
+
band: str = "bg",
|
|
160
|
+
channel: int = 6,
|
|
161
|
+
) -> Tuple["dbus.SystemBus", str, str]:
|
|
162
|
+
"""
|
|
163
|
+
Ensure a NetworkManager hotspot profile exists (DBus), return (bus, devpath, connection_path).
|
|
164
|
+
"""
|
|
165
|
+
try:
|
|
166
|
+
import dbus # type: ignore
|
|
167
|
+
except Exception as e: # pragma: no cover
|
|
168
|
+
raise NotSupportedError(f"dbus-python not available: {e}")
|
|
169
|
+
|
|
170
|
+
hotspot_uuid = self._get_or_make_hotspot_uuid()
|
|
171
|
+
|
|
172
|
+
# Build settings dicts (matches upstream example structure)
|
|
173
|
+
s_con = dbus.Dictionary(
|
|
174
|
+
{
|
|
175
|
+
"type": "802-11-wireless",
|
|
176
|
+
"uuid": hotspot_uuid,
|
|
177
|
+
# Keep a stable, human-readable id in NetworkManager UI
|
|
178
|
+
"id": f"iotsploit-hotspot-{self.wifi_iface_name}",
|
|
179
|
+
"autoconnect": dbus.Boolean(False),
|
|
180
|
+
}
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
s_wifi = dbus.Dictionary(
|
|
184
|
+
{
|
|
185
|
+
"ssid": dbus.ByteArray(ssid.encode("utf-8")),
|
|
186
|
+
"mode": "ap",
|
|
187
|
+
"band": band,
|
|
188
|
+
"channel": dbus.UInt32(int(channel)),
|
|
189
|
+
}
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
s_ip4 = dbus.Dictionary({"method": "shared"})
|
|
193
|
+
s_ip6 = dbus.Dictionary({"method": "ignore"})
|
|
194
|
+
|
|
195
|
+
con: Dict[str, Any] = {
|
|
196
|
+
"connection": s_con,
|
|
197
|
+
"802-11-wireless": s_wifi,
|
|
198
|
+
"ipv4": s_ip4,
|
|
199
|
+
"ipv6": s_ip6,
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
# WPA2-PSK (same as example). If passwd empty, we create an open AP.
|
|
203
|
+
if passwd:
|
|
204
|
+
s_wsec = dbus.Dictionary({"key-mgmt": "wpa-psk", "psk": passwd})
|
|
205
|
+
con["802-11-wireless-security"] = s_wsec
|
|
206
|
+
|
|
207
|
+
con = dbus.Dictionary(con)
|
|
208
|
+
|
|
209
|
+
bus = dbus.SystemBus()
|
|
210
|
+
|
|
211
|
+
# Find or create the connection profile under Settings
|
|
212
|
+
settings_proxy = bus.get_object(
|
|
213
|
+
"org.freedesktop.NetworkManager",
|
|
214
|
+
"/org/freedesktop/NetworkManager/Settings",
|
|
215
|
+
)
|
|
216
|
+
settings = dbus.Interface(settings_proxy, "org.freedesktop.NetworkManager.Settings")
|
|
217
|
+
|
|
218
|
+
connection_path = None
|
|
219
|
+
for path in settings.ListConnections():
|
|
220
|
+
proxy = bus.get_object("org.freedesktop.NetworkManager", path)
|
|
221
|
+
settings_connection = dbus.Interface(
|
|
222
|
+
proxy,
|
|
223
|
+
"org.freedesktop.NetworkManager.Settings.Connection",
|
|
224
|
+
)
|
|
225
|
+
config = settings_connection.GetSettings()
|
|
226
|
+
if config.get("connection", {}).get("uuid") == hotspot_uuid:
|
|
227
|
+
connection_path = path
|
|
228
|
+
break
|
|
229
|
+
|
|
230
|
+
if not connection_path:
|
|
231
|
+
connection_path = settings.AddConnection(con)
|
|
232
|
+
logger.info(f"Created hotspot profile via DBus: uuid={hotspot_uuid} path={connection_path}")
|
|
233
|
+
else:
|
|
234
|
+
logger.info(f"Reusing hotspot profile via DBus: uuid={hotspot_uuid} path={connection_path}")
|
|
235
|
+
|
|
236
|
+
# Resolve device path by interface name
|
|
237
|
+
nm_proxy = bus.get_object("org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager")
|
|
238
|
+
nm = dbus.Interface(nm_proxy, "org.freedesktop.NetworkManager")
|
|
239
|
+
devpath = nm.GetDeviceByIpIface(self.wifi_iface_name)
|
|
240
|
+
|
|
241
|
+
# Cache for stop/status usage
|
|
242
|
+
self._hotspot_connection_path = str(connection_path)
|
|
243
|
+
|
|
244
|
+
return bus, str(devpath), str(connection_path)
|
|
245
|
+
|
|
246
|
+
def _wait_hotspot_activated_dbus(self, bus: "dbus.SystemBus", acpath: str, timeout: int = 10) -> bool:
|
|
247
|
+
"""Wait until the active connection reaches ACTIVATED (DBus)."""
|
|
248
|
+
import dbus # type: ignore
|
|
249
|
+
|
|
250
|
+
proxy = bus.get_object("org.freedesktop.NetworkManager", acpath)
|
|
251
|
+
active_props = dbus.Interface(proxy, "org.freedesktop.DBus.Properties")
|
|
252
|
+
|
|
253
|
+
start = time.time()
|
|
254
|
+
while time.time() < start + timeout:
|
|
255
|
+
try:
|
|
256
|
+
state = int(
|
|
257
|
+
active_props.Get(
|
|
258
|
+
"org.freedesktop.NetworkManager.Connection.Active",
|
|
259
|
+
"State",
|
|
260
|
+
)
|
|
261
|
+
)
|
|
262
|
+
except Exception:
|
|
263
|
+
state = -1
|
|
264
|
+
if state == _NM_ACTIVE_CONNECTION_STATE_ACTIVATED:
|
|
265
|
+
return True
|
|
266
|
+
time.sleep(1)
|
|
267
|
+
|
|
268
|
+
return False
|
|
269
|
+
|
|
270
|
+
def _get_ap_clients(self) -> List[Dict[str, Any]]:
|
|
271
|
+
"""
|
|
272
|
+
Get connected clients from DHCP leases.
|
|
273
|
+
|
|
274
|
+
NetworkManager uses dnsmasq for DHCP in shared mode.
|
|
275
|
+
This method parses the DHCP lease files to get connected clients.
|
|
276
|
+
"""
|
|
277
|
+
client_list = []
|
|
278
|
+
try:
|
|
279
|
+
# NetworkManager stores leases in /var/lib/NetworkManager/dnsmasq-*.leases
|
|
280
|
+
lease_dir = Path("/var/lib/NetworkManager")
|
|
281
|
+
if lease_dir.exists():
|
|
282
|
+
lease_files = list(lease_dir.glob("dnsmasq-*.leases"))
|
|
283
|
+
for lease_file in lease_files:
|
|
284
|
+
try:
|
|
285
|
+
with open(lease_file, 'r') as f:
|
|
286
|
+
for line in f:
|
|
287
|
+
parts = line.strip().split()
|
|
288
|
+
if len(parts) >= 4:
|
|
289
|
+
client_list.append({
|
|
290
|
+
"mac": parts[1],
|
|
291
|
+
"ip": parts[2],
|
|
292
|
+
"name": parts[3] if len(parts) > 3 else "",
|
|
293
|
+
})
|
|
294
|
+
except Exception as e:
|
|
295
|
+
logger.warning(f"Failed to read lease file {lease_file}: {e}")
|
|
296
|
+
|
|
297
|
+
# Fallback: try traditional dnsmasq location
|
|
298
|
+
if not client_list:
|
|
299
|
+
traditional_lease = Path("/var/lib/misc/dnsmasq.leases")
|
|
300
|
+
if traditional_lease.exists():
|
|
301
|
+
try:
|
|
302
|
+
with open(traditional_lease, 'r') as f:
|
|
303
|
+
for line in f:
|
|
304
|
+
parts = line.strip().split()
|
|
305
|
+
if len(parts) >= 4:
|
|
306
|
+
client_list.append({
|
|
307
|
+
"mac": parts[1],
|
|
308
|
+
"ip": parts[2],
|
|
309
|
+
"name": parts[3] if len(parts) > 3 else "",
|
|
310
|
+
})
|
|
311
|
+
except Exception as e:
|
|
312
|
+
logger.warning(f"Failed to read traditional lease file: {e}")
|
|
313
|
+
except Exception as e:
|
|
314
|
+
logger.warning(f"Failed to get AP clients from DHCP leases: {e}")
|
|
315
|
+
|
|
316
|
+
return client_list
|
|
317
|
+
|
|
318
|
+
# ==================== Public API ====================
|
|
319
|
+
|
|
320
|
+
def scan(self) -> List[Dict[str, Any]]:
|
|
321
|
+
"""
|
|
322
|
+
Scan for available WiFi networks.
|
|
323
|
+
|
|
324
|
+
Returns:
|
|
325
|
+
List of dictionaries containing WiFi network information.
|
|
326
|
+
"""
|
|
327
|
+
logger.info("Start WiFi scan via NetworkManager")
|
|
328
|
+
|
|
329
|
+
# Request a new scan
|
|
330
|
+
try:
|
|
331
|
+
self._device.request_scan(None)
|
|
332
|
+
except Exception as e:
|
|
333
|
+
logger.warning(f"Scan request failed (may already be scanning): {e}")
|
|
334
|
+
|
|
335
|
+
# Wait for scan to complete
|
|
336
|
+
time.sleep(3)
|
|
337
|
+
|
|
338
|
+
# Get access points
|
|
339
|
+
access_points = self._device.get_access_points()
|
|
340
|
+
|
|
341
|
+
result_list = []
|
|
342
|
+
seen_ssids = set() # Deduplicate by SSID
|
|
343
|
+
|
|
344
|
+
for ap in access_points:
|
|
345
|
+
ssid_bytes = ap.get_ssid()
|
|
346
|
+
if ssid_bytes is None:
|
|
347
|
+
continue
|
|
348
|
+
|
|
349
|
+
ssid = ssid_bytes.get_data().decode('utf-8', errors='ignore')
|
|
350
|
+
if not ssid or ssid in seen_ssids:
|
|
351
|
+
continue
|
|
352
|
+
|
|
353
|
+
seen_ssids.add(ssid)
|
|
354
|
+
|
|
355
|
+
result_list.append({
|
|
356
|
+
"ssid": ssid,
|
|
357
|
+
"bssid": ap.get_bssid(),
|
|
358
|
+
"signal": ap.get_strength(),
|
|
359
|
+
"security": self._get_security_flags_string(ap),
|
|
360
|
+
"frequency": ap.get_frequency(),
|
|
361
|
+
})
|
|
362
|
+
logger.info(f"Found WiFi: {ssid} ({ap.get_bssid()})")
|
|
363
|
+
|
|
364
|
+
logger.info(f"Scan complete. Found {len(result_list)} networks.")
|
|
365
|
+
return result_list
|
|
366
|
+
|
|
367
|
+
def sta_connect(self, ssid: str, passwd: str) -> None:
|
|
368
|
+
"""
|
|
369
|
+
Connect to a WiFi network in station (STA) mode.
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
ssid: Network SSID to connect to
|
|
373
|
+
passwd: Network password (empty string for open networks)
|
|
374
|
+
"""
|
|
375
|
+
logger.info(f"Connecting to WiFi: {ssid}")
|
|
376
|
+
|
|
377
|
+
# Check if already connected to this network
|
|
378
|
+
if (self._wifi_mode == "STA" and
|
|
379
|
+
self._sta_conn_wifi_ssid == ssid and
|
|
380
|
+
self._sta_conn_wifi_passwd == passwd):
|
|
381
|
+
if self._device.get_state() == NM.DeviceState.ACTIVATED:
|
|
382
|
+
logger.info(f"Already connected to {ssid}, skipping")
|
|
383
|
+
return
|
|
384
|
+
|
|
385
|
+
# Disconnect any existing connection first
|
|
386
|
+
self.sta_disconnect()
|
|
387
|
+
|
|
388
|
+
# Store connection info
|
|
389
|
+
self._sta_conn_wifi_ssid = ssid
|
|
390
|
+
self._sta_conn_wifi_passwd = passwd
|
|
391
|
+
|
|
392
|
+
# Create connection profile
|
|
393
|
+
connection_id = f"iotsploit-{ssid}-{uuid.uuid4().hex[:8]}"
|
|
394
|
+
|
|
395
|
+
# Build connection settings
|
|
396
|
+
connection = NM.SimpleConnection.new()
|
|
397
|
+
|
|
398
|
+
# Connection settings
|
|
399
|
+
s_con = NM.SettingConnection.new()
|
|
400
|
+
s_con.set_property(NM.SETTING_CONNECTION_ID, connection_id)
|
|
401
|
+
s_con.set_property(NM.SETTING_CONNECTION_UUID, str(uuid.uuid4()))
|
|
402
|
+
s_con.set_property(NM.SETTING_CONNECTION_TYPE, "802-11-wireless")
|
|
403
|
+
s_con.set_property(NM.SETTING_CONNECTION_AUTOCONNECT, False)
|
|
404
|
+
connection.add_setting(s_con)
|
|
405
|
+
|
|
406
|
+
# Wireless settings
|
|
407
|
+
s_wifi = NM.SettingWireless.new()
|
|
408
|
+
s_wifi.set_property(NM.SETTING_WIRELESS_SSID, GLib.Bytes.new(ssid.encode('utf-8')))
|
|
409
|
+
s_wifi.set_property(NM.SETTING_WIRELESS_MODE, "infrastructure")
|
|
410
|
+
connection.add_setting(s_wifi)
|
|
411
|
+
|
|
412
|
+
# Security settings (if password provided)
|
|
413
|
+
if passwd:
|
|
414
|
+
s_wifi_sec = NM.SettingWirelessSecurity.new()
|
|
415
|
+
s_wifi_sec.set_property(NM.SETTING_WIRELESS_SECURITY_KEY_MGMT, "wpa-psk")
|
|
416
|
+
s_wifi_sec.set_property(NM.SETTING_WIRELESS_SECURITY_PSK, passwd)
|
|
417
|
+
connection.add_setting(s_wifi_sec)
|
|
418
|
+
|
|
419
|
+
# IPv4 settings (auto/DHCP)
|
|
420
|
+
s_ip4 = NM.SettingIP4Config.new()
|
|
421
|
+
s_ip4.set_property(NM.SETTING_IP_CONFIG_METHOD, "auto")
|
|
422
|
+
connection.add_setting(s_ip4)
|
|
423
|
+
|
|
424
|
+
# IPv6 settings
|
|
425
|
+
s_ip6 = NM.SettingIP6Config.new()
|
|
426
|
+
s_ip6.set_property(NM.SETTING_IP_CONFIG_METHOD, "auto")
|
|
427
|
+
connection.add_setting(s_ip6)
|
|
428
|
+
|
|
429
|
+
try:
|
|
430
|
+
# Add and activate connection
|
|
431
|
+
self._client.add_and_activate_connection(
|
|
432
|
+
connection,
|
|
433
|
+
self._device,
|
|
434
|
+
None, # specific_object (AP path, optional)
|
|
435
|
+
None # cancellable
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
self._active_connection_id = connection_id
|
|
439
|
+
self._wifi_mode = "STA"
|
|
440
|
+
|
|
441
|
+
# Wait for connection
|
|
442
|
+
if self._wait_for_connection(timeout=30):
|
|
443
|
+
logger.info(f"Successfully connected to {ssid}")
|
|
444
|
+
else:
|
|
445
|
+
logger.error(f"Connection to {ssid} timed out or failed")
|
|
446
|
+
raise ConnectionError(f"Failed to connect to {ssid}")
|
|
447
|
+
|
|
448
|
+
except Exception as e:
|
|
449
|
+
logger.error(f"Failed to connect to {ssid}: {e}")
|
|
450
|
+
self._wifi_mode = "IDLE"
|
|
451
|
+
raise ConnectionError(f"Failed to connect to {ssid}: {e}")
|
|
452
|
+
|
|
453
|
+
def sta_disconnect(self) -> None:
|
|
454
|
+
"""
|
|
455
|
+
Disconnect from the current WiFi network in station mode.
|
|
456
|
+
"""
|
|
457
|
+
logger.info("Disconnecting from WiFi")
|
|
458
|
+
|
|
459
|
+
if self._wifi_mode != "STA":
|
|
460
|
+
logger.info(f"Not in STA mode (current: {self._wifi_mode}), skipping disconnect")
|
|
461
|
+
return
|
|
462
|
+
|
|
463
|
+
try:
|
|
464
|
+
# Disconnect the device
|
|
465
|
+
self._device.disconnect(None)
|
|
466
|
+
|
|
467
|
+
# Wait for disconnection
|
|
468
|
+
time.sleep(1)
|
|
469
|
+
|
|
470
|
+
# Clean up connection profile
|
|
471
|
+
if self._active_connection_id:
|
|
472
|
+
self._delete_connection_by_id(self._active_connection_id)
|
|
473
|
+
self._active_connection_id = None
|
|
474
|
+
|
|
475
|
+
self._wifi_mode = "IDLE"
|
|
476
|
+
self._sta_conn_wifi_ssid = ""
|
|
477
|
+
self._sta_conn_wifi_passwd = ""
|
|
478
|
+
|
|
479
|
+
logger.info("Disconnected from WiFi")
|
|
480
|
+
|
|
481
|
+
except Exception as e:
|
|
482
|
+
logger.error(f"Error during disconnect: {e}")
|
|
483
|
+
self._wifi_mode = "IDLE"
|
|
484
|
+
|
|
485
|
+
def ap_start(self, ssid: Optional[str] = None, passwd: Optional[str] = None, wpa_mode: int = 2) -> Tuple[str, str]:
|
|
486
|
+
"""
|
|
487
|
+
Start WiFi access point (AP) mode using NetworkManager hotspot.
|
|
488
|
+
|
|
489
|
+
Args:
|
|
490
|
+
ssid: AP SSID (if None, a default will be generated)
|
|
491
|
+
passwd: AP password (if None, a default will be used)
|
|
492
|
+
wpa_mode: WPA mode (ignored, NetworkManager uses WPA2 by default)
|
|
493
|
+
|
|
494
|
+
Returns:
|
|
495
|
+
Tuple of (ssid, passwd) that were actually used
|
|
496
|
+
"""
|
|
497
|
+
# First disconnect any existing connection
|
|
498
|
+
self.sta_disconnect()
|
|
499
|
+
self.ap_stop()
|
|
500
|
+
|
|
501
|
+
# Generate default SSID if not provided
|
|
502
|
+
if ssid is None:
|
|
503
|
+
hw_addr = self._device.get_hw_address()
|
|
504
|
+
ssid = f"SAT_{hw_addr[-5:-3]}{hw_addr[-2:]}"
|
|
505
|
+
|
|
506
|
+
if passwd is None:
|
|
507
|
+
passwd = "12345678"
|
|
508
|
+
|
|
509
|
+
logger.info(f"Starting AP mode: SSID={ssid}")
|
|
510
|
+
|
|
511
|
+
try:
|
|
512
|
+
import dbus # type: ignore
|
|
513
|
+
from dbus.exceptions import DBusException # type: ignore
|
|
514
|
+
|
|
515
|
+
bus, devpath, connection_path = self._ensure_hotspot_profile_dbus(
|
|
516
|
+
ssid=ssid,
|
|
517
|
+
passwd=passwd,
|
|
518
|
+
band="bg",
|
|
519
|
+
channel=6,
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
nm_proxy = bus.get_object("org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager")
|
|
523
|
+
nm = dbus.Interface(nm_proxy, "org.freedesktop.NetworkManager")
|
|
524
|
+
|
|
525
|
+
# ActivateConnection(connection, device, specific_object="/")
|
|
526
|
+
acpath = nm.ActivateConnection(connection_path, devpath, "/")
|
|
527
|
+
self._hotspot_active_connection_path = str(acpath)
|
|
528
|
+
|
|
529
|
+
if not self._wait_hotspot_activated_dbus(bus, str(acpath), timeout=10):
|
|
530
|
+
raise NotSupportedError("Failed to start access point (timeout waiting for ACTIVATED)")
|
|
531
|
+
|
|
532
|
+
logger.info(f"AP started successfully via DBus: {ssid}")
|
|
533
|
+
|
|
534
|
+
except DBusException as e:
|
|
535
|
+
# Common in headless/CLI: polkit cannot prompt -> PermissionDenied
|
|
536
|
+
dbus_name = ""
|
|
537
|
+
try:
|
|
538
|
+
dbus_name = e.get_dbus_name() or ""
|
|
539
|
+
except Exception:
|
|
540
|
+
dbus_name = ""
|
|
541
|
+
|
|
542
|
+
if dbus_name.endswith(".PermissionDenied") or "PermissionDenied" in str(e):
|
|
543
|
+
self._wifi_mode = "IDLE"
|
|
544
|
+
raise NotSupportedError(
|
|
545
|
+
"启动热点被 NetworkManager 拒绝授权:Not authorized to share connections via wifi.\n"
|
|
546
|
+
"这通常需要 polkit 授权(桌面环境会弹窗)或以 root 运行。\n"
|
|
547
|
+
"如果你是在终端/无图形界面运行,请用 sudo 运行当前程序/命令再试。"
|
|
548
|
+
) from e
|
|
549
|
+
|
|
550
|
+
self._wifi_mode = "IDLE"
|
|
551
|
+
raise NotSupportedError(f"Failed to start AP mode via DBus: {e}") from e
|
|
552
|
+
except Exception as e:
|
|
553
|
+
self._wifi_mode = "IDLE"
|
|
554
|
+
raise NotSupportedError(f"Failed to start AP mode via DBus: {e}") from e
|
|
555
|
+
|
|
556
|
+
# Update local state (common for both DBus/libnm paths)
|
|
557
|
+
self._ap_ssid = ssid
|
|
558
|
+
self._ap_passwd = passwd
|
|
559
|
+
self._wifi_mode = "AP"
|
|
560
|
+
return ssid, passwd
|
|
561
|
+
|
|
562
|
+
def ap_stop(self) -> None:
|
|
563
|
+
"""
|
|
564
|
+
Stop WiFi access point (AP) mode.
|
|
565
|
+
"""
|
|
566
|
+
logger.info("Stopping AP mode")
|
|
567
|
+
|
|
568
|
+
if self._wifi_mode != "AP":
|
|
569
|
+
logger.info(f"Not in AP mode (current: {self._wifi_mode}), skipping")
|
|
570
|
+
return
|
|
571
|
+
|
|
572
|
+
try:
|
|
573
|
+
import dbus # type: ignore
|
|
574
|
+
|
|
575
|
+
bus = dbus.SystemBus()
|
|
576
|
+
nm_proxy = bus.get_object("org.freedesktop.NetworkManager", "/org/freedesktop/NetworkManager")
|
|
577
|
+
nm = dbus.Interface(nm_proxy, "org.freedesktop.NetworkManager")
|
|
578
|
+
devpath = nm.GetDeviceByIpIface(self.wifi_iface_name)
|
|
579
|
+
|
|
580
|
+
proxy = bus.get_object("org.freedesktop.NetworkManager", devpath)
|
|
581
|
+
device = dbus.Interface(proxy, "org.freedesktop.NetworkManager.Device")
|
|
582
|
+
device.Disconnect()
|
|
583
|
+
|
|
584
|
+
time.sleep(1)
|
|
585
|
+
|
|
586
|
+
# 重要:按 DBus 示例逻辑,默认不删除 hotspot profile,方便下次复用
|
|
587
|
+
self._hotspot_active_connection_path = None
|
|
588
|
+
|
|
589
|
+
self._wifi_mode = "IDLE"
|
|
590
|
+
self._ap_ssid = ""
|
|
591
|
+
self._ap_passwd = ""
|
|
592
|
+
|
|
593
|
+
logger.info("AP stopped successfully")
|
|
594
|
+
|
|
595
|
+
except Exception as e:
|
|
596
|
+
logger.error(f"Error stopping AP: {e}")
|
|
597
|
+
self._wifi_mode = "IDLE"
|
|
598
|
+
|
|
599
|
+
def status(self) -> Dict[str, Any]:
|
|
600
|
+
"""
|
|
601
|
+
Get current WiFi status.
|
|
602
|
+
|
|
603
|
+
Returns:
|
|
604
|
+
Dictionary containing current WiFi status information.
|
|
605
|
+
"""
|
|
606
|
+
status_dict = {
|
|
607
|
+
"wifi_mode": self._wifi_mode,
|
|
608
|
+
"device_state": str(self._device.get_state().value_nick),
|
|
609
|
+
"hw_address": self._device.get_hw_address(),
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
if self._wifi_mode == "STA":
|
|
613
|
+
status_dict["sta_conn_wifi_ssid"] = self._sta_conn_wifi_ssid
|
|
614
|
+
status_dict["sta_conn_wifi_passwd"] = self._sta_conn_wifi_passwd
|
|
615
|
+
|
|
616
|
+
# Get active connection info
|
|
617
|
+
active_conn = self._device.get_active_connection()
|
|
618
|
+
if active_conn:
|
|
619
|
+
ip4_config = active_conn.get_ip4_config()
|
|
620
|
+
if ip4_config:
|
|
621
|
+
addresses = ip4_config.get_addresses()
|
|
622
|
+
if addresses:
|
|
623
|
+
status_dict["sta_status"] = {
|
|
624
|
+
"ip_address": addresses[0].get_address(),
|
|
625
|
+
"state": active_conn.get_state().value_nick,
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
if self._wifi_mode == "AP":
|
|
629
|
+
status_dict["ap_ssid"] = self._ap_ssid
|
|
630
|
+
status_dict["ap_passwd"] = self._ap_passwd
|
|
631
|
+
|
|
632
|
+
# Get connected clients from DHCP leases
|
|
633
|
+
status_dict["client_list"] = self._get_ap_clients()
|
|
634
|
+
|
|
635
|
+
logger.info(f"Status: {status_dict}")
|
|
636
|
+
return status_dict
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
# Export the backend class
|
|
640
|
+
wifi_backend = LinuxWifiBackend
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Windows WiFi Backend Implementation.
|
|
3
|
+
|
|
4
|
+
This module provides Windows-specific WiFi backend implementation.
|
|
5
|
+
Currently a placeholder implementation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from typing import List, Optional, Dict, Any, Tuple
|
|
10
|
+
|
|
11
|
+
from iotsploit_core.ports.wifi_backend import WifiBackend
|
|
12
|
+
from iotsploit_core.utils.exceptions import NotSupportedError
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class WindowsWifiBackend(WifiBackend):
|
|
18
|
+
"""
|
|
19
|
+
Windows WiFi backend implementation.
|
|
20
|
+
|
|
21
|
+
This is a placeholder implementation. Full implementation would use
|
|
22
|
+
Windows-specific APIs (e.g., netsh, Windows WLAN API).
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, wifi_iface_name: Optional[str] = None):
|
|
26
|
+
"""
|
|
27
|
+
Initialize Windows WiFi backend.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
wifi_iface_name: WiFi interface name (optional on Windows)
|
|
31
|
+
"""
|
|
32
|
+
self.wifi_iface_name = wifi_iface_name
|
|
33
|
+
self._wifi_mode = "IDLE"
|
|
34
|
+
|
|
35
|
+
def scan(self) -> List[Dict[str, Any]]:
|
|
36
|
+
"""
|
|
37
|
+
Scan for available WiFi networks.
|
|
38
|
+
|
|
39
|
+
Raises:
|
|
40
|
+
NotSupportedError: Windows WiFi scanning not yet implemented
|
|
41
|
+
"""
|
|
42
|
+
raise NotSupportedError("WiFi scanning is not yet implemented on Windows")
|
|
43
|
+
|
|
44
|
+
def sta_connect(self, ssid: str, passwd: str) -> None:
|
|
45
|
+
"""
|
|
46
|
+
Connect to a WiFi network in station (STA) mode.
|
|
47
|
+
|
|
48
|
+
Raises:
|
|
49
|
+
NotSupportedError: Windows WiFi connection not yet implemented
|
|
50
|
+
"""
|
|
51
|
+
raise NotSupportedError("WiFi station mode connection is not yet implemented on Windows")
|
|
52
|
+
|
|
53
|
+
def sta_disconnect(self) -> None:
|
|
54
|
+
"""
|
|
55
|
+
Disconnect from the current WiFi network in station mode.
|
|
56
|
+
|
|
57
|
+
Raises:
|
|
58
|
+
NotSupportedError: Windows WiFi disconnection not yet implemented
|
|
59
|
+
"""
|
|
60
|
+
raise NotSupportedError("WiFi station mode disconnection is not yet implemented on Windows")
|
|
61
|
+
|
|
62
|
+
def ap_start(self, ssid: Optional[str] = None, passwd: Optional[str] = None, wpa_mode: int = 2) -> Tuple[str, str]:
|
|
63
|
+
"""
|
|
64
|
+
Start WiFi access point (AP) mode.
|
|
65
|
+
|
|
66
|
+
Raises:
|
|
67
|
+
NotSupportedError: Windows does not support WiFi AP mode
|
|
68
|
+
"""
|
|
69
|
+
raise NotSupportedError("WiFi access point mode is not supported on Windows")
|
|
70
|
+
|
|
71
|
+
def ap_stop(self) -> None:
|
|
72
|
+
"""
|
|
73
|
+
Stop WiFi access point (AP) mode.
|
|
74
|
+
|
|
75
|
+
Raises:
|
|
76
|
+
NotSupportedError: Windows does not support WiFi AP mode
|
|
77
|
+
"""
|
|
78
|
+
raise NotSupportedError("WiFi access point mode is not supported on Windows")
|
|
79
|
+
|
|
80
|
+
def status(self) -> Dict[str, Any]:
|
|
81
|
+
"""
|
|
82
|
+
Get current WiFi status.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Dictionary containing current WiFi status information.
|
|
86
|
+
"""
|
|
87
|
+
return {
|
|
88
|
+
"wifi_mode": self._wifi_mode,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# Export the backend class
|
|
93
|
+
wifi_backend = WindowsWifiBackend
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Platform Distribution Module (Scapy-Style).
|
|
3
|
+
|
|
4
|
+
This module provides platform-specific backend implementations based on the
|
|
5
|
+
current platform. It uses conditional imports to select the appropriate
|
|
6
|
+
platform adapters at import time.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from iotsploit_core.platforms.consts import LINUX, DARWIN, WINDOWS, PLATFORM
|
|
10
|
+
from iotsploit_core.utils.exceptions import NotSupportedError
|
|
11
|
+
|
|
12
|
+
if LINUX:
|
|
13
|
+
from iotsploit_platforms.adapters.platforms.linux import wifi_backend
|
|
14
|
+
elif DARWIN:
|
|
15
|
+
from iotsploit_platforms.adapters.platforms.darwin import wifi_backend
|
|
16
|
+
elif WINDOWS:
|
|
17
|
+
from iotsploit_platforms.adapters.platforms.windows import wifi_backend
|
|
18
|
+
else:
|
|
19
|
+
raise NotSupportedError(f"Platform {PLATFORM} not supported")
|
|
20
|
+
|
|
21
|
+
__all__ = ["wifi_backend"]
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Platform Backend Selector.
|
|
3
|
+
|
|
4
|
+
This module provides functions to select and instantiate platform-specific
|
|
5
|
+
backends based on the current operating system. It serves as the main entry
|
|
6
|
+
point for applications (CLI/Django/MCP) to obtain backend instances.
|
|
7
|
+
|
|
8
|
+
The selector automatically chooses the appropriate implementation for the
|
|
9
|
+
current platform and handles backend instantiation with proper parameters.
|
|
10
|
+
|
|
11
|
+
Configuration is read from environment variables:
|
|
12
|
+
- WiFi: IOTSPLOIT_WIFI_IFACE, IOTSPLOIT_WIFI_FORWARD_ETH
|
|
13
|
+
- (Add more backends here as they are implemented)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import inspect
|
|
17
|
+
import os
|
|
18
|
+
from typing import Optional
|
|
19
|
+
|
|
20
|
+
from iotsploit_core.context import PluginContext
|
|
21
|
+
from iotsploit_core.platforms.consts import LINUX
|
|
22
|
+
from iotsploit_platforms.platforms import wifi_backend
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# =============================================================================
|
|
26
|
+
# Backend Configuration from Environment
|
|
27
|
+
# =============================================================================
|
|
28
|
+
|
|
29
|
+
def _get_wifi_config() -> dict:
|
|
30
|
+
"""Get WiFi backend configuration from environment variables."""
|
|
31
|
+
return {
|
|
32
|
+
"wifi_iface_name": os.getenv("IOTSPLOIT_WIFI_IFACE", "wlp6s0"),
|
|
33
|
+
"forward_eth_name": os.getenv("IOTSPLOIT_WIFI_FORWARD_ETH"),
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# TODO: Add more backend config functions as they are implemented
|
|
38
|
+
# def _get_bluetooth_config() -> dict:
|
|
39
|
+
# return {
|
|
40
|
+
# "adapter": os.getenv("IOTSPLOIT_BLUETOOTH_ADAPTER", "hci0"),
|
|
41
|
+
# }
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# =============================================================================
|
|
45
|
+
# Backend Factory Functions
|
|
46
|
+
# =============================================================================
|
|
47
|
+
|
|
48
|
+
def get_wifi_backend(
|
|
49
|
+
wifi_iface_name: Optional[str] = None,
|
|
50
|
+
forward_eth_name: Optional[str] = None
|
|
51
|
+
) -> wifi_backend:
|
|
52
|
+
"""
|
|
53
|
+
Get a WiFi backend instance for the current platform.
|
|
54
|
+
|
|
55
|
+
This function automatically selects the appropriate WiFi backend
|
|
56
|
+
implementation based on the current operating system (Linux/Windows/macOS)
|
|
57
|
+
and returns an instantiated backend.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
wifi_iface_name: WiFi interface name. If None, reads from IOTSPLOIT_WIFI_IFACE env var.
|
|
61
|
+
forward_eth_name: Ethernet interface for forwarding. If None, reads from IOTSPLOIT_WIFI_FORWARD_ETH.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
An instance of the platform-specific WiFi backend class
|
|
65
|
+
|
|
66
|
+
Example:
|
|
67
|
+
>>> backend = get_wifi_backend() # Uses env vars
|
|
68
|
+
>>> backend = get_wifi_backend(wifi_iface_name="wlan0") # Override
|
|
69
|
+
"""
|
|
70
|
+
# Get config from env if not provided
|
|
71
|
+
if wifi_iface_name is None or forward_eth_name is None:
|
|
72
|
+
env_config = _get_wifi_config()
|
|
73
|
+
if wifi_iface_name is None:
|
|
74
|
+
wifi_iface_name = env_config["wifi_iface_name"]
|
|
75
|
+
if forward_eth_name is None:
|
|
76
|
+
forward_eth_name = env_config["forward_eth_name"]
|
|
77
|
+
|
|
78
|
+
# wifi_backend is the class imported from platforms/__init__.py
|
|
79
|
+
# which is already platform-specific (LinuxWifiBackend, WindowsWifiBackend, etc.)
|
|
80
|
+
|
|
81
|
+
# Check if the constructor accepts forward_eth_name parameter
|
|
82
|
+
# Linux backend accepts it, Windows/Darwin don't
|
|
83
|
+
sig = inspect.signature(wifi_backend.__init__)
|
|
84
|
+
params = sig.parameters
|
|
85
|
+
|
|
86
|
+
if 'forward_eth_name' in params:
|
|
87
|
+
# Linux backend - supports forward_eth_name
|
|
88
|
+
return wifi_backend(
|
|
89
|
+
wifi_iface_name=wifi_iface_name,
|
|
90
|
+
forward_eth_name=forward_eth_name
|
|
91
|
+
)
|
|
92
|
+
else:
|
|
93
|
+
# Windows/Darwin backend - only accepts wifi_iface_name
|
|
94
|
+
return wifi_backend(wifi_iface_name=wifi_iface_name)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# =============================================================================
|
|
98
|
+
# Context Builder (Main Entry Point)
|
|
99
|
+
# =============================================================================
|
|
100
|
+
|
|
101
|
+
def build_context() -> PluginContext:
|
|
102
|
+
"""
|
|
103
|
+
Build a PluginContext with all available backends.
|
|
104
|
+
|
|
105
|
+
This function creates a structured context object containing all
|
|
106
|
+
platform-specific backends. Configuration is automatically read from
|
|
107
|
+
environment variables.
|
|
108
|
+
|
|
109
|
+
This is the recommended way to inject backends into plugins.
|
|
110
|
+
Adding a new backend only requires:
|
|
111
|
+
1. Add field to PluginContext
|
|
112
|
+
2. Add _get_xxx_config() function
|
|
113
|
+
3. Add get_xxx_backend() function
|
|
114
|
+
4. Add backend instance to PluginContext here
|
|
115
|
+
|
|
116
|
+
Environment variables:
|
|
117
|
+
- WiFi: IOTSPLOIT_WIFI_IFACE, IOTSPLOIT_WIFI_FORWARD_ETH
|
|
118
|
+
- (More backends as implemented)
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
A PluginContext instance with all backends initialized
|
|
122
|
+
|
|
123
|
+
Example:
|
|
124
|
+
>>> ctx = build_context() # Reads config from env
|
|
125
|
+
>>> plugin.initialize(ctx)
|
|
126
|
+
>>> # Plugin can access ctx.wifi, ctx.bluetooth, etc.
|
|
127
|
+
"""
|
|
128
|
+
return PluginContext(
|
|
129
|
+
wifi=get_wifi_backend(), # Uses env vars automatically
|
|
130
|
+
# bluetooth=get_bluetooth_backend(), # TODO: Add when implemented
|
|
131
|
+
# camera=get_camera_backend(), # TODO: Add when implemented
|
|
132
|
+
)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: iotsploit-platforms
|
|
3
|
+
Version: 0.0.6
|
|
4
|
+
Summary: IoTSploit platform-specific adapters (WiFi, Input, SSH backends)
|
|
5
|
+
License: GPL-3.0-or-later
|
|
6
|
+
Keywords: iot,security,testing,pentest,wifi,platform
|
|
7
|
+
Author: IoTSploit Team
|
|
8
|
+
Author-email: support@iotsploit.org
|
|
9
|
+
Requires-Python: >=3.10,<4.0
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: Information Technology
|
|
13
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Security
|
|
20
|
+
Classifier: Topic :: System :: Hardware
|
|
21
|
+
Requires-Dist: PyGObject (<3.51)
|
|
22
|
+
Requires-Dist: dbus-python (>=1.4.0,<2.0.0)
|
|
23
|
+
Requires-Dist: iotsploit-core (>=0.0.6,<0.0.7)
|
|
24
|
+
Project-URL: Documentation, https://www.iotsploit.org/
|
|
25
|
+
Project-URL: Homepage, https://www.iotsploit.org/
|
|
26
|
+
Project-URL: Repository, https://github.com/TKXB/iotsploit
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# iotsploit-platforms
|
|
30
|
+
|
|
31
|
+
IoTSploit platform-specific adapters package.
|
|
32
|
+
|
|
33
|
+
This package provides platform-specific implementations for WiFi, Input, and SSH backends across different operating systems.
|
|
34
|
+
|
|
35
|
+
## Structure
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
iotsploit-platforms/
|
|
39
|
+
├── src/
|
|
40
|
+
│ └── iotsploit_platforms/
|
|
41
|
+
│ ├── adapters/
|
|
42
|
+
│ │ └── platforms/
|
|
43
|
+
│ │ ├── linux/
|
|
44
|
+
│ │ │ └── wifi_backend.py
|
|
45
|
+
│ │ ├── windows/
|
|
46
|
+
│ │ │ └── wifi_backend.py
|
|
47
|
+
│ │ └── darwin/
|
|
48
|
+
│ │ └── wifi_backend.py
|
|
49
|
+
│ └── platforms/
|
|
50
|
+
│ └── __init__.py # Platform distribution module
|
|
51
|
+
└── pyproject.toml
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Usage
|
|
55
|
+
|
|
56
|
+
The platform distribution module automatically selects the appropriate backend based on the current platform:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from iotsploit_platforms.platforms import wifi_backend
|
|
60
|
+
|
|
61
|
+
# wifi_backend is the appropriate backend class for the current platform
|
|
62
|
+
backend = wifi_backend(wifi_iface_name="wlan0")
|
|
63
|
+
networks = backend.scan()
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Platform Support
|
|
67
|
+
|
|
68
|
+
- **Linux**: Full WiFi backend implementation using pywifi, hostapd, and dnsmasq
|
|
69
|
+
- **Windows**: Placeholder implementation (not yet implemented)
|
|
70
|
+
- **Darwin (macOS)**: Placeholder implementation (not yet implemented)
|
|
71
|
+
|
|
72
|
+
## Dependencies
|
|
73
|
+
|
|
74
|
+
- `iotsploit-core`: Core interfaces and utilities
|
|
75
|
+
- `pywifi`: WiFi operations on Linux
|
|
76
|
+
- `netifaces`: Network interface information
|
|
77
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
iotsploit_platforms/__init__.py,sha256=qIdcGSBfms7xHrTdjngKWHHyxlAwfHwfjMnUzYx3MKk,282
|
|
2
|
+
iotsploit_platforms/adapters/__init__.py,sha256=t5j3JOE_1IwKyPZR52ZJS6vCzWQ6S6b60SLWdc4x_Zw,107
|
|
3
|
+
iotsploit_platforms/adapters/platforms/__init__.py,sha256=cd_FlS_tHhdY8IevxcRajB62wZxUh7qV-9pJpjQ7mEM,130
|
|
4
|
+
iotsploit_platforms/adapters/platforms/darwin/__init__.py,sha256=Kf9rdA5Iys-ETfusqwwwRyVsjsSgRtZKEQS8oJDwiJs,155
|
|
5
|
+
iotsploit_platforms/adapters/platforms/darwin/wifi_backend.py,sha256=nwKtHp0UwoisLAh4Oe8ik1Q8FKCj3LqpOQt8veg3Amo,2839
|
|
6
|
+
iotsploit_platforms/adapters/platforms/linux/__init__.py,sha256=sk8_KV-TUxowSisdml8XP9CnMLk7T2mD_3qcPh34erU,145
|
|
7
|
+
iotsploit_platforms/adapters/platforms/linux/wifi_backend.py,sha256=6asQ8gNhRFk2xXweXR2_zUlWzP4cCWh07oCwSw4AFLg,24539
|
|
8
|
+
iotsploit_platforms/adapters/platforms/windows/__init__.py,sha256=orEvxsa_mEK-Agp0IXdn679JLotLvum3aTP5SW2toV4,149
|
|
9
|
+
iotsploit_platforms/adapters/platforms/windows/wifi_backend.py,sha256=MH9oytRnlegOiThDwqZH75HhTYToTUMer_mpq1dKkX4,2850
|
|
10
|
+
iotsploit_platforms/platforms/__init__.py,sha256=-pOe3_AunsxV3qtFqb8ZDcVpnRKATOsqp4BJJHoG4-I,738
|
|
11
|
+
iotsploit_platforms/selector.py,sha256=4r77t4U3CUtP5EJfD3d_BdewpId2Iwwz5YOW79ET1-Q,4930
|
|
12
|
+
iotsploit_platforms-0.0.6.dist-info/METADATA,sha256=GzKDRfYw4z-lc0O51uUIEqu6wdmfeaD9eHuYUiOFLpA,2665
|
|
13
|
+
iotsploit_platforms-0.0.6.dist-info/WHEEL,sha256=IYZQI976HJqqOpQU6PHkJ8fb3tMNBFjg-Cn-pwAbaFM,88
|
|
14
|
+
iotsploit_platforms-0.0.6.dist-info/RECORD,,
|