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.
- iotsploit_exploits/__init__.py +1 -0
- iotsploit_exploits/adb_check/__init__.py +0 -0
- iotsploit_exploits/adb_check/adb_check.py +493 -0
- iotsploit_exploits/demo/__init__.py +0 -0
- iotsploit_exploits/demo/async_sleep_attack.py +106 -0
- iotsploit_exploits/demo/stream_data_attack.py +184 -0
- iotsploit_exploits/flood_attack/__init__.py +0 -0
- iotsploit_exploits/flood_attack/flood_attack.py +129 -0
- iotsploit_exploits/flood_attack/syn_flood_attack.py +233 -0
- iotsploit_exploits/greatfet_echo.py +103 -0
- iotsploit_exploits/greatfet_rubber_duck.py +417 -0
- iotsploit_exploits/hydra_cracker/weak_pass.txt +471 -0
- iotsploit_exploits/hydra_cracker/weak_pass_simple.txt +5 -0
- iotsploit_exploits/hydra_ssh_attack.py +159 -0
- iotsploit_exploits/ip_scan/__init__.py +0 -0
- iotsploit_exploits/ip_scan/ip_scan.py +196 -0
- iotsploit_exploits/nmap_scan/__init__.py +0 -0
- iotsploit_exploits/nmap_scan/nmap_scan.py +207 -0
- iotsploit_exploits/plugin_ssh.py +146 -0
- iotsploit_exploits/rubber_duck_scripts/linux_infogather.txt +126 -0
- iotsploit_exploits/rubber_duck_scripts/windows_payload.txt +93 -0
- iotsploit_exploits/serial/__init__.py +0 -0
- iotsploit_exploits/serial/picocom_serial_reader.py +704 -0
- iotsploit_exploits/simple_rubber_duck.py +183 -0
- iotsploit_exploits/wifi_scan/__init__.py +0 -0
- iotsploit_exploits/wifi_scan/wifi_scan.py +242 -0
- iotsploit_exploits-0.0.6.dist-info/METADATA +65 -0
- iotsploit_exploits-0.0.6.dist-info/RECORD +30 -0
- iotsploit_exploits-0.0.6.dist-info/WHEEL +4 -0
- iotsploit_exploits-0.0.6.dist-info/entry_points.txt +16 -0
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
|
|
3
|
+
import pluggy
|
|
4
|
+
from typing import Optional, Any
|
|
5
|
+
import asyncio
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
from importlib.resources import files
|
|
9
|
+
from facedancer.errors import EndEmulation
|
|
10
|
+
from iotsploit_core.core.exploit_spec import ExploitResult, AsyncExploitResult
|
|
11
|
+
from iotsploit_core.core.base_plugin import BasePlugin
|
|
12
|
+
from facedancer.devices.keyboard import USBKeyboardDevice
|
|
13
|
+
from facedancer.classes.hid.keyboard import KeyboardModifiers
|
|
14
|
+
from iotsploit_core.utils import iots_logger
|
|
15
|
+
|
|
16
|
+
hookimpl = pluggy.HookimplMarker("exploit_mgr")
|
|
17
|
+
|
|
18
|
+
class RubberDuckPlugin(BasePlugin):
|
|
19
|
+
def __init__(self):
|
|
20
|
+
super().__init__({
|
|
21
|
+
'Name': 'Rubber Duck',
|
|
22
|
+
'Description': 'Emulates a USB keyboard to type keystrokes automatically (USB Rubber Duck).',
|
|
23
|
+
'License': 'GPL',
|
|
24
|
+
'Author': ['iotsploit'],
|
|
25
|
+
'RequiresRoot': False,
|
|
26
|
+
'Parameters': {
|
|
27
|
+
'payload': {
|
|
28
|
+
'type': 'str',
|
|
29
|
+
'required': False,
|
|
30
|
+
'description': 'Commands to type (each line will be executed as a separate command)',
|
|
31
|
+
'default': 'echo Hello from Rubber Duck!\nid\nls -la\n'
|
|
32
|
+
},
|
|
33
|
+
'delay_before_typing': {
|
|
34
|
+
'type': 'int',
|
|
35
|
+
'required': False,
|
|
36
|
+
'description': 'Time to wait in seconds before starting to type',
|
|
37
|
+
'default': 2
|
|
38
|
+
},
|
|
39
|
+
'delay_between_lines': {
|
|
40
|
+
'type': 'float',
|
|
41
|
+
'required': False,
|
|
42
|
+
'description': 'Time to wait in seconds between typing each line',
|
|
43
|
+
'default': 0.5
|
|
44
|
+
},
|
|
45
|
+
'delay_after_typing': {
|
|
46
|
+
'type': 'int',
|
|
47
|
+
'required': False,
|
|
48
|
+
'description': 'Time to wait in seconds after typing completes before ending the emulation',
|
|
49
|
+
'default': 1
|
|
50
|
+
},
|
|
51
|
+
'script_file': {
|
|
52
|
+
'type': 'str',
|
|
53
|
+
'required': False,
|
|
54
|
+
'description': 'Path to a script file containing commands to type',
|
|
55
|
+
'default': ''
|
|
56
|
+
},
|
|
57
|
+
'payload_format': {
|
|
58
|
+
'type': 'str',
|
|
59
|
+
'required': False,
|
|
60
|
+
'description': 'Format of the payload: "plain" for plain text or "ducky" for DuckyScript format',
|
|
61
|
+
'default': 'plain'
|
|
62
|
+
},
|
|
63
|
+
'target_os': {
|
|
64
|
+
'type': 'str',
|
|
65
|
+
'required': False,
|
|
66
|
+
'description': 'Target operating system for built-in scripts: "windows", "linux", or "none" for custom script',
|
|
67
|
+
'default': 'none'
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
self.device = None
|
|
72
|
+
self.running = False
|
|
73
|
+
self.logger = iots_logger.get_logger("rubber_duck")
|
|
74
|
+
|
|
75
|
+
# Resolve built-in payloads from packaged resources.
|
|
76
|
+
_scripts = files("iotsploit_exploits") / "rubber_duck_scripts"
|
|
77
|
+
self.script_files = {
|
|
78
|
+
'windows': str(_scripts / 'windows_payload.txt'),
|
|
79
|
+
'linux': str(_scripts / 'linux_infogather.txt')
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
# Define special key mappings
|
|
83
|
+
self.special_keys = {
|
|
84
|
+
'CTRL': KeyboardModifiers.MOD_LEFT_CTRL,
|
|
85
|
+
'SHIFT': KeyboardModifiers.MOD_LEFT_SHIFT,
|
|
86
|
+
'ALT': KeyboardModifiers.MOD_LEFT_ALT,
|
|
87
|
+
'GUI': KeyboardModifiers.MOD_LEFT_META,
|
|
88
|
+
'WINDOWS': KeyboardModifiers.MOD_LEFT_META,
|
|
89
|
+
'COMMAND': KeyboardModifiers.MOD_LEFT_META, # Mac Command key
|
|
90
|
+
'OPTION': KeyboardModifiers.MOD_LEFT_ALT, # Mac Option key
|
|
91
|
+
'ESC': 0x29,
|
|
92
|
+
'ESCAPE': 0x29,
|
|
93
|
+
'ENTER': 0x28,
|
|
94
|
+
'RETURN': 0x28,
|
|
95
|
+
'TAB': 0x2B,
|
|
96
|
+
'SPACE': 0x2C,
|
|
97
|
+
'BACKSPACE': 0x2A,
|
|
98
|
+
'DELETE': 0x4C,
|
|
99
|
+
'INSERT': 0x49,
|
|
100
|
+
'HOME': 0x4A,
|
|
101
|
+
'END': 0x4D,
|
|
102
|
+
'PAGEUP': 0x4B,
|
|
103
|
+
'PAGEDOWN': 0x4E,
|
|
104
|
+
'UP': 0x52,
|
|
105
|
+
'DOWN': 0x51,
|
|
106
|
+
'LEFT': 0x50,
|
|
107
|
+
'RIGHT': 0x4F,
|
|
108
|
+
'F1': 0x3A,
|
|
109
|
+
'F2': 0x3B,
|
|
110
|
+
'F3': 0x3C,
|
|
111
|
+
'F4': 0x3D,
|
|
112
|
+
'F5': 0x3E,
|
|
113
|
+
'F6': 0x3F,
|
|
114
|
+
'F7': 0x40,
|
|
115
|
+
'F8': 0x41,
|
|
116
|
+
'F9': 0x42,
|
|
117
|
+
'F10': 0x43,
|
|
118
|
+
'F11': 0x44,
|
|
119
|
+
'F12': 0x45,
|
|
120
|
+
'CAPSLOCK': 0x39,
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@hookimpl
|
|
124
|
+
def initialize(self, device_plugin: Optional[Any] = None):
|
|
125
|
+
self.logger.info("Initializing RubberDuckPlugin")
|
|
126
|
+
|
|
127
|
+
def custom_facedancer_main(self, device, coroutine):
|
|
128
|
+
"""
|
|
129
|
+
Custom implementation of Facedancer's main function that doesn't parse command line arguments.
|
|
130
|
+
This is needed when running inside Django where sys.argv contains Django's arguments.
|
|
131
|
+
"""
|
|
132
|
+
self.logger.info("Starting custom Facedancer emulation")
|
|
133
|
+
try:
|
|
134
|
+
# Directly call the device's emulate method with our coroutine
|
|
135
|
+
device.emulate(coroutine)
|
|
136
|
+
self.logger.info("Facedancer emulation completed successfully")
|
|
137
|
+
except EndEmulation as e:
|
|
138
|
+
self.logger.info(f"Facedancer emulation ended: {str(e)}")
|
|
139
|
+
except KeyboardInterrupt:
|
|
140
|
+
self.logger.info("Facedancer emulation interrupted by user")
|
|
141
|
+
except Exception as e:
|
|
142
|
+
raise e
|
|
143
|
+
|
|
144
|
+
async def _parse_ducky_script(self, script):
|
|
145
|
+
"""Parse a DuckyScript formatted script into executable commands"""
|
|
146
|
+
commands = []
|
|
147
|
+
delay = 0.5 # Default delay in seconds
|
|
148
|
+
|
|
149
|
+
for line in script.splitlines():
|
|
150
|
+
line = line.strip()
|
|
151
|
+
if not line or line.startswith('REM'): # Comment line
|
|
152
|
+
continue
|
|
153
|
+
|
|
154
|
+
# Handle delay commands
|
|
155
|
+
if line.startswith('DELAY '):
|
|
156
|
+
try:
|
|
157
|
+
delay_ms = int(line.split(' ', 1)[1])
|
|
158
|
+
commands.append(('DELAY', delay_ms / 1000)) # Convert to seconds
|
|
159
|
+
except (ValueError, IndexError):
|
|
160
|
+
self.logger.warning(f"Invalid delay command: {line}")
|
|
161
|
+
continue
|
|
162
|
+
|
|
163
|
+
# Handle STRING command (regular typing)
|
|
164
|
+
if line.startswith('STRING '):
|
|
165
|
+
text = line[7:] # Extract text after 'STRING '
|
|
166
|
+
commands.append(('STRING', text))
|
|
167
|
+
continue
|
|
168
|
+
|
|
169
|
+
# Handle modifier + key combinations like "GUI r"
|
|
170
|
+
parts = line.split()
|
|
171
|
+
if len(parts) == 2 and parts[0] in self.special_keys:
|
|
172
|
+
modifier = parts[0]
|
|
173
|
+
key = parts[1]
|
|
174
|
+
commands.append(('MOD_KEY', (modifier, key)))
|
|
175
|
+
continue
|
|
176
|
+
|
|
177
|
+
# Handle multiple special keys like "CTRL ALT DELETE"
|
|
178
|
+
if all(part in self.special_keys for part in parts):
|
|
179
|
+
commands.append(('KEYS', parts))
|
|
180
|
+
continue
|
|
181
|
+
|
|
182
|
+
self.logger.warning(f"Unrecognized DuckyScript command: {line}")
|
|
183
|
+
|
|
184
|
+
return commands
|
|
185
|
+
|
|
186
|
+
async def _execute_special_keys(self, key_combination):
|
|
187
|
+
"""Execute a special key combination"""
|
|
188
|
+
modifiers = 0
|
|
189
|
+
keys = []
|
|
190
|
+
|
|
191
|
+
for key in key_combination:
|
|
192
|
+
if key in ['CTRL', 'SHIFT', 'ALT', 'GUI', 'WINDOWS', 'COMMAND', 'OPTION']:
|
|
193
|
+
modifiers |= self.special_keys[key]
|
|
194
|
+
else:
|
|
195
|
+
keys.append(self.special_keys[key])
|
|
196
|
+
|
|
197
|
+
if not keys: # If only modifiers, add a blank key
|
|
198
|
+
keys = [0]
|
|
199
|
+
|
|
200
|
+
for key in keys:
|
|
201
|
+
# Use type_scancode instead of press_key/release_all_keys
|
|
202
|
+
await self.device.type_scancode(key, modifiers=modifiers, duration=0.05)
|
|
203
|
+
|
|
204
|
+
def _prepare_payload(self, parameters):
|
|
205
|
+
"""Prepare payload from parameters - works both in sync and async contexts"""
|
|
206
|
+
parameters = parameters or {}
|
|
207
|
+
delay_before_typing = int(parameters.get('delay_before_typing',
|
|
208
|
+
self.info['Parameters']['delay_before_typing']['default']))
|
|
209
|
+
delay_between_lines = float(parameters.get('delay_between_lines',
|
|
210
|
+
self.info['Parameters']['delay_between_lines']['default']))
|
|
211
|
+
delay_after_typing = int(parameters.get('delay_after_typing',
|
|
212
|
+
self.info['Parameters']['delay_after_typing']['default']))
|
|
213
|
+
payload_format = parameters.get('payload_format',
|
|
214
|
+
self.info['Parameters']['payload_format']['default'])
|
|
215
|
+
target_os = parameters.get('target_os',
|
|
216
|
+
self.info['Parameters']['target_os']['default'])
|
|
217
|
+
|
|
218
|
+
# Determine payload source (target_os, script_file, or direct payload)
|
|
219
|
+
script_file = parameters.get('script_file', self.info['Parameters']['script_file']['default'])
|
|
220
|
+
|
|
221
|
+
# If target_os is specified, use the corresponding built-in script
|
|
222
|
+
if target_os != 'none' and target_os in self.script_files:
|
|
223
|
+
script_file = self.script_files[target_os]
|
|
224
|
+
self.logger.info(f"Using built-in {target_os} script: {script_file}")
|
|
225
|
+
# For built-in scripts, automatically set payload format to ducky
|
|
226
|
+
payload_format = 'ducky'
|
|
227
|
+
|
|
228
|
+
payload = parameters.get('payload', self.info['Parameters']['payload']['default'])
|
|
229
|
+
|
|
230
|
+
if script_file and os.path.exists(script_file):
|
|
231
|
+
self.logger.info(f"Reading payload from script file: {script_file}")
|
|
232
|
+
try:
|
|
233
|
+
with open(script_file, 'r') as f:
|
|
234
|
+
payload = f.read()
|
|
235
|
+
except Exception as e:
|
|
236
|
+
self.logger.error(f"Error reading script file: {str(e)}")
|
|
237
|
+
return None, None, None, f"Failed to read script file: {str(e)}"
|
|
238
|
+
elif script_file and not os.path.exists(script_file):
|
|
239
|
+
self.logger.error(f"Script file not found: {script_file}")
|
|
240
|
+
return None, None, None, f"Script file not found: {script_file}"
|
|
241
|
+
|
|
242
|
+
return payload, payload_format, {
|
|
243
|
+
'delay_before_typing': delay_before_typing,
|
|
244
|
+
'delay_between_lines': delay_between_lines,
|
|
245
|
+
'delay_after_typing': delay_after_typing
|
|
246
|
+
}, None
|
|
247
|
+
|
|
248
|
+
@hookimpl
|
|
249
|
+
def execute(self, target: Optional[Any] = None, parameters: Optional[dict] = None):
|
|
250
|
+
"""
|
|
251
|
+
Primary execution method - synchronous version.
|
|
252
|
+
This is the preferred entry point for Celery tasks.
|
|
253
|
+
"""
|
|
254
|
+
self.logger.info("Executing RubberDuckPlugin")
|
|
255
|
+
|
|
256
|
+
try:
|
|
257
|
+
# Check if we already have a running instance
|
|
258
|
+
if self.running:
|
|
259
|
+
return ExploitResult(False, "An instance is already running", {})
|
|
260
|
+
|
|
261
|
+
# Prepare payload and get configuration
|
|
262
|
+
payload, payload_format, config, error = self._prepare_payload(parameters)
|
|
263
|
+
if error:
|
|
264
|
+
return ExploitResult(False, error, {})
|
|
265
|
+
|
|
266
|
+
# Initialize USB device
|
|
267
|
+
self.logger.info("Initializing USB keyboard device...")
|
|
268
|
+
try:
|
|
269
|
+
self.device = USBKeyboardDevice()
|
|
270
|
+
self.logger.info(f"USB keyboard device initialized")
|
|
271
|
+
except Exception as e:
|
|
272
|
+
self.logger.error(f"Failed to initialize USB keyboard device: {str(e)}")
|
|
273
|
+
return ExploitResult(False, f"Failed to initialize USB keyboard device: {str(e)}", {})
|
|
274
|
+
|
|
275
|
+
# Set running flag
|
|
276
|
+
self.running = True
|
|
277
|
+
|
|
278
|
+
# Define our typing coroutine
|
|
279
|
+
async def process_commands():
|
|
280
|
+
try:
|
|
281
|
+
self.logger.info(f"Waiting {config['delay_before_typing']} seconds before typing...")
|
|
282
|
+
await asyncio.sleep(config['delay_before_typing'])
|
|
283
|
+
|
|
284
|
+
# Parse the payload based on format
|
|
285
|
+
if payload_format.lower() == 'ducky':
|
|
286
|
+
self.logger.info("Parsing payload as DuckyScript")
|
|
287
|
+
commands = await self._parse_ducky_script(payload)
|
|
288
|
+
else:
|
|
289
|
+
# Plain text format - each line is a command to type
|
|
290
|
+
self.logger.info("Using plain text payload format")
|
|
291
|
+
commands = [('STRING', line) for line in payload.splitlines()]
|
|
292
|
+
|
|
293
|
+
# Process typing commands
|
|
294
|
+
self.logger.info("Running typing commands...")
|
|
295
|
+
|
|
296
|
+
total_commands = len(commands)
|
|
297
|
+
for i, (cmd_type, cmd_data) in enumerate(commands):
|
|
298
|
+
if not self.running:
|
|
299
|
+
self.logger.info("Typing interrupted")
|
|
300
|
+
break
|
|
301
|
+
|
|
302
|
+
try:
|
|
303
|
+
if cmd_type == 'DELAY':
|
|
304
|
+
self.logger.info(f"Waiting for {cmd_data} seconds...")
|
|
305
|
+
await asyncio.sleep(cmd_data)
|
|
306
|
+
elif cmd_type == 'STRING':
|
|
307
|
+
self.logger.info(f"Typing text ({i+1}/{total_commands}): {cmd_data}")
|
|
308
|
+
await self.device.type_string(cmd_data)
|
|
309
|
+
# Add enter key at the end of each command line for plain text mode
|
|
310
|
+
if payload_format.lower() == 'plain':
|
|
311
|
+
await self.device.type_scancode(0x28) # ENTER key
|
|
312
|
+
await asyncio.sleep(0.05)
|
|
313
|
+
elif cmd_type == 'KEYS':
|
|
314
|
+
self.logger.info(f"Executing key combination ({i+1}/{total_commands}): {' '.join(cmd_data)}")
|
|
315
|
+
await self._execute_special_keys(cmd_data)
|
|
316
|
+
|
|
317
|
+
# Handle MOD_KEY (modifier + regular key)
|
|
318
|
+
elif cmd_type == 'MOD_KEY':
|
|
319
|
+
modifier, key = cmd_data
|
|
320
|
+
self.logger.info(f"Executing modifier + key ({i+1}/{total_commands}): {modifier} {key}")
|
|
321
|
+
modifiers = self.special_keys[modifier]
|
|
322
|
+
|
|
323
|
+
# If key is a special key, use its scancode
|
|
324
|
+
if key.upper() in self.special_keys:
|
|
325
|
+
scancode = self.special_keys[key.upper()]
|
|
326
|
+
# Otherwise, type it as a regular character
|
|
327
|
+
else:
|
|
328
|
+
# First press the modifier key
|
|
329
|
+
await self.device.type_string(key, modifiers=modifiers)
|
|
330
|
+
await asyncio.sleep(0.05)
|
|
331
|
+
continue
|
|
332
|
+
|
|
333
|
+
# Use type_scancode with modifier
|
|
334
|
+
await self.device.type_scancode(scancode, modifiers=modifiers, duration=0.05)
|
|
335
|
+
|
|
336
|
+
# Report progress
|
|
337
|
+
progress = (i + 1) / total_commands
|
|
338
|
+
self.logger.info(f"Progress: {progress:.1%}")
|
|
339
|
+
|
|
340
|
+
# Wait between commands
|
|
341
|
+
if i < total_commands - 1:
|
|
342
|
+
await asyncio.sleep(config['delay_between_lines'])
|
|
343
|
+
|
|
344
|
+
except Exception as e:
|
|
345
|
+
self.logger.error(f"Error executing command {i+1}: {str(e)}")
|
|
346
|
+
|
|
347
|
+
self.logger.info(f"Typing complete. Waiting {config['delay_after_typing']} seconds before ending...")
|
|
348
|
+
await asyncio.sleep(config['delay_after_typing'])
|
|
349
|
+
|
|
350
|
+
# Clean end of the emulation
|
|
351
|
+
self.logger.info("Exiting facedancer emulation...")
|
|
352
|
+
raise EndEmulation("Typing completed successfully")
|
|
353
|
+
|
|
354
|
+
except Exception as e:
|
|
355
|
+
self.logger.error(f"Error in command processing: {str(e)}")
|
|
356
|
+
if hasattr(e, '__traceback__'):
|
|
357
|
+
import traceback
|
|
358
|
+
self.logger.error(''.join(traceback.format_tb(e.__traceback__)))
|
|
359
|
+
raise # Re-raise the exception
|
|
360
|
+
|
|
361
|
+
try:
|
|
362
|
+
# Use our custom Facedancer main function to avoid argument parsing issues
|
|
363
|
+
self.logger.info("Starting Facedancer execution...")
|
|
364
|
+
self.custom_facedancer_main(self.device, process_commands())
|
|
365
|
+
self.logger.info("Facedancer execution completed successfully")
|
|
366
|
+
except Exception as e:
|
|
367
|
+
self.logger.error(f"Error in Facedancer execution: {str(e)}")
|
|
368
|
+
if hasattr(e, '__traceback__'):
|
|
369
|
+
import traceback
|
|
370
|
+
self.logger.error(''.join(traceback.format_tb(e.__traceback__)))
|
|
371
|
+
self.running = False
|
|
372
|
+
return ExploitResult(False, f"Facedancer execution failed: {str(e)}", {})
|
|
373
|
+
|
|
374
|
+
# Return success - at this point the operation is complete
|
|
375
|
+
return ExploitResult(True, "RubberDuck executed successfully", {
|
|
376
|
+
"status": "complete",
|
|
377
|
+
"message": "Device has typed all commands"
|
|
378
|
+
})
|
|
379
|
+
|
|
380
|
+
except Exception as e:
|
|
381
|
+
self.running = False
|
|
382
|
+
self.logger.error(f"Error during RubberDuck execution: {str(e)}")
|
|
383
|
+
if hasattr(e, '__traceback__'):
|
|
384
|
+
import traceback
|
|
385
|
+
self.logger.error(''.join(traceback.format_tb(e.__traceback__)))
|
|
386
|
+
return ExploitResult(False, f"RubberDuck failed: {str(e)}", {})
|
|
387
|
+
finally:
|
|
388
|
+
# Reset the running flag
|
|
389
|
+
self.running = False
|
|
390
|
+
|
|
391
|
+
@hookimpl
|
|
392
|
+
def cleanup(self):
|
|
393
|
+
"""Clean up resources when plugin execution is finished"""
|
|
394
|
+
self.logger.info("Cleaning up RubberDuckPlugin")
|
|
395
|
+
|
|
396
|
+
try:
|
|
397
|
+
# Signal any running operations to stop
|
|
398
|
+
self.running = False
|
|
399
|
+
|
|
400
|
+
# Disconnect the device if it exists
|
|
401
|
+
if hasattr(self, 'device') and self.device:
|
|
402
|
+
try:
|
|
403
|
+
self.logger.info("Disconnecting USB keyboard device...")
|
|
404
|
+
self.device.disconnect()
|
|
405
|
+
self.logger.info("Keyboard Device disconnected")
|
|
406
|
+
except Exception as e:
|
|
407
|
+
self.logger.error(f"Error disconnecting device: {str(e)}")
|
|
408
|
+
finally:
|
|
409
|
+
self.device = None
|
|
410
|
+
|
|
411
|
+
self.logger.info("Cleanup completed successfully")
|
|
412
|
+
|
|
413
|
+
except Exception as e:
|
|
414
|
+
self.logger.error(f"Error during cleanup: {str(e)}")
|
|
415
|
+
if hasattr(e, '__traceback__'):
|
|
416
|
+
import traceback
|
|
417
|
+
self.logger.error(''.join(traceback.format_tb(e.__traceback__)))
|