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,704 @@
1
+ #!/usr/bin/python3
2
+
3
+ import logging
4
+ import pluggy
5
+ import asyncio
6
+ import serial
7
+ import serial.tools.list_ports
8
+ import re
9
+ import time
10
+ from typing import Optional, Any, List, Dict
11
+ from threading import Thread, Event
12
+ from queue import Queue, Empty
13
+ from dataclasses import dataclass
14
+ from iotsploit_core.core.exploit_spec import AsyncExploitResult
15
+ from iotsploit_core.core.base_plugin import BasePlugin
16
+ from iotsploit_core.utils import iots_logger
17
+
18
+ logger = iots_logger.get_logger("picocom_serial_reader")
19
+ hookimpl = pluggy.HookimplMarker("exploit_mgr")
20
+
21
+ # Add file logging for debugging
22
+ import logging
23
+ import os
24
+ file_logger = logging.getLogger('picocom_serial_reader_file')
25
+ file_logger.setLevel(logging.DEBUG)
26
+
27
+ # Create logs directory if it doesn't exist
28
+ log_dir = "/tmp/sat_logs"
29
+ os.makedirs(log_dir, exist_ok=True)
30
+
31
+ # Add file handler
32
+ file_handler = logging.FileHandler(f"{log_dir}/picocom_serial_reader.log")
33
+ file_handler.setLevel(logging.DEBUG)
34
+ formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
35
+ file_handler.setFormatter(formatter)
36
+ file_logger.addHandler(file_handler)
37
+
38
+ def log_both(level, message):
39
+ """Log to both regular logger and file logger"""
40
+ getattr(logger, level)(message)
41
+ getattr(file_logger, level)(message)
42
+
43
+ @dataclass
44
+ class SerialAnalysis:
45
+ """Data structure for serial port analysis results"""
46
+ device_type: str = "unknown"
47
+ confidence: float = 0.0
48
+ detected_patterns: List[str] = None
49
+ login_detected: bool = False
50
+ shell_type: str = "unknown"
51
+ firmware_info: Dict[str, str] = None
52
+ raw_output: List[str] = None
53
+
54
+ def __post_init__(self):
55
+ if self.detected_patterns is None:
56
+ self.detected_patterns = []
57
+ if self.firmware_info is None:
58
+ self.firmware_info = {}
59
+ if self.raw_output is None:
60
+ self.raw_output = []
61
+
62
+ class PicocomSerialReaderPlugin(BasePlugin):
63
+ def __init__(self):
64
+ super().__init__({
65
+ 'Name': 'Picocom Serial Reader',
66
+ 'Description': 'AI-powered serial port reader and analyzer similar to picocom',
67
+ 'License': 'GPL',
68
+ 'Author': ['iotsploit'],
69
+ 'Parameters': {
70
+ 'port': {
71
+ 'type': 'str',
72
+ 'required': True,
73
+ 'description': 'Serial port path (e.g., /dev/ttyUSB0, COM1)',
74
+ 'default': '/dev/ttyUSB0'
75
+ },
76
+ 'baudrate': {
77
+ 'type': 'int',
78
+ 'required': False,
79
+ 'description': 'Baud rate for serial communication',
80
+ 'default': 115200,
81
+ 'validation': {
82
+ 'min': 300,
83
+ 'max': 921600
84
+ }
85
+ },
86
+ 'timeout': {
87
+ 'type': 'int',
88
+ 'required': False,
89
+ 'description': 'Maximum time to wait for output (seconds)',
90
+ 'default': 60,
91
+ 'validation': {
92
+ 'min': 5,
93
+ 'max': 300
94
+ }
95
+ },
96
+ 'auto_interact': {
97
+ 'type': 'bool',
98
+ 'required': False,
99
+ 'description': 'Automatically send Enter and common inputs to trigger responses',
100
+ 'default': True
101
+ },
102
+ 'analyze_output': {
103
+ 'type': 'bool',
104
+ 'required': False,
105
+ 'description': 'Enable AI analysis of serial output',
106
+ 'default': True
107
+ }
108
+ }
109
+ })
110
+
111
+ # Plugin state
112
+ self._stop_reading = False
113
+ self._serial_conn = None
114
+ self._reader_thread = None
115
+ self._output_queue = Queue()
116
+ self._analysis = SerialAnalysis()
117
+
118
+ # Detection patterns
119
+ self._login_patterns = [
120
+ r'login\s*:',
121
+ r'username\s*:',
122
+ r'user\s*:',
123
+ r'password\s*:',
124
+ r'passwd\s*:',
125
+ r'>\s*$',
126
+ r'#\s*$',
127
+ r'\$\s*$',
128
+ r'.*@.*:.*\$',
129
+ r'.*@.*:.*#'
130
+ ]
131
+
132
+ self._shell_patterns = {
133
+ 'bash': [r'bash-\d+\.\d+', r'GNU bash'],
134
+ 'sh': [r'\/bin\/sh', r'\$\s*$'],
135
+ 'busybox': [r'BusyBox', r'built-in shell'],
136
+ 'uboot': [r'U-Boot', r'=>\s*$', r'Hit any key to stop autoboot'],
137
+ 'bootloader': [r'Bootloader', r'Boot>', r'bootloader>'],
138
+ 'linux': [r'Linux version', r'GNU\/Linux', r'kernel'],
139
+ 'embedded': [r'Embedded', r'firmware', r'version']
140
+ }
141
+
142
+ self._interaction_sequences = [
143
+ "\r\n", # Enter
144
+ "\r", # Carriage return
145
+ "\n", # Line feed
146
+ " \r\n", # Space + Enter
147
+ "help\r\n",
148
+ "?\r\n",
149
+ "ls\r\n",
150
+ "id\r\n",
151
+ "whoami\r\n"
152
+ ]
153
+
154
+ @hookimpl
155
+ def initialize(self, device_plugin: Optional[Any] = None):
156
+ logger.info("Initializing PicocomSerialReaderPlugin")
157
+ self._stop_reading = False
158
+ self._analysis = SerialAnalysis()
159
+
160
+ @hookimpl
161
+ async def execute_async(self, target: Optional[Any] = None, parameters: Optional[dict] = None) -> AsyncExploitResult:
162
+ """Asynchronous execution method for serial port reading and analysis"""
163
+ log_both('info', "๐ŸŽฏ Starting Picocom Serial Reader execution")
164
+ async_result = AsyncExploitResult()
165
+
166
+ try:
167
+ # Extract parameters
168
+ params = parameters or {}
169
+ port = params.get('port', '/dev/ttyUSB0')
170
+ baudrate = params.get('baudrate', 115200)
171
+ timeout = params.get('timeout', 30)
172
+ auto_interact = params.get('auto_interact', True)
173
+ analyze_output = params.get('analyze_output', True)
174
+
175
+ log_both('info', f"๐ŸŽฏ Serial parameters: port={port}, baudrate={baudrate}, timeout={timeout}")
176
+
177
+ # Check if port exists
178
+ available_ports = self._list_serial_ports()
179
+ if port not in available_ports:
180
+ async_result.update(
181
+ status=False,
182
+ message=f"Port {port} not found. Available ports: {', '.join(available_ports)}",
183
+ progress=100
184
+ )
185
+ return async_result
186
+
187
+ # Initialize serial connection
188
+ async_result.update(
189
+ status=True,
190
+ progress=10,
191
+ message=f"Connecting to serial port {port}...",
192
+ data={"available_ports": available_ports}
193
+ )
194
+
195
+ success = await self._connect_serial(port, baudrate)
196
+ if not success:
197
+ async_result.update(
198
+ status=False,
199
+ message=f"Failed to connect to {port}",
200
+ progress=100
201
+ )
202
+ return async_result
203
+
204
+ # Start reading thread
205
+ async_result.update(
206
+ status=True,
207
+ progress=20,
208
+ message="Starting serial communication...",
209
+ data={"port": port, "baudrate": baudrate}
210
+ )
211
+
212
+ self._start_reader_thread()
213
+
214
+ # Main reading and interaction loop
215
+ start_time = time.time()
216
+ interaction_step = 0
217
+ last_interaction_time = start_time
218
+ last_log_time = start_time
219
+ log_interval = 10.0 # Log every 10 seconds
220
+
221
+ # Simple early exit: exit when all interactions done + have some data
222
+ min_data_threshold = 5 # Need at least 5 lines of output for early exit
223
+
224
+ log_both('info', f"๐Ÿš€ Starting main reading loop - timeout: {timeout}s")
225
+ log_both('info', f"๐Ÿš€ Auto-interact: {auto_interact}, Available interactions: {len(self._interaction_sequences)}")
226
+ log_both('info', f"๐Ÿš€ Serial connection status: {self._serial_conn.is_open if self._serial_conn else 'No connection'}")
227
+ log_both('info', f"๐Ÿš€ Simple early exit: will exit when all {len(self._interaction_sequences)} interactions done + {min_data_threshold}+ lines captured")
228
+
229
+ while time.time() - start_time < timeout and not self._stop_reading:
230
+ # Check for stop condition
231
+ if self._stop_reading:
232
+ logger.info("๐Ÿ›‘ Stop reading flag set, breaking loop")
233
+ break
234
+
235
+ # Update progress
236
+ elapsed = time.time() - start_time
237
+ progress = 20 + (elapsed / timeout) * 70 # 20-90% for reading phase
238
+
239
+ # Periodic detailed logging
240
+ current_time = time.time()
241
+ if current_time - last_log_time >= log_interval:
242
+ log_both('info', f"๐Ÿ“Š PROGRESS UPDATE: {elapsed:.1f}s/{timeout}s ({progress:.1f}%)")
243
+ log_both('info', f"๐Ÿ“Š Serial port status: connected={self._serial_conn.is_open if self._serial_conn else False}")
244
+ log_both('info', f"๐Ÿ“Š Lines captured so far: {len(self._analysis.raw_output)}")
245
+ log_both('info', f"๐Ÿ“Š Interactions sent: {interaction_step}/{len(self._interaction_sequences)}")
246
+ log_both('info', f"๐Ÿ“Š Device type detected: {self._analysis.device_type}")
247
+ log_both('info', f"๐Ÿ“Š Login detected: {self._analysis.login_detected}")
248
+
249
+ # Check serial connection health
250
+ if self._serial_conn:
251
+ try:
252
+ waiting_bytes = self._serial_conn.in_waiting
253
+ log_both('info', f"๐Ÿ“Š Bytes waiting in buffer: {waiting_bytes}")
254
+ except Exception as e:
255
+ log_both('warning', f"๐Ÿ“Š Error checking in_waiting: {e}")
256
+
257
+ last_log_time = current_time
258
+
259
+ # Collect any new output
260
+ new_output = self._collect_output()
261
+ if new_output:
262
+ self._analysis.raw_output.extend(new_output)
263
+ log_both('info', f"๐Ÿ“ฅ Received {len(new_output)} lines of output:")
264
+ for i, line in enumerate(new_output[:5]): # Show first 5 lines
265
+ log_both('info', f"๐Ÿ“ฅ Line {i+1}: {repr(line)}")
266
+ if len(new_output) > 5:
267
+ log_both('info', f"๐Ÿ“ฅ ... and {len(new_output) - 5} more lines")
268
+
269
+ # Analyze output in real-time
270
+ if analyze_output:
271
+ self._analyze_output_patterns(new_output)
272
+ log_both('info', f"๐Ÿ” Analysis updated: device_type={self._analysis.device_type}, confidence={self._analysis.confidence:.2f}")
273
+ else:
274
+ log_both('debug', f"๐Ÿ“ญ No new output available (elapsed: {elapsed:.1f}s)")
275
+
276
+ # Simple early exit: all interactions done + have data = exit immediately
277
+ interactions_complete = interaction_step >= len(self._interaction_sequences)
278
+ sufficient_data = len(self._analysis.raw_output) >= min_data_threshold
279
+
280
+ if interactions_complete and sufficient_data:
281
+ log_both('info', f"๐ŸŽฏ SIMPLE EARLY EXIT: All done!")
282
+ log_both('info', f"๐ŸŽฏ - All interactions sent: {interaction_step}/{len(self._interaction_sequences)} โœ…")
283
+ log_both('info', f"๐ŸŽฏ - Sufficient data captured: {len(self._analysis.raw_output)} lines โœ…")
284
+ # Stop reader thread early to speed up cleanup
285
+ self._stop_reading = True
286
+ log_both('info', f"๐ŸŽฏ Exiting after {elapsed:.1f}s instead of waiting full {timeout}s")
287
+ break
288
+
289
+ # Auto-interact if enabled and no recent output
290
+ if (auto_interact and
291
+ current_time - last_interaction_time > 3 and # Wait 3 seconds between interactions
292
+ interaction_step < len(self._interaction_sequences)):
293
+
294
+ interaction = self._interaction_sequences[interaction_step]
295
+ log_both('info', f"๐Ÿ’ฌ Sending interaction #{interaction_step}: {repr(interaction)}")
296
+
297
+ if self._send_data(interaction):
298
+ last_interaction_time = current_time
299
+ interaction_step += 1
300
+ log_both('info', f"โœ… Successfully sent interaction #{interaction_step-1}")
301
+
302
+ async_result.update(
303
+ status=True,
304
+ progress=progress,
305
+ message=f"Sent interaction: {repr(interaction)} | Lines captured: {len(self._analysis.raw_output)}",
306
+ data={
307
+ "interactions_sent": interaction_step,
308
+ "output_lines": len(self._analysis.raw_output),
309
+ "login_detected": self._analysis.login_detected,
310
+ "device_type": self._analysis.device_type
311
+ }
312
+ )
313
+ else:
314
+ log_both('error', f"โŒ Failed to send interaction #{interaction_step}: {repr(interaction)}")
315
+ else:
316
+ log_both('debug', f"โณ Waiting... (elapsed: {elapsed:.1f}s, next_interaction: {interaction_step < len(self._interaction_sequences)})")
317
+ async_result.update(
318
+ status=True,
319
+ progress=progress,
320
+ message=f"Reading serial output... | Lines captured: {len(self._analysis.raw_output)}",
321
+ data={
322
+ "output_lines": len(self._analysis.raw_output),
323
+ "login_detected": self._analysis.login_detected,
324
+ "device_type": self._analysis.device_type
325
+ }
326
+ )
327
+
328
+ # Short sleep to prevent busy waiting
329
+ await asyncio.sleep(0.5)
330
+
331
+ logger.info(f"๐Ÿ Main loop completed: elapsed={time.time() - start_time:.1f}s, lines_captured={len(self._analysis.raw_output)}")
332
+
333
+ # Final analysis
334
+ async_result.update(
335
+ status=True,
336
+ progress=90,
337
+ message="Performing final analysis...",
338
+ data=self._get_analysis_data()
339
+ )
340
+
341
+ if analyze_output:
342
+ final_analysis = self._perform_comprehensive_analysis()
343
+ self._analysis = final_analysis
344
+
345
+ # Generate final report
346
+ report = self._generate_analysis_report()
347
+
348
+ async_result.update(
349
+ status=True,
350
+ progress=100,
351
+ message="Serial reading and analysis completed",
352
+ data={
353
+ "analysis": self._get_analysis_data(),
354
+ "report": report,
355
+ "total_output_lines": len(self._analysis.raw_output),
356
+ "execution_time": time.time() - start_time
357
+ }
358
+ )
359
+
360
+ logger.info(f"Returning AsyncExploitResult with status={async_result.status}, message='{async_result.message}'")
361
+ return async_result
362
+
363
+ except Exception as e:
364
+ logger.error(f"Error during serial reading: {str(e)}")
365
+ async_result.update(
366
+ status=False,
367
+ message=f"Serial reading failed: {str(e)}",
368
+ progress=100,
369
+ data={"error": str(e)}
370
+ )
371
+ return async_result
372
+ finally:
373
+ # Cleanup
374
+ await self._cleanup()
375
+
376
+ @hookimpl
377
+ def stop(self):
378
+ """Stop the running serial reading"""
379
+ logger.info("Stopping serial reading")
380
+ self._stop_reading = True
381
+
382
+ @hookimpl
383
+ def cleanup(self):
384
+ """Cleanup after serial reading"""
385
+ logger.info("Cleaning up PicocomSerialReaderPlugin")
386
+ # Perform synchronous cleanup instead of creating async task
387
+ self._cleanup_sync()
388
+
389
+ def _cleanup_sync(self):
390
+ """Synchronous cleanup for serial connection and threads"""
391
+ logger.info("Cleaning up serial connection (sync)...")
392
+ self._stop_reading = True
393
+
394
+ # Wait for reader thread to finish (non-blocking)
395
+ if self._reader_thread and self._reader_thread.is_alive():
396
+ self._reader_thread.join(timeout=2)
397
+ if self._reader_thread.is_alive():
398
+ logger.warning("Reader thread did not stop within 2s โ€“ skipping join to avoid hang")
399
+ # Do NOT attempt to close the serial port while the reader thread is still active
400
+ # to avoid potential deadlocks. We will detach and let it die with the process.
401
+ self._reader_thread = None
402
+ return # Skip the rest of cleanup
403
+
404
+ # Close serial connection (safe to do only if reader thread has stopped)
405
+ if self._serial_conn and self._serial_conn.is_open:
406
+ try:
407
+ self._serial_conn.close()
408
+ logger.info("Serial connection closed")
409
+ except Exception as e:
410
+ logger.error(f"Error closing serial connection: {e}")
411
+
412
+ self._serial_conn = None
413
+ self._reader_thread = None
414
+
415
+ async def _cleanup(self):
416
+ """Async cleanup for use during normal execution"""
417
+ logger.info("Cleaning up serial connection...")
418
+ self._stop_reading = True
419
+
420
+ # Wait for reader thread to finish (non-blocking)
421
+ if self._reader_thread and self._reader_thread.is_alive():
422
+ self._reader_thread.join(timeout=2)
423
+ if self._reader_thread.is_alive():
424
+ logger.warning("Reader thread did not stop within 2s โ€“ skipping join to avoid hang")
425
+ # Do NOT attempt to close the serial port while the reader thread is still active
426
+ # to avoid potential deadlocks. We will detach and let it die with the process.
427
+ self._reader_thread = None
428
+ return # Skip the rest of cleanup
429
+
430
+ # Close serial connection (safe to do only if reader thread has stopped)
431
+ if self._serial_conn and self._serial_conn.is_open:
432
+ try:
433
+ self._serial_conn.close()
434
+ logger.info("Serial connection closed")
435
+ except Exception as e:
436
+ logger.error(f"Error closing serial connection: {e}")
437
+
438
+ self._serial_conn = None
439
+ self._reader_thread = None
440
+
441
+ def _list_serial_ports(self) -> List[str]:
442
+ """List available serial ports"""
443
+ try:
444
+ ports = serial.tools.list_ports.comports()
445
+ return [port.device for port in ports]
446
+ except Exception as e:
447
+ logger.error(f"Error listing serial ports: {e}")
448
+ return []
449
+
450
+ async def _connect_serial(self, port: str, baudrate: int) -> bool:
451
+ """Connect to serial port"""
452
+ try:
453
+ log_both('info', f"๐Ÿ”Œ Attempting to connect to serial port {port} at {baudrate} baud...")
454
+ log_both('info', f"๐Ÿ”Œ Connection parameters: 8N1, timeout=1s, no flow control")
455
+
456
+ self._serial_conn = serial.Serial(
457
+ port=port,
458
+ baudrate=baudrate,
459
+ bytesize=serial.EIGHTBITS,
460
+ parity=serial.PARITY_NONE,
461
+ stopbits=serial.STOPBITS_ONE,
462
+ timeout=1,
463
+ xonxoff=False,
464
+ rtscts=False,
465
+ dsrdtr=False
466
+ )
467
+
468
+ if self._serial_conn.is_open:
469
+ log_both('info', f"โœ… Successfully connected to {port} at {baudrate} baud")
470
+ log_both('info', f"โœ… Serial port details: name={self._serial_conn.name}, is_open={self._serial_conn.is_open}")
471
+ log_both('info', f"โœ… Port settings: baudrate={self._serial_conn.baudrate}, bytesize={self._serial_conn.bytesize}")
472
+
473
+ # Try to read any initial data
474
+ initial_waiting = self._serial_conn.in_waiting
475
+ log_both('info', f"โœ… Initial bytes waiting: {initial_waiting}")
476
+
477
+ return True
478
+ else:
479
+ log_both('error', f"โŒ Failed to open serial port {port}")
480
+ return False
481
+
482
+ except Exception as e:
483
+ log_both('error', f"โŒ Error connecting to serial port {port}: {e}")
484
+ log_both('error', f"โŒ Exception type: {type(e).__name__}")
485
+ return False
486
+
487
+ def _start_reader_thread(self):
488
+ """Start the serial reading thread"""
489
+ if self._reader_thread and self._reader_thread.is_alive():
490
+ return
491
+
492
+ self._reader_thread = Thread(target=self._reader_loop, daemon=True)
493
+ self._reader_thread.start()
494
+
495
+ def _reader_loop(self):
496
+ """Main reading loop running in separate thread"""
497
+ logger.info("๐Ÿงต Serial reader thread started")
498
+ buffer = b""
499
+ total_bytes_read = 0
500
+ total_lines_queued = 0
501
+ last_activity_log = time.time()
502
+ activity_log_interval = 30.0 # Log activity every 30 seconds
503
+
504
+ while not self._stop_reading and self._serial_conn and self._serial_conn.is_open:
505
+ try:
506
+ current_time = time.time()
507
+
508
+ # Periodic activity logging
509
+ if current_time - last_activity_log >= activity_log_interval:
510
+ logger.info(f"๐Ÿงต Reader thread status: bytes_read={total_bytes_read}, lines_queued={total_lines_queued}")
511
+ logger.info(f"๐Ÿงต Buffer size: {len(buffer)} bytes")
512
+ logger.info(f"๐Ÿงต Serial port connected: {self._serial_conn.is_open}")
513
+ last_activity_log = current_time
514
+
515
+ waiting = self._serial_conn.in_waiting
516
+ if waiting > 0:
517
+ logger.debug(f"๐Ÿงต Reading {waiting} bytes from serial port")
518
+ data = self._serial_conn.read(waiting)
519
+ buffer += data
520
+ total_bytes_read += len(data)
521
+
522
+ logger.debug(f"๐Ÿงต Read {len(data)} bytes, buffer now {len(buffer)} bytes")
523
+
524
+ # Process complete lines
525
+ lines_in_this_batch = 0
526
+ while b'\n' in buffer or b'\r' in buffer:
527
+ if b'\r\n' in buffer:
528
+ line, buffer = buffer.split(b'\r\n', 1)
529
+ elif b'\n' in buffer:
530
+ line, buffer = buffer.split(b'\n', 1)
531
+ elif b'\r' in buffer:
532
+ line, buffer = buffer.split(b'\r', 1)
533
+
534
+ try:
535
+ decoded_line = line.decode('utf-8', errors='replace').strip()
536
+ if decoded_line: # Only queue non-empty lines
537
+ self._output_queue.put(decoded_line)
538
+ lines_in_this_batch += 1
539
+ total_lines_queued += 1
540
+ logger.debug(f"๐Ÿงต Queued line #{total_lines_queued}: {repr(decoded_line)}")
541
+ except Exception as e:
542
+ logger.warning(f"๐Ÿงต Error decoding line: {e}")
543
+
544
+ if lines_in_this_batch > 0:
545
+ logger.info(f"๐Ÿงต Processed {lines_in_this_batch} lines from {len(data)} bytes")
546
+ else:
547
+ logger.debug(f"๐Ÿงต No data waiting (checked at {current_time:.1f})")
548
+
549
+ time.sleep(0.01) # Small delay to prevent excessive CPU usage
550
+
551
+ except Exception as e:
552
+ logger.error(f"๐Ÿงต Error in reader loop: {e}")
553
+ logger.error(f"๐Ÿงต Exception type: {type(e).__name__}")
554
+ import traceback
555
+ logger.error(f"๐Ÿงต Traceback: {traceback.format_exc()}")
556
+ break
557
+
558
+ logger.info(f"๐Ÿงต Serial reader thread ended - Total: {total_bytes_read} bytes, {total_lines_queued} lines")
559
+
560
+ def _collect_output(self) -> List[str]:
561
+ """Collect available output from the queue"""
562
+ output = []
563
+ try:
564
+ while True:
565
+ line = self._output_queue.get_nowait()
566
+ output.append(line)
567
+ except Empty:
568
+ pass
569
+ return output
570
+
571
+ def _send_data(self, data: str) -> bool:
572
+ """Send data to serial port"""
573
+ try:
574
+ if self._serial_conn and self._serial_conn.is_open:
575
+ encoded_data = data.encode('utf-8')
576
+ log_both('info', f"๐Ÿ“ค Sending {len(encoded_data)} bytes: {repr(data)}")
577
+ self._serial_conn.write(encoded_data)
578
+ self._serial_conn.flush()
579
+ log_both('info', f"๐Ÿ“ค Data sent and flushed successfully")
580
+ return True
581
+ else:
582
+ log_both('error', f"๐Ÿ“ค Cannot send data - serial connection not available")
583
+ return False
584
+ except Exception as e:
585
+ log_both('error', f"๐Ÿ“ค Error sending data: {e}")
586
+ log_both('error', f"๐Ÿ“ค Exception type: {type(e).__name__}")
587
+ return False
588
+
589
+ def _analyze_output_patterns(self, output_lines: List[str]):
590
+ """Analyze output for patterns in real-time"""
591
+ for line in output_lines:
592
+ line_lower = line.lower()
593
+
594
+ # Check for login patterns
595
+ for pattern in self._login_patterns:
596
+ if re.search(pattern, line_lower):
597
+ self._analysis.login_detected = True
598
+ self._analysis.detected_patterns.append(f"login_pattern: {pattern}")
599
+ logger.info(f"Login pattern detected: {pattern}")
600
+
601
+ # Check for shell types
602
+ for shell_type, patterns in self._shell_patterns.items():
603
+ for pattern in patterns:
604
+ if re.search(pattern, line, re.IGNORECASE):
605
+ if self._analysis.shell_type == "unknown" or self._analysis.confidence < 0.8:
606
+ self._analysis.shell_type = shell_type
607
+ self._analysis.device_type = shell_type
608
+ self._analysis.confidence = 0.8
609
+ self._analysis.detected_patterns.append(f"shell_pattern: {shell_type}")
610
+ logger.info(f"Shell type detected: {shell_type}")
611
+
612
+ def _perform_comprehensive_analysis(self) -> SerialAnalysis:
613
+ """Perform comprehensive analysis of all collected output"""
614
+ analysis = SerialAnalysis()
615
+ analysis.raw_output = self._analysis.raw_output.copy()
616
+ analysis.detected_patterns = self._analysis.detected_patterns.copy()
617
+ analysis.login_detected = self._analysis.login_detected
618
+
619
+ all_output = "\n".join(analysis.raw_output)
620
+ all_output_lower = all_output.lower()
621
+
622
+ # Advanced pattern matching
623
+ confidence_scores = {}
624
+
625
+ # Boot sequence analysis
626
+ boot_patterns = {
627
+ 'uboot': [r'u-boot', r'hit any key to stop autoboot', r'=>'],
628
+ 'linux': [r'linux version', r'kernel', r'init:', r'systemd'],
629
+ 'busybox': [r'busybox', r'built-in shell'],
630
+ 'embedded': [r'firmware', r'bootloader', r'flash'],
631
+ 'network': [r'ip address', r'dhcp', r'network', r'ethernet'],
632
+ 'iot': [r'wifi', r'sensor', r'temperature', r'gpio']
633
+ }
634
+
635
+ for device_type, patterns in boot_patterns.items():
636
+ score = 0
637
+ for pattern in patterns:
638
+ matches = len(re.findall(pattern, all_output_lower))
639
+ score += matches * 0.2
640
+
641
+ if score > 0:
642
+ confidence_scores[device_type] = min(score, 1.0)
643
+
644
+ # Determine most likely device type
645
+ if confidence_scores:
646
+ best_match = max(confidence_scores.items(), key=lambda x: x[1])
647
+ analysis.device_type = best_match[0]
648
+ analysis.confidence = best_match[1]
649
+
650
+ # Extract firmware information
651
+ version_patterns = [
652
+ r'version\s+([0-9.]+)',
653
+ r'firmware\s+([0-9.]+)',
654
+ r'build\s+([0-9.]+)',
655
+ r'rev\s+([0-9.]+)'
656
+ ]
657
+
658
+ for pattern in version_patterns:
659
+ matches = re.findall(pattern, all_output_lower)
660
+ if matches:
661
+ analysis.firmware_info['version'] = matches[0]
662
+ break
663
+
664
+ return analysis
665
+
666
+ def _generate_analysis_report(self) -> str:
667
+ """Generate a human-readable analysis report"""
668
+ report = []
669
+ report.append("=== Serial Port Analysis Report ===")
670
+ report.append(f"Device Type: {self._analysis.device_type}")
671
+ report.append(f"Confidence: {self._analysis.confidence:.2f}")
672
+ report.append(f"Login Shell Detected: {'Yes' if self._analysis.login_detected else 'No'}")
673
+ report.append(f"Shell Type: {self._analysis.shell_type}")
674
+ report.append(f"Total Output Lines: {len(self._analysis.raw_output)}")
675
+
676
+ if self._analysis.firmware_info:
677
+ report.append("\nFirmware Information:")
678
+ for key, value in self._analysis.firmware_info.items():
679
+ report.append(f" {key}: {value}")
680
+
681
+ if self._analysis.detected_patterns:
682
+ report.append("\nDetected Patterns:")
683
+ for pattern in set(self._analysis.detected_patterns): # Remove duplicates
684
+ report.append(f" - {pattern}")
685
+
686
+ if self._analysis.raw_output:
687
+ report.append("\nSample Output (last 10 lines):")
688
+ for line in self._analysis.raw_output[-10:]:
689
+ report.append(f" > {line}")
690
+
691
+ return "\n".join(report)
692
+
693
+ def _get_analysis_data(self) -> Dict:
694
+ """Get analysis data as dictionary"""
695
+ return {
696
+ "device_type": self._analysis.device_type,
697
+ "confidence": self._analysis.confidence,
698
+ "login_detected": self._analysis.login_detected,
699
+ "shell_type": self._analysis.shell_type,
700
+ "firmware_info": self._analysis.firmware_info,
701
+ "detected_patterns": self._analysis.detected_patterns,
702
+ "output_lines_count": len(self._analysis.raw_output),
703
+ "raw_output_sample": self._analysis.raw_output[-5:] if self._analysis.raw_output else []
704
+ }