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 Assistant

refuge.coop
GPC.solutions SAS
EMA Tech.

Analyse et rétro-ingénierie du protocole vers le routeur et le projecteur en vue d’un éventuel portage Linux.

Lenovo ThinkPad Stack Assistant splash screen

Splash screen de la version Microsoft Windows.

Logiciel

Configuration et mise à jour du routeur et/ou du projecteur.

OS pris en charge

Dernière(s) version(s)

2.0.6 (Mai/Juin 2017).

Protocole

(Par désassemblage apk Android v2.0.6)

Découverte : mDNS/DNS-SD, browse _http._tcp., filtre serviceName ∋ “StackProjector”, resolveService → host:port dynamique. IP projecteur statique connue : 192.168.33.100.

JSON

Clés du ReceivingThread (v2.0.6)

Affichage / Projection

CléType (déduit)Rôle
keystone_statusStringÉtat correction trapèze
keystone_levelStringNiveau correction trapèze
brightStringLuminosité (ancienne ?)
brightnessStringLuminosité (nouvelle ?)
input-sourceStringSource active (hdmi/usb/miracast…)
hdmiStringÉtat/port HDMI
hdmi_audioStringAudio sur HDMI

Audio

CléTypeRôle
speakerStringVolume/haut-parleur interne
volumnString⚠️ typo Lenovo — volume (à reproduire à l’identique)

Batterie / Powerbank

CléTypeRôle
powerbankStringMode batterie externe
battery-level / battery_levelStringNiveau (%) — deux variantes de séparateur
battery-status / battery_statusStringÉtat (charging/discharging/full)
lowpowerStringAlerte batterie faible

Stockage / HDD

CléTypeRôle
hddStringPrésence HDD interne
hddnameStringNom/volume
hddnumStringNombre partitions/disks
hddtotalcapacityStringCapacité totale
hddtypeStringType (SSD/HDD/…)
usbhddStringPrésence USB
usbhddpathStringMountpoint
usbhddsizeStringTaille

Cast / Média

CléTypeRôle
isMiracastStringMiracast actif
isPlayerStringLecteur multimédia actif
multiCastStringMode multi-écran
pathStringChemin fichier en cours

Réseau / Connectivité

CléTypeRôle
bluetoothStringÉtat BT
isAPStateStringMode point d’accès
routerStringÉtat/rôle routeur

Système / Device

CléTypeRôle
agent_versionStringVersion agent embarqué
versionStringVersion firmware
nameStringNom de l’appareil
languageStringLangue UI
locale / localeidStringLocale
countryStringPays
accessbilityString⚠️ typo — accessibilité
allowNonMarketStringSideloading autorisé

Horloge

CléTypeRôle
year month day hour minuteStringDate/heure (probablement en réponse à un check)

Protocole / Contrôle

CléTypeRôle
checkStringRequête de synchronisation d’état
resultStringRéponse générique
retStringCode retour
FullString⚠️ majuscule — réponse complète ?

Microsoft Windows

Client en .NET (Stack.exe)

Version 0.2.0.6, assembly WPF (MahApps.Metro). Rétro-ingénierie via décompilation native (ilspy-bin), les symboles complets étant livrés avec le binaire.

Dépendances embarquées

ComposantBinaireRôle
SSHdropbear.exe, dbclient.exe, dropbearkey.exe (+ cygwin1.dll)tunnel/maintenance vers le device
Découverte réseauInterop.Bonjour.dllmDNS/DNS-SD (cf. protocole)
BluetoothInTheHand.Net.Personal.dllappairage BT
QR CodeThoughtWorks.QRCode.dllprovisioning / partage de config
AudioCoreAudioApi.dllsortie audio locale

Fichiers chiffrés

Deux blobs opaques dans files/ sont déchiffrés par la classe stack.Helper.CSEncryptDecrypt. Le code décompilé révèle un chiffrement purement décoratif :

public static void DecryptFile(string sInputFilename, string sOutputFilename, string sKey)
{
    DESCryptoServiceProvider DES = new DESCryptoServiceProvider();
    DES.Key = Encoding.ASCII.GetBytes(sKey);   // clé = la string en dur
    DES.IV  = Encoding.ASCII.GetBytes(sKey);   // IV = même string
    // … DES-CBC, padding PKCS7 implicite …
}
DéfautConstat
AlgorithmeDES (1977), clé de 8 octets — cassable par brute-force
Clé"ThinkPad" — public const string FirmwareFileKey = "ThinkPad" (8 caractères = taille exacte d’une clé DES)
IVégal à la clé — annule tout l’intérêt du mode CBC
Rôle réelEmpêcher un strings sur le .bin ? Cocher une case dans la spec ?

Déchiffrement

from Crypto.Cipher import DES
from Crypto.Util.Padding import unpad

key = b"ThinkPad"
for f in ["gjzglx.txt", "Moser_rj_gj.bin"]:
    data = open(f, "rb").read()
    plain = unpad(DES.new(key, DES.MODE_CBC, iv=key).decrypt(data), 8)
    open(f.split("/")[-1] + ".dec", "wb").write(plain)
Blob chiffréContenu en clair
gjzglx.txtManifeste OTA (JSON bilingue CN/EN) — version/max-version 1.1.1.8 + notes de version.
Moser_rj_gj.binConteneur firmware — similaire au sw_fw.bin du module routeur : header Realtek (10 264 o) → LZMA (kernel + initrd) → SquashFS v4.0 LZMA. Voir l’article Routeur pour l’extraction complète.

Récapitulatif — sécurité décorative

Couche« Protection »
Clé SSH1111… (factice)
Symboles.pdb livré en prod
ChiffrementDES (1977)
Clé"ThinkPad" en const string
IVÉgal à la clé

Le fil conducteur est le même que côté routeur (telnet root:root, CGI sans auth, OTA signé MD5 seul) : chaque couche de sécurité existe pour décourager le curieux, aucune pour arrêter un analyseur.


Observations critiques pour le portage

  1. Typos à préserver : volumn (pas volume), accessbility (pas accessibility). Le projecteur attend ces chaînes exactes.

  2. Doublons de séparateur : battery-level / battery_level, battery-status / battery_status, bright / brightness. Deux générations de firmware coexistent dans le parser, le portage doit accepter les deux ou trancher.

  3. Tout est String (y compris ce qui devrait être Int/Boolean). Le getInt/getBoolean du grep lit probablement une autre classe (le SendingThread ou un handler séparé). Le ReceivingThread parse tout en String.

  4. Full avec majuscule : probablement un flag de réponse complète vs incrémentale. À confirmer en écoutant le flux.

  5. Pas de clé power / on / off : l’extinction passe peut-être par result ou par fermeture de socket. À vérifier.