Bintec W20XXax-Firmware in QEMU (Docker): Extraktion, generischer Kernel-Boot, Doku
This commit is contained in:
@@ -0,0 +1,270 @@
|
||||
# Bintec W20XXax-Firmware in QEMU (Docker) – Emulations- und Analyse-Umgebung
|
||||
|
||||
Die AP-Firmware (Bintec W2022ax / W2044ax, "Wanda Wave 2") laeuft **nicht** direkt
|
||||
in QEMU: Sie ist fuer einen Qualcomm IPQ60xx-SoC ("kaio") gebaut, und QEMU hat
|
||||
dafuer **kein Maschinenmodell**. Der Weg der Emulation ist daher:
|
||||
|
||||
> Firmware auseinandernehmen -> RootFS extrahieren -> RootFS mit generischem
|
||||
> Debian-arm64-Kernel in `qemu-system-aarch64 -M virt` booten -> Webinterface + CLI beobachten
|
||||
|
||||
Das gesamte Setup laeuft unprivilegiert in Docker (TCG-Emulation, kein KVM,
|
||||
keine Root-Rechte auf dem Host noetig). Alles liegt in diesem Ordner
|
||||
(`qemu-fw-docker/`), der Host bleibt unveraendert.
|
||||
|
||||
## Ergebnis
|
||||
|
||||
```
|
||||
root@osdx:/# uname -a
|
||||
Linux osdx 6.1.0-50-arm64 #1 SMP Debian 6.1.176-1 aarch64 GNU/Linux
|
||||
```
|
||||
|
||||
- Das OSDx-System bootet bis zur interaktiven **Root-Shell** auf der Serial-Console
|
||||
- Das originale **Webinterface** antwortet (Login-Seite "bintec Wanda Wave 2",
|
||||
XML/XSL-GUI ueber esi.cgi) auf Port 80 und 443
|
||||
- SSH ist erreichbar (Port 22 in der VM)
|
||||
- `poweroff`/`reboot` in der VM funktionieren sauber (Reboot-Loop ist entschaeft)
|
||||
|
||||
## Schnellstart (VM + Webinterface)
|
||||
|
||||
```bash
|
||||
cd qemu-fw-docker
|
||||
docker build -t bintec-fw . # einmalig
|
||||
./start-vm.sh # VM als Container "bintec-vm" starten
|
||||
```
|
||||
|
||||
Nach ~2-3 Minuten Boot:
|
||||
|
||||
| Zugang | Adresse | Anmerkung |
|
||||
|---------------------|--------------------------------|-------------------------------|
|
||||
| Webinterface HTTP | http://localhost:8080 | |
|
||||
| Webinterface HTTPS | https://localhost:8443 | self-signed Zertifikat |
|
||||
| SSH | ssh -p 2222 localhost | root/leer bzw. admin |
|
||||
| Serial (lesend) | docker logs -f bintec-vm | |
|
||||
| Serial (interaktiv) | docker attach bintec-vm | abkoppeln: Strg+P dann Strg+Q |
|
||||
| Stoppen | docker stop bintec-vm && docker rm bintec-vm | |
|
||||
|
||||
Serial-Login: die VM startet direkt in eine Root-Shell auf ttyAMA0
|
||||
(keine Anmeldung noetig).
|
||||
|
||||
## Von der Firmware zum lauffaehigen System (kompletter Ablauf)
|
||||
|
||||
### Schritt 0: Firmware-Paket besorgen
|
||||
|
||||
Es gibt zwei Paketformen (beide von Bintec/Teldat, Inhalt identisch):
|
||||
|
||||
| Paket | Inhalt | Aufbereitung |
|
||||
|-------|--------|--------------|
|
||||
| `WiFi6_System_Software_vX.Y.Z.img` | Full-Flash-Image, direkt verwendbar | kopieren nach `firmware/` |
|
||||
| `OSDx_dist_W20XXax_vX.Y.Z.tgz` | Tar-Archiv mit `full_kaio_vX.Y.Z.img` + `README.txt` + `version_map.txt` | entpacken, .img nach `firmware/` |
|
||||
|
||||
Falls das Paket als Tar kommt:
|
||||
|
||||
```bash
|
||||
tar xzf OSDx_dist_W20XXax_v3.6.1.2.tgz
|
||||
# -> full_kaio_v3.6.1.2.img (identisch zur .img-Variante)
|
||||
mkdir -p firmware && cp full_kaio_v3.6.1.2.img firmware/
|
||||
```
|
||||
|
||||
Die `version_map.txt` im Archiv sagt, welche img-Datei zu welchem AP-Modell
|
||||
gehoert (W2022ax / W2044ax / APR2044ax verwenden alle dasselbe `full_kaio`-Image).
|
||||
|
||||
### Schritt 1: Container-Image bauen (einmalig)
|
||||
|
||||
```bash
|
||||
cd qemu-fw-docker
|
||||
docker build -t bintec-fw .
|
||||
```
|
||||
|
||||
### Schritt 2: Firmware zerlegen (Phase 2)
|
||||
|
||||
```bash
|
||||
docker run --rm \
|
||||
-v "$PWD:/build" \
|
||||
-v "$PWD/firmware:/input:ro" \
|
||||
bintec-fw /build/extract.sh /input/<name-der>.img
|
||||
```
|
||||
|
||||
Was passiert:
|
||||
1. `carve.py` sucht das SquashFS (versionsunabhaengig per Magic + Plausibilitaets-
|
||||
pruefung, uebernimmt aber den bekannten Offset von v3.6.1.2 als Direkttreffer)
|
||||
und schneidet es aus: `work/rootfs.squashfs`
|
||||
2. Das FIT-Image (ARM64-Kernel gzip + Device-Trees) wird als FDT geparst:
|
||||
`work/kernel.img` (bootfaehig), `work/kernel.raw`, `work/<modell>.dtb`
|
||||
3. `unsquashfs` -> `work/rootfs/` (Debian-Userspace)
|
||||
4. Inventur nach `logs/inventory.txt` (OS-Version, Dienste, GUI-Pfade, Module)
|
||||
|
||||
### Schritt 3: Boot-Artefakte bauen (Phase 4a)
|
||||
|
||||
```bash
|
||||
docker run --rm -v "$PWD:/build" bintec-fw /build/build-generic.sh
|
||||
```
|
||||
|
||||
- laedt Debian-arm64-Kernel (`linux-image-*-arm64`, bookworm) + arm64-busybox
|
||||
- baut Mini-Initramfs (virtio/ext4/crc32c-Module + switch_root)
|
||||
- kopiert RootFS nach `work/rootfs-generic/`, legt alle Emulations-Anpassungen
|
||||
an (siehe Tabelle unten) und verpackt alles nach `work/rootfs.ext4`
|
||||
- Schritte 2+3 sind idempotent und duerfen wiederholt werden
|
||||
|
||||
### Schritt 4: Booten
|
||||
|
||||
```bash
|
||||
./start-vm.sh # Dauerbetrieb + Webinterface
|
||||
# oder mit Log-Tail:
|
||||
docker run --rm -v "$PWD:/build" bintec-fw /build/boot-generic.sh
|
||||
```
|
||||
|
||||
### Andere Firmware-Versionen emulieren
|
||||
|
||||
- `carve.py` ist generisch gehalten: SquashFS wird ueber Magic+Superblock-
|
||||
Pruefung gesucht, das FIT ueber `kernel@1` + beliebiges `fdt@*`-Node
|
||||
(bevorzugt w2044ax). Getestet gegen v3.6.1.1 und v3.6.1.2 (`archive_.../`).
|
||||
- Ablauf fuer eine neue Version: img in `firmware/` legen, Schritte 2-4 wiederholen.
|
||||
`work/` vorher optional leeren (`rm -rf work/rootfs* work/kernel* work/fit.img`).
|
||||
- Achtung: `work/rootfs.ext4` ist versionsspezifisch – nach dem Wechsel auf
|
||||
jeden Fall Schritt 3 neu ausfuehren.
|
||||
- Falls eine kuenftige Version eine andere FIT-Struktur nutzt, verraten das
|
||||
`logs/carve.log` (Kandidaten + Metadaten) und `binwalk firmware/<img>`.
|
||||
|
||||
## Ordnerinhalt
|
||||
|
||||
```
|
||||
qemu-fw-docker/
|
||||
├── firmware/
|
||||
│ └── WiFi6_System_Software_v3.6.1.2.img Original-Firmware (143 MB, md5 bb4c065e...)
|
||||
├── Dockerfile Container: Debian bookworm-slim + QEMU/Tools
|
||||
├── carve.py Firmware-Carver: SquashFS + FIT (Kernel/DTB) extrahieren
|
||||
├── extract.sh Phase 2: Firmware zerlegen + Inventur (erwartet /input/*.img)
|
||||
├── build-generic.sh Phase 4: Debian-Kernel laden, RootFS aufbereiten, ext4+Initramfs bauen
|
||||
├── boot-native.sh Versuch 1: Original-QSDK-Kernel in -M virt (erwartungsgemaess stumm)
|
||||
├── boot-generic.sh Versuch 2 mit Logging (Timeout 420 s)
|
||||
├── boot-interactive.sh Interaktiver Dauerbetrieb (Serial -> docker logs/attach)
|
||||
├── test-web.sh Automatischer Webinterface-Test (QEMU im Hintergrund + wget)
|
||||
├── start-vm.sh Bequemer Start (Container "bintec-vm", Ports published)
|
||||
├── PLAN.md Urspruenglicher Plan
|
||||
├── README.md Diese Datei
|
||||
├── logs/ Alle Logfiles (carve, inventory, boot-*, webtest)
|
||||
└── work/ Arbeitsartefakte (RootFS, Kernel, ext4-Image, ~2 GB)
|
||||
├── rootfs/ entpacktes Original-RootFS (556 MB, read-only Basis)
|
||||
├── rootfs.squashfs geschnitztes SquashFS aus der Firmware
|
||||
├── kernel.img Original-QSDK-Kernel 4.4.60 (ARM64, dekomprimiert)
|
||||
├── kernel.raw Original-Kernel gzip-Blob
|
||||
├── w2044ax.dtb Device-Tree W2044ax (aus dem FIT)
|
||||
├── fit.img komplettes FIT-Image
|
||||
├── initramfs.img Mini-Initramfs (busybox + virtio/ext4-Module)
|
||||
├── rootfs-generic/ aufbereitete RootFS-Kopie (siehe Aenderungen unten)
|
||||
├── rootfs.ext4 das gebootete Root-Dateisystem (1,5 GB sparse)
|
||||
└── deb-kernel/ Debian bookworm arm64-Kernel (6.1.0-50-arm64) + busybox
|
||||
```
|
||||
|
||||
## Repo vs. lokal generierte Daten (firmware/ und work/)
|
||||
|
||||
Beide Ordner sind absichtlich **nicht** Teil des Repos (`.gitignore`):
|
||||
|
||||
- `firmware/` – die Firmware ist proprietär (Bintec/Teldat). Bitte selbst
|
||||
herunterladen (Schritt 0 oben) und das .img dort ablegen.
|
||||
- `work/` – wird **komplett generiert**, es gibt keine manuellen Dateien:
|
||||
`extract.sh` (Schritt 2) erzeugt `work/rootfs/` aus dem Firmware-Image,
|
||||
`build-generic.sh` (Schritt 3) baut daraus Kernel, Initramfs und
|
||||
`rootfs.ext4` inkl. aller Emulations-Anpassungen (Tabelle unten).
|
||||
Zum Nachbauen einfach die Schritte 1-4 der Schnellstart-Anleitung ausführen.
|
||||
|
||||
## Firmware-Layout (WiFi6_System_Software_v3.6.1.2.img)
|
||||
|
||||
Full-Flash-Dump "full.img", Plattform `kaio` (IPQ60xx), U-Boot 11.4-1-2016.01:
|
||||
|
||||
| Offset | Groesse | Inhalt |
|
||||
|------------|-----------|------------------------------------------------------------------------|
|
||||
| 0x00000000 | ~21 MB | Container-Header, u-boot.sbl.img, SBL1 (mit Qualcomm-Attestation) |
|
||||
| 0x1503930 | 3,95 MB | FIT "ARM64 OpenWrt FIT": kernel@1 (Linux **4.4.60 QSDK**, gzip, load/entry 0x41080000), fdt@w2022ax, fdt@w2044ax, configs fuer beide AP-Varianten |
|
||||
| 0x18c7854 | 117 MB | SquashFS v4 (xz) = RootFS |
|
||||
|
||||
RootFS-Inventur (Details: `logs/inventory.txt`):
|
||||
|
||||
- **Debian 10 (buster)**, arm64, glibc 2.28, init = systemd
|
||||
- OSDx-Stack: `/etc/osdx`, `/opt/vyatta` (Vyatta/VyOS-CLI-Framework), `/opt/qsdk`
|
||||
- Dienste: FRR (BGP/OSPF), SNMP, SSH, dnsmasq, radvd, PPP, python3.7
|
||||
- Web-GUI: `/usr/sbin/gui` ("Bintec Elmeg VyOS GUI", boost.asio), gestartet
|
||||
ueber `bevyosgui.service`, aktiviert durch die Factory-Config-Zeile `set service gui`
|
||||
- GUI-Inhalte: `/usr/share/gui/` (XML/XSL-Ajax-GUI mit Telekom/Bintec-Branding)
|
||||
- Login: Factory-User `admin` (Config-Zeile in `/opt/vyatta/etc/config-default.boot.default`)
|
||||
- Kernelpakete: `/lib/modules/4.4.60` (QSDK)
|
||||
|
||||
## Pipeline im Detail (Technische Hintergruende)
|
||||
|
||||
### Phase 2 – Firmware extrahieren (`extract.sh` + `carve.py`)
|
||||
|
||||
- `carve.py` liest die .img, findet per Superblock das SquashFS (Offset
|
||||
0x18c7854 bei v3.6.1.x) und schneidet es exakt aus (`bytes_used`).
|
||||
- Das FIT (bei 0x1503930, endet exakt am SquashFS-Start) wird als FDT geparst;
|
||||
herauskommen `kernel.raw` (gzip), `kernel.img` (ARM64-Image, Magic "ARMd"
|
||||
bei Offset 0x38), `<modell>.dtb`.
|
||||
- Danach `unsquashfs` -> `work/rootfs/`, Inventur -> `logs/inventory.txt`.
|
||||
|
||||
Hinweis: das FIT-Struct-Block endet ohne FDT_END-Token (Generator-Besonderheit);
|
||||
`carve.py` behandelt das toleriert.
|
||||
|
||||
### Phase 4a – Kernel beschaffen + RootFS aufbereiten (`build-generic.sh`)
|
||||
|
||||
- Lautet den Debian-Package-Index (bookworm, arm64) und laedt
|
||||
`linux-image-6.1.0-50-arm64` (aus linux-signed-arm64) + `busybox-static` (arm64).
|
||||
- Config-Check: Debian-Kernel hat PL011-Konsole =y, aber EXT4/VIRTIO_BLK/VIRTIO_PCI
|
||||
=m -> deshalb **Mini-Initramfs** (busybox + `modprobe virtio_pci virtio_blk
|
||||
virtio_net crc32c ext4` + switch_root).
|
||||
- Die Kernel-.deb enthaelt keine modules.dep -> `depmod -b` wird ausgefuehrt.
|
||||
- RootFS wird kopiert (`rootfs-generic`), Kernel-Module fuer 6.1 hinzugefuegt
|
||||
und mit `mkfs.ext4 -d` nach `rootfs.ext4` (1,5 GB sparse) verpackt.
|
||||
|
||||
### Phase 4b – Booten (`boot-generic.sh` / `boot-interactive.sh` / `start-vm.sh`)
|
||||
|
||||
```
|
||||
qemu-system-aarch64 -M virt -cpu cortex-a53 -m 1024 -smp 1 \
|
||||
-kernel vmlinuz-6.1.0-50-arm64 -initrd initramfs.img \
|
||||
-drive if=virtio,format=raw,file=rootfs.ext4 \
|
||||
-netdev user,id=n0,hostfwd=...:2222-:22,hostfwd=...:8080-:80,hostfwd=...:8443-:443 \
|
||||
-device virtio-net-pci,netdev=n0 -no-reboot -nographic \
|
||||
-append "root=/dev/vda rw console=ttyAMA0,115200"
|
||||
```
|
||||
|
||||
## Aenderungen im RootFS (nur in der Kopie rootfs-generic/rootfs.ext4)
|
||||
|
||||
Grund: OSDx erwartet die echte AP-Hardware (QCA-WLAN, Flash-Environment "0x0",
|
||||
LEDs, Watchdog durch cfgd). Ohne Hardware haelt sich das System nicht oben –
|
||||
folgende Stellschrauben machen den Boot stabil und die Shell erreichbar:
|
||||
|
||||
| Eingriff (per systemd-Drop-In / Symlink) | Wirkung |
|
||||
|------------------------------------------|---------|
|
||||
| `serial-getty@ttyAMA0.service.d/autologin.conf` | Direkte Root-Shell auf ttyAMA0 (OSDx-PAM lehnt Autologin/leeres PW ab) |
|
||||
| `vyos-cfgd.service.d/emulation.conf`: `WatchdogSec=0`, `TimeoutSec=0`, `StartLimitBurst=0` | Ohne echte HW sendet cfgd keine Watchdog-Pings -> sonst `StartLimitAction=reboot-force` bei ~200 s |
|
||||
| `qca-osdx-mon.service` -> /dev/null (maskiert) | QCA-WLAN-Monitor braucht echte Hardware |
|
||||
| `osdx-modulelauncher.service.d/…`: `TimeoutSec=0` | Timeout entschaeft |
|
||||
| `systemd-reboot.service` -> /dev/null (maskiert) | `osdx-load-config` ruft bei Config-Fehlern `reboot` auf -> wird jetzt nur mit Fehler abgewiesen |
|
||||
| `qemu-net.service` | Netz-Bringup: 10.0.2.15/24 auf eth0/br0, Default-Route ueber 10.0.2.2 (QEMU-Usernet) |
|
||||
| `multi-user.target.wants/bevyosgui.service` | Web-GUI aktiviert |
|
||||
| `/etc/shadow`: root-Passwort geleert | Fallback-Anmeldung |
|
||||
| Debian-Module 6.1.0-50-arm64 nach `/lib/modules/` | RootFS brachte nur QSDK-4.4.60-Module mit |
|
||||
|
||||
## Logs
|
||||
|
||||
| Datei | Inhalt |
|
||||
|-------|--------|
|
||||
| `logs/carve.log` | Extraktion (Offsets, FIT-Metadaten) |
|
||||
| `logs/inventory.txt` | RootFS-Inventur |
|
||||
| `logs/kernel-config.txt` | Debian-Kernel-Config-Auszug |
|
||||
| `logs/unsquashfs.log` | Entpacken |
|
||||
| `logs/boot-native.log` | Versuch 1: Original-QSDK-Kernel – 60 s komplett stumm (Crash vor jeder Konsolenausgabe, erwartbar: kein IPQ60xx-Modell) |
|
||||
| `logs/boot-generic.log` | Versuch 2: vollstaendiger Boot bis `root@osdx:/#` |
|
||||
| `logs/boot-interactive.log` | Interaktivitaets-Nachweis (uname, ip link, /opt/vyatta, sauberes poweroff) |
|
||||
| `logs/boot-webtest.log` | Webinterface-Test |
|
||||
|
||||
## Bekannte Einschraenkungen
|
||||
|
||||
- QEMU `virt` emuliert kein IPQ60xx: kein WiFi, keine Switch-/LED-/NAND-HW.
|
||||
Im Log normal: "Cannot open 0x0", "environment not initialized",
|
||||
LED-/qca-**-Service-Fehler.
|
||||
- OSDx-Config-Loader laeuft mit "factory configuration for default" ab
|
||||
(echte Configs liegen im Flash-Environment, das emuliert nicht existiert).
|
||||
- Versuch 1 (Original-QSDK-Kernel) bootet nicht – dokumentiert in
|
||||
`logs/boot-native.log`.
|
||||
- TCG-Emulation: Boot ~2-3 min, laeuft danach stabil.
|
||||
- GUI-Session-Login laeuft ueber vyos-cfgd; falls der Browser-Login mit
|
||||
admin/Factory-Passwort scheitert, Serial-Shell nutzen (docker attach).
|
||||
Reference in New Issue
Block a user