Bintec W20XXax-Firmware in QEMU (Docker): Extraktion, generischer Kernel-Boot, Doku

This commit is contained in:
2026-09-16 21:47:42 +02:00
commit 6ac7e6b305
20 changed files with 2779 additions and 0 deletions
+270
View File
@@ -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).