Update README.md
This commit is contained in:
248
README.md
248
README.md
@@ -1,8 +1,8 @@
|
||||
# ⚡ IoT Firmware Loader
|
||||
# ⚡ IoT Firmware Loader (MiraV3)
|
||||
|
||||
Công cụ desktop dùng để **scan, phát hiện và flash firmware hàng loạt** cho các thiết bị IoT (ESP32/ESP8266) trong mạng LAN.
|
||||
Công cụ desktop dùng để **scan, phát hiện và flash firmware hàng loạt** cho các thiết bị OpenWrt (qua giao diện LuCI) trong mạng LAN.
|
||||
|
||||
> **Tech stack:** Python 3.9+ · PyQt6 · Scapy · Requests
|
||||
> **Tech stack:** Python 3.9+ · PyQt6 · Scapy · Requests · PyInstaller
|
||||
|
||||
---
|
||||
|
||||
@@ -10,13 +10,13 @@ Công cụ desktop dùng để **scan, phát hiện và flash firmware hàng lo
|
||||
|
||||
```
|
||||
iot_fw_loader/
|
||||
├── main.py # UI chính (PyQt6) + điều phối toàn bộ luồng
|
||||
├── scanner.py # Quét thiết bị trong mạng LAN
|
||||
├── device_filter.py # Lọc thiết bị IoT theo MAC vendor
|
||||
├── flasher.py # Upload firmware qua HTTP
|
||||
├── main.py # UI chính (PyQt6) + Điều phối luồng và xử lý đa luồng
|
||||
├── scanner.py # Quét thiết bị mạng đa lớp (Ping sweep + ARP + Scapy)
|
||||
├── flasher.py # Upload firmware và tự động hóa qua giao diện OpenWrt LuCI
|
||||
├── run.sh # Script khởi chạy (macOS/Linux)
|
||||
├── run.bat # Script khởi chạy (Windows)
|
||||
└── venv/ # Python virtual environment
|
||||
├── build_windows.bat # Script đóng gói thành file .exe độc lập (Windows)
|
||||
└── venv/ # Python virtual environment (tự tạo)
|
||||
```
|
||||
|
||||
---
|
||||
@@ -26,9 +26,10 @@ iot_fw_loader/
|
||||
### Yêu cầu
|
||||
|
||||
- Python 3.9+
|
||||
- Các thư viện: `PyQt6`, `scapy`, `requests`
|
||||
- Các thư viện: `PyQt6`, `scapy`, `requests`, `pyinstaller` (để build).
|
||||
- *Trên Windows:* Cần cài đặt [Npcap](https://npcap.com/) để `scapy` có thể quét ARP ở chế độ sâu (không bắt buộc, có fallback dự phòng).
|
||||
|
||||
### Khởi chạy nhanh
|
||||
### Khởi chạy nhanh (Môi trường Dev)
|
||||
|
||||
**macOS / Linux:**
|
||||
|
||||
@@ -44,212 +45,93 @@ run.bat
|
||||
|
||||
> Script tự tạo `venv` và cài dependencies nếu chưa có.
|
||||
|
||||
**Hoặc chạy thủ công:**
|
||||
### 📦 Build ra file chạy độc lập (.exe) cho Windows
|
||||
Chạy script sau để tự động đóng gói ứng dụng thành 1 file `.exe` duy nhất (không cần cài Python trên máy đích):
|
||||
|
||||
```bash
|
||||
source venv/bin/activate
|
||||
python main.py
|
||||
```bat
|
||||
build_windows.bat
|
||||
```
|
||||
|
||||
File nhận được sẽ nằm ở: `dist\IoT_Firmware_Loader.exe`.
|
||||
|
||||
---
|
||||
|
||||
## 🏗 Kiến trúc hệ thống
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ main.py (UI) │
|
||||
│ ┌───────────┐ ┌───────────┐ ┌────────────────────┐ │
|
||||
│ │ Machine │ │ Firmware │ │ Network Scan │ │
|
||||
│ │ Info │ │ Selector │ │ (QThread) │ │
|
||||
│ └───────────┘ └───────────┘ └────────┬───────────┘ │
|
||||
│ ┌────────────────────────────────────┼───────────┐ │
|
||||
│ ┌───────────┐ ┌───────────┐ ┌────────────────────────────┐ │
|
||||
│ │ Machine │ │ Firmware │ │ Network Scan (QThread) │ │
|
||||
│ │ Info │ │ Selector │ │ Tham số: Network (CIDR) │ │
|
||||
│ └───────────┘ └───────────┘ └────────┬───────────────────┘ │
|
||||
│ ┌────────────────────────────────────┼───────────────────┐ │
|
||||
│ │ Device Table │ │ │
|
||||
│ │ IP │ MAC │ Type │ Status │ │ │
|
||||
│ └────────────────────────────────────┼───────────┘ │
|
||||
│ ┌────────────────────────────────────┼───────────┐ │
|
||||
│ │ [ ] IP │ Name │ MAC │ Status │ │ │
|
||||
│ │ (Hỗ trợ lọc ẩn Gateway & Self) │ │ │
|
||||
│ └────────────────────────────────────┼───────────────────┘ │
|
||||
│ ┌────────────────────────────────────┼───────────────────┐ │
|
||||
│ │ Flash Controls + Progress Bar │ │ │
|
||||
│ │ (ThreadPoolExecutor × 5) │ │ │
|
||||
│ └────────────────────────────────────┼───────────┘ │
|
||||
└───────────────────────────────────────┼─────────────┘
|
||||
│ │ (ThreadPoolExecutor) │ │ │
|
||||
│ └────────────────────────────────────┼───────────────────┘ │
|
||||
└───────────────────────────────────────┼─────────────────────┘
|
||||
│
|
||||
┌───────────────────┼───────────────────┐
|
||||
│ │ │
|
||||
┌─────▼─────┐ ┌──────▼──────┐ ┌──────▼──────┐
|
||||
│ scanner.py │ │device_filter│ │ flasher.py │
|
||||
│ │ │ .py │ │ │
|
||||
│ Scapy ARP │ │ MAC vendor │ │ HTTP POST │
|
||||
│ ↓ fallback│ │ matching │ │ /update │
|
||||
│ arp -a │ │ │ │ │
|
||||
└────────────┘ └─────────────┘ └─────────────┘
|
||||
│ │
|
||||
┌─────▼─────┐ ┌─────▼─────┐
|
||||
│ scanner.py│ │ flasher.py│
|
||||
│ │ │ │
|
||||
│ 1. Ping │ │ LuCI HTTP │
|
||||
│ Sweep │ │ /cgi-bin/ │
|
||||
│ 2. arp -a │ │ luci │
|
||||
│ 3. Scapy │ │ │
|
||||
└───────────┘ └───────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Luồng hoạt động chi tiết
|
||||
|
||||
### Tổng quan
|
||||
### 1. Quét mạng (Network Scan)
|
||||
|
||||
```
|
||||
Khởi động App ──▶ Hiển thị Machine Info
|
||||
│
|
||||
▼
|
||||
Chọn Firmware (.bin/.hex/.uf2)
|
||||
│
|
||||
▼
|
||||
Nhập dải mạng ──▶ Nhấn "Scan LAN"
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────┐
|
||||
│ ScanThread (background) │
|
||||
│ 1. Thử Scapy ARP broadcast │
|
||||
│ 2. Nếu lỗi → fallback arp -a │
|
||||
└──────────────┬──────────────────┘
|
||||
▼
|
||||
Lọc IoT device theo MAC vendor
|
||||
│
|
||||
▼
|
||||
Hiển thị bảng: tất cả thiết bị
|
||||
(IoT = 🟢 xanh lá, Other = ⚪)
|
||||
│
|
||||
▼
|
||||
Nhấn "Flash All IoT Devices"
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────┐
|
||||
│ ThreadPoolExecutor (5 workers) │
|
||||
│ POST firmware → mỗi thiết bị │
|
||||
│ Cập nhật status + progress bar │
|
||||
└──────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
Hoàn tất ✅
|
||||
```
|
||||
Module `scanner.py` sử dụng chiến lược 3 lớp để đảm bảo phát hiện đa dạng các thiết bị mạng mà vẫn duy trì khả năng tránh spam/lag cho OS:
|
||||
|
||||
---
|
||||
1. **Ping Sweep:** Gửi gói tin Ping song song (tối đa 50 threads) để đánh thức thiết bị và điền IP/MAC vào bảng ARP Cache của hệ điều hành.
|
||||
2. **ARP Table Fallback:** Đọc bảng ARP nội bộ của OS (`arp -a`) bằng Regex. Hoạt động đa nền tảng (Windows/macOS/Linux) mà không cần quyền Admin/Root.
|
||||
3. **Scapy ARP (Tính năng nâng cao):** Gửi các gói tin ARP Broadcast trực tiếp để đảm bảo bao phủ gap nếu có. Yêu cầu quyền Root trên Linux/macOS hoặc Npcap trên Windows.
|
||||
*Ngoài ra, công cụ tự động dò Hostname (`socket.gethostbyaddr`) song song để lấy tên thiết bị.*
|
||||
|
||||
## 📋 Phân tích từng module
|
||||
### 2. Giao diện thiết bị (Device Table)
|
||||
|
||||
### 1. `main.py` — Giao diện & Điều phối
|
||||
- Thiết bị được thu thập sẽ hiện ra trên UI dạng bảng, với tính năng tuỳ chọn hiển thị (checkbox ẩn Gateway mạng hiện tại và chính máy tính đang quét phần mềm).
|
||||
- Trạng thái các luồng UI được tách rời bằng QThread + QPropertyAnimation cho hộp Collapsible nhằm tự động cuộn khi ẩn hiện nội dung, không làm treo ứng dụng.
|
||||
|
||||
| Thành phần | Mô tả |
|
||||
| ------------------------- | --------------------------------------------- |
|
||||
| `get_local_ip()` | Lấy IP máy tính qua UDP socket tới `8.8.8.8` |
|
||||
| `get_default_network(ip)` | Tự tính dải mạng `/24` từ IP máy |
|
||||
| `get_machine_info()` | Thu thập hostname, IP, OS, MAC |
|
||||
| `ScanThread` | `QThread` chạy scan ở background, tránh đơ UI |
|
||||
| `App` | Widget chính chứa toàn bộ UI |
|
||||
### 3. Nạp Firmware (OpenWrt Flasher)
|
||||
|
||||
**Các phần UI:**
|
||||
Từ phiên bản hiện tại, phương thức ESP32 OTA không còn được áp dụng, thay vào đó module `flasher.py` tự động hóa quá trình update của router **OpenWrt (Barrier Breaker 14.07)**:
|
||||
|
||||
| Group Box | Chức năng |
|
||||
| ---------------- | -------------------------------------- |
|
||||
| 🖥 Machine Info | Hiển thị Hostname, IP, OS, MAC của máy |
|
||||
| 📦 Firmware | Chọn file firmware |
|
||||
| 📡 Network Scan | Nhập dải mạng + nút Scan |
|
||||
| 📋 Devices Found | Bảng thiết bị (IP, MAC, Type, Status) |
|
||||
| 🚀 Flash | Progress bar + nút Flash All |
|
||||
|
||||
**Xử lý sự kiện chính:**
|
||||
|
||||
| Method | Trigger | Hành động |
|
||||
| ------------------ | ------------------ | ------------------------------------------ |
|
||||
| `select_fw()` | Nhấn "Select File" | Mở dialog chọn firmware |
|
||||
| `scan()` | Nhấn "Scan LAN" | Tạo `ScanThread` → chạy background |
|
||||
| `_on_scan_done()` | Scan hoàn tất | Phân loại IoT/Other → hiển thị bảng |
|
||||
| `_on_scan_error()` | Scan lỗi | Hiện `QMessageBox` lỗi |
|
||||
| `flash_all()` | Nhấn "Flash All" | Tạo `ThreadPoolExecutor` → flash song song |
|
||||
| `flash_worker()` | Mỗi thread | POST firmware → cập nhật status |
|
||||
|
||||
---
|
||||
|
||||
### 2. `scanner.py` — Quét mạng
|
||||
|
||||
**Chiến lược 2 lớp (dual-layer scan):**
|
||||
|
||||
| Phương pháp | Hàm | Yêu cầu | Độ chính xác |
|
||||
| ----------------------- | ------------------------ | -------------- | ------------------------------------------- |
|
||||
| **Primary:** Scapy ARP | `_scan_with_scapy()` | Root/sudo | Cao — scan active |
|
||||
| **Fallback:** ARP table | `_scan_with_arp_table()` | Không cần root | Trung bình — chỉ thấy thiết bị đã giao tiếp |
|
||||
|
||||
**Luồng trong `scan_network()`:**
|
||||
|
||||
```python
|
||||
try:
|
||||
return _scan_with_scapy(network) # Cần root
|
||||
except:
|
||||
return _scan_with_arp_table(network) # Fallback, parse "arp -a"
|
||||
```
|
||||
|
||||
**Scapy ARP scan:**
|
||||
|
||||
- Tạo ARP request → broadcast `ff:ff:ff:ff:ff:ff`
|
||||
- Thu thập response → trích IP + MAC
|
||||
- Timeout: 2 giây
|
||||
|
||||
**ARP table fallback:**
|
||||
|
||||
- Chạy lệnh `arp -a` của hệ thống
|
||||
- Parse output bằng regex: `\(IP\) at MAC`
|
||||
- Lọc theo dải mạng đầu vào
|
||||
|
||||
---
|
||||
|
||||
### 3. `device_filter.py` — Phân loại thiết bị
|
||||
|
||||
Lọc thiết bị IoT dựa trên **3 byte đầu của MAC address** (OUI — Organizationally Unique Identifier):
|
||||
|
||||
| MAC Prefix | Vendor |
|
||||
| ---------- | ------------------------- |
|
||||
| `24:6F:28` | Espressif (ESP32/ESP8266) |
|
||||
| `84:F3:EB` | Espressif |
|
||||
| `DC:4F:22` | Espressif |
|
||||
|
||||
> **Lưu ý:** Chỉ thiết bị có MAC bắt đầu bằng các prefix trên mới được đánh dấu là IoT. Tất cả thiết bị khác vẫn hiển thị trong bảng với label "Other".
|
||||
|
||||
---
|
||||
|
||||
### 4. `flasher.py` — Nạp firmware
|
||||
|
||||
| Bước | Chi tiết |
|
||||
| ------------- | --------------------------------------------------- |
|
||||
| 1. Mở file | `open(firmware_path, "rb")` |
|
||||
| 2. Upload | `POST http://{ip}/update` với `multipart/form-data` |
|
||||
| 3. Chờ reboot | `time.sleep(3)` — đợi thiết bị khởi động lại |
|
||||
| 4. Kết quả | Trả `"DONE"` hoặc `"FAIL"` |
|
||||
|
||||
> **Giả định:** Thiết bị IoT chạy HTTP server với endpoint `/update` hỗ trợ OTA firmware update (chuẩn ESP32 Arduino OTA).
|
||||
* **Bước 1:** Gửi `POST` chứa thông tin đăng nhập (username, password mặc định là root/trống, hoặc luci_username tuỳ thuộc firmware) để lấy session cookie `stok`.
|
||||
* **Bước 2:** Đẩy file firmware `*.bin` dạng `multipart/form-data` lên `/cgi-bin/luci/;stok=.../admin/system/flashops`.
|
||||
* **Bước 3:** Gửi lệnh tiếp tục (Kèm tuỳ chọn giữ cấu hình `keep=on` nếu có).
|
||||
* **Bước 4:** Thiết bị xác nhận và tự khởi động lại. Cập nhật Status trên Application.
|
||||
* 🛠 Hỗ trợ `ThreadPoolExecutor` để Flash đồng thời nhiều thiết bị, với lựa chọn số luồng đồng thời tuỳ chỉnh.
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Cấu hình
|
||||
|
||||
| Tham số | Giá trị | Vị trí |
|
||||
| ----------------- | ----------------------- | ----------------------------------------------- |
|
||||
| Network range | Tự tính `/24` từ IP máy | `main.py` → `get_default_network()` |
|
||||
| Scan timeout | 2 giây | `scanner.py` → `srp(..., timeout=2)` |
|
||||
| Flash timeout | 10 giây | `flasher.py` → `requests.post(..., timeout=10)` |
|
||||
| Reboot wait | 3 giây | `flasher.py` → `time.sleep(3)` |
|
||||
| Max flash workers | 5 | `main.py` → `ThreadPoolExecutor(max_workers=5)` |
|
||||
| Firmware filter | `.bin .hex .uf2` | `main.py` → `QFileDialog` filter |
|
||||
| Tham số | Mô tả |
|
||||
| ---------------------- | ------------------------------------------------------------------- |
|
||||
| **Network** | Dải mạng quét (vd: `192.168.1.0/24`). Tự động tính từ local IP app. |
|
||||
| **Show all** | Tuỳ chọn cho phép liệt kê cả Gateway IP máy chủ. |
|
||||
| **Concurrent devices** | Số luồng để đĩa flash song song. `0` = Không giới hạn. |
|
||||
| **Firmware filter** | Mặc định hiển thị và hỗ trợ `.bin`, `.hex`, `.uf2`. |
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Lưu ý bảo mật & Quyền
|
||||
|
||||
- **Scapy cần root/sudo** trên macOS/Linux để gửi raw ARP packets
|
||||
- Nếu không có quyền root → tự động dùng `arp -a` (không cần root nhưng chỉ thấy thiết bị đã giao tiếp gần đây)
|
||||
- Firmware upload qua **HTTP không mã hóa** — chỉ nên dùng trong mạng LAN nội bộ
|
||||
|
||||
---
|
||||
|
||||
## 📊 Trạng thái thiết bị (State Machine)
|
||||
|
||||
```
|
||||
READY ──────▶ FLASHING
|
||||
│ │
|
||||
│ ┌─────┴──────┐
|
||||
│ ▼ ▼
|
||||
│ ✅ DONE ❌ FAIL
|
||||
│
|
||||
└── (thiết bị Other: trạng thái "—", không flash)
|
||||
```
|
||||
- **Quyền Scanner:** Ở chế độ quét Scapy (Sâu), cần khởi chạy bằng quyền Admin (Windows) hoặc Sudo (macOS/Linux), tuy nhiên App có thể chạy kể cả không có quyền Admin nhờ chiến lược Ping Sweep + `arp -a`.
|
||||
- **Ẩn Console (Window):** Khi gọi subproces (`ping`, `arp`), ứng dụng có tích hợp thuộc tính platform `CREATE_NO_WINDOW` để ngăn chặn các terminal đen giật nháy trên màn hình Windows.
|
||||
- **Bảo mật mạng:** Quá trình tải firmware lên thiết bị thông qua HTTP thuần, chỉ nên sử dụng ở mạng LAN nội bộ, tránh những mạng mở hoặc public để tránh lộ file firmware.
|
||||
- **LuCI Login:** API này nhắm thẳng vào quá trình đăng nhập qua form, nên sẽ tự động handle các router đang dùng OpenWrt Barrier Breaker mà không cần phải can thiệp tay.
|
||||
|
||||
Reference in New Issue
Block a user