# 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/.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/.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/`. ## 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), `.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).