Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Lenovo ThinkPad Stack Router

refuge.coop
GPC.solutions SAS
EMA Tech.

Rétro-ingénierie du module routeur du ThinkPad Stack — extraction firmware, accès root, source GPL publiée, portage envisagé.

Lenovo ThinkPad Stack Router + Disk (Third)

Ressources :

Démontage

Ouverture du module routeur (18/02/2024).

Album photos

PosteMarquageRôle
SoC / radioRealtek RTL8881ANMIPS + radio 5 GHz
Radio 2,4 GHzRealtek RTL8192ER2ᵉ radio discrète 802.11n 2,4 GHz
ECITE IT8586Econtrôleur empilement / LEDs / charge
FlashWinbond 25Q128BVSGNOR 128 Mbit = 16 Mio
RAMMicron MT4D161664ADDR SDRAM 128 Mio
RégulationSilergy SY8008régulateur DC-DC
EthernetBothhand NS0014 LFmodule d’isolation 10/100 BASE TX

Ce que les marquages corrigent ou confirment :

Connecteur pogo (repérage au multimètre) : USB + 3V3 + 5V + GND.

Identification

Nom de projet interne : Poker Preuve : path de build …/poker-1.1.3.4/Poker/rtl819x/linux-2.6.30

ChampValeur
RôleModule routeur du ThinkPad Stack (gateway réseau + partage HDD)
Hostnamelenovo-stack
Firmware1.1.3.4 (build 2016-07-07 recovery / 2016-03-24 OTA)
EC0.1.0.2
Gateway IP192.168.33.254
Serveur HTTPBoa v0.94, User: root

Connectique pogo — empilement des modules

Les autres modules du Stack (powerbank, disque dur, enceinte, projecteur) se raccordent par connecteur pogo. Le routeur n’a pas de batterie propre, pourtant son EC (/proc/ite8566_PowerSource, ite8566_RelativeStateOfChg) rapporte un état de charge — il ne peut donc lire que la batterie d’un module empilé. L’ITE8566 du routeur est ainsi probablement le négociateur d’empilement (alim + charge), pas un simple contrôleur de LEDs.

PlanRôleStatut
Alimentationrails 3,3 V + 5 Vmesurés
DonnéesUSB (Disque dur)observé
Énergieétat de charge du powerbank lu par l’EC du routeurinférence forte (le routeur rapporte une charge qu’il ne possède pas)
Bus de négociationI2C/SMBus dédié vs ligne simple (présence + tension)à lever au MSO

Pas de bus vidéo. Le projecteur est un PC x86 autonome (Intel Z8550, son propre GPU) qui génère sa propre image ; le pilotage inter-modules passe par le réseau (Wi-Fi, JSON), pas par les broches. Les pogo portent l’énergie et l’USB, à priori rien d’autre.

Plateforme

ComposantRéférenceNotes
SoCRealtek RTL8881A v1.0 (chipVersion: RTL8881AN)MIPS, dual-band 2,4 + 5 GHz (WiFi intégré), intPA, 2T2R
FlashNOR 16 Mio (erasesize 0x1000)pas de NAND — pas de bad blocks, pas d’ECC
RAM128 MioMemTotal 117 080 kio (~114 Mio dispo) — large marge pour le port
SDKRealtek SDK v3.4.6.4-r23198rtl819x, fastpath
Kernel2.6.30.9build software_release_1.1.3.4, gcc 4.4.5, image raw (pas uImage)
C libraryuClibc 0.9.30.3
EC (LED/Projecteur)ITE ITE8566via /proc/ITE8566_io, /proc/ite8566_*

Le RTL8881A est un SoC MIPS à WiFi intégré — pas un RTL8196/8197 générique. C’est ce qui exclut OpenWrt. Le NIC Ethernet interne est rapporté comme 8196C par le driver (Probing RTL8186… chip name: 8196C) — même IP interne, pas une puce séparée.

Logiciel

Voir Lenovo ThinkPad Stack Assistant.

Flash — layout réel (dump MTD)

Trois partitions MTD sur la NOR 16 Mio :

MTDNomTailleContenu
mtd0boot+cfg+linux2,3 Mioheader Realtek + LZMA kernel+initrd
mtd1root fs10,2 MioSquashFS v4.0 LZMA (837 inodes, ~4,5 Mio réels)
mtd2jffs24,0 Miooverlay rw (/var monté) — config persistante
mtd0: 00240000 00001000 "boot+cfg+linux"
mtd1: 009c0000 00001000 "root fs"
mtd2: 00400000 00001000 "jffs2"

/proc/cmdline : console=ttyS0,115200 root=/dev/mtdblock1 cgroup_enable=memory → le rootfs est mtd1, monté directement en SquashFS (pas de pivot initrd).

Dans mtd0, deux LZMA : un petit blob à 0x1B00 (= boot monitor Realtek, gère recovery/OTA bas niveau — chaînes Jump to image start=0x%x, layout flash) et le gros à 0x42818 (= kernel + initrd, 4,3 Mio décompressés). Les offsets diffèrent du conteneur OTA (0x2818) : ici on est sur la partition crue, pas le wrapper fw.bin.

Extraction

# rootfs (mtd1) — SquashFS LZMA standard, unsquashfs natif suffit
binwalk --extract mtd1.bin          # ou unsquashfs -d rootfs mtd1.bin

# kernel (mtd0) — LZMA à 0x42818
binwalk --extract mtd0.bin          # decompressed.bin @ 42818 = kernel raw

# jffs2 (mtd2) — NE PAS parser offline (voir plus bas)

jffs2 : le dump offline est piégeux (jefferson force le mauvais endianness, je_tools non packagé sur Arch). Le magic 19 85 20 03 est little-endian. La méthode fiable : /var est déjà monté rw sur le box → on sert les fichiers via Boa, pas de parsing.

Firmware — conteneurs OTA / recovery

Conteneur sw_fw.bin (Source: APK Levono Stack Assistant) et fw.bin (Source: OTA réseau), structure identique :

OffsetContenu
0x0000–0x2817Header Realtek (10 264 octets) — conteneur, pas une signature
0x2818LZMA → kernel 2.6.30.9 + initrd
0x14E022SquashFS v4.0 LZMA → rootfs

OTA ≠ recovery (même version)

SourceBuildRôle
OTA réseau (fw.bin)2016-03-24poussé par download.lenovo.com
Recovery APK (sw_fw.bin)2016-07-07embarqué dans le Stack Assistant v2.0.6

Le recovery est 3,5 mois plus récent que l’OTA sous le même numéro 1.1.3.4. Le router.cgi des deux builds a des chaînes identiques (aucun patch de sécurité entre les deux) → le header est un conteneur, pas une signature. Un repack custom passe par le POST OTA.

Accès root

Trivial, confirmé.

Mécanique d’authentification (contre-intuitive)

# Telnet direct (root:root)
telnet 192.168.33.254

Configuration persistante — format COMPCS

/var/config.dat (~9,7 Ko) = blob sérialisé Realtek, magic COMPCS, pas compressé (le lzma échoue — c’est un format à table d’offsets little-endian, lisible en strings). Les champs texte (Stackable Router, Lenovo, admin, SSID, PSK) sont en clair, entrecoupés de pointeurs d’offset croissants.

strings -a -t x config.dat | grep -iE 'stack|lenovo|admin|ssid|psk|passw'

Le parseur exact est dans le source GPL du router.cgi (fonction de lecture de config.dat) → format documentable sans rétro-ingénierie aveugle. Pour le port : soit reproduire le parseur, soit remplacer par un store propre.

Le reste de la config live vit dans /var (jffs2 monté) : wpa-wlan0.conf / wpa-wlan1.conf (PSK en clair, usePassphrase=0), passwd, wsc-*.conf.

WiFi — bandes & procfs

Attribution des bandes

InterfaceBande
wlan05 GHz
wlan12,4 GHz

Le procfs du driver built-in est /proc/wlan0/ (répertoire de MIBs), pas /proc/rtl8192cd/. La variante RF se lit dans mib_rf :

cat /proc/wlan0/mib_rf       # → chipVersion: RTL8881AN, MIMO_TR_mode: 2T2R
cat /proc/wlan0/mib_rf_ac    # tables 5 GHz (VHT)
cat /proc/rf_switch          # commutation PA/LNA double bande

Protocole — dispatcher router.cgi

Surface applicative unique, sans authentification :

GET/POST /cgi-bin/router.cgi?method=<action>   (~80 actions)

Le CGI lit QUERY_STRING → dispatche sur method=. Les commandes shell sont construites via sprintf/strcpy sur des %s issus de la requête, puis passées à system/popen/execl. Chaque %s non filtré = command injection root.

methodCommande construiteVecteur
find_filesfind "/var/tmp/usb/%s" -iname "*%s*"path / keyword
get_fileshare_linkln -s %s %sfilepath
set_time_funcdate -s "%s"valeur temps
disk_unlockhdparm --security-unlock %s /dev/%smot de passe disque
set_wps_funciwpriv wlan0 set_mib pin=%sPIN WPS

Pont projecteur — ITE8566

Le DLP est pilotable via le routeur, sans passer par l’Android :

Interface procfsMéthode CGIRôle
/proc/ite8566_PicoProjectorprojpico projecteur DLP
/proc/ite8566_PowerSourceget_ele_statusbatterie / secteur
/proc/ITE8566_io—LED / IO (LedSet0 1, LedSet1 3)

Le routeur commande le projecteur via l’ITE Tech ITE8566. Le DLP n’est pas un front matériel isolé — c’est un fichier procfs du routeur.

Cloud à neutraliser pour le portage

ComposantEndpointRôle
reachitreachit.lenovo.comtunnel de contrôle à distance
sslclientuss.lenovomm.com/authen/1.2/...auth compte Lenovo (lenovoid)
cwmp (TR-069)—gestion à distance opérateur
OTAdownload.lenovo.com/lenovostack/mise à jour firmware

Auth à deux modes : admin local OU lenovoid cloud (change_router_login_type).

Sur l’unité analysée, le cloud n’a jamais été appairé : /var/private, /var/myca, /var/cwmp_config, /var/cwmp_default sont vides. L’étape « neutraliser le cloud » se réduit donc à ne pas embarquer les binaires (reachit, reachsync, sslclient, dlserver, cwmp) au recompile — rien à désauthentifier.

GPL — source complète publiée

Lenovo a livré la source correspondante intégrale.
(Conformité quasi complète, ce qui est asssez rare pour le noter !)

Source : https://support.lenovo.com/us/en/downloads/DS105234

ComposantStatut
Kernel 2.6.30.9 + patches fastpath Realteksource ✅
Driver WiFi RTL8881A (rtl8192cd/WlanHAL/RTL8881A/*.c)source ✅ — flag CONFIG_WLAN_HAL_8881A
Userspace (boa, busybox, dropbear, hostapd, iptables, openssl…)source ✅
squashfs-tools (packer du rootfs)source ✅
Toolchain MIPS (rsdk-1.5.5 : gcc 4.x + uClibc 0.9.30.3 + binutils)source ✅
Microcode radio (WlanHAL/Data/8881A/*.bin)blob ❌ — normal, inévitable

Le driver de la puce exacte est en .c compilable — 9 fichiers de HAL dédiés RTL8881A (Hal8881AGen.c, Hal8881APhyCfg.c, Hal8881ATxDesc.c…). Le seul binaire restant est le microcode radio, comme tout driver WiFi (iwlwifi, ath9k, rtw89).

Verdict portage

Cible du portage (dépropriétarisation) :

  1. Recompiler kernel + userspace + driver WiFi (reproductibilité byte-level), table 8881AN.

  2. Supprimer le cloud Lenovo : reachit, sslclient, TR-069 (cwmp), OTA (ne pas les embarquer).

  3. Remplacer :

    • telnet → dropbear

    • Boa → uhttpd

  4. Garder uniquement le microcode radio (incontournable).

Port du driver rtl8192cd vers OpenWrt moderne : non viable — le driver est couplé au framework propriétaire net80211 (iwpriv), incompatible avec mac80211/nl80211 d’OpenWrt. La source est nécessaire mais pas suffisante : ce serait une réécriture mac80211 + un target SoC RTL8881A à créer, pas un portage. La source du WiFi sert au rebuild depuis le SDK (audit, patch, recompile), pas à un portage OpenWrt.

Ordre d’attaque

  1. Dump flash (RCE find_files ou telnet root:root) → mapper /proc/mtd, backup, lire la variante radio (/proc/wlan0/mib_rf).

  2. Build toolchain rsdk-1.5.5 → recompiler le code source GPL et valider la reproductibilité.

  3. Repack avec squashfs-tools recompilé, en retirant le cloud.


Le routeur est entièrement ouvert : root trivial, source GPL complète, driver WiFi en .c. Le seul front restant est le microcode radio — structurel, comme partout. Tout le reste est identifié, documenté et de difficulté bornée.