Hogyan flasheltem tuned kalibrációt egy BMW DDE6-ra WinKFP-vel? Sehogy. 😂
Volt egy egyszerűnek tűnő ötletem: fogom a módosított .0da cal-fájlt, feltolom WinKFP-vel, és kész. Hát, nem így lett. Egy kis nyomozás, négy zsákutca, és egy meglepő végkifejlet után most már pontosan tudom, miért nem megy ez WinKFP-vel, és hogy kell megoldani saját flasherrel.
Ez a poszt a sztoriról szól. Ha valaki maga akar flash toolt írni, a végén ott a recept és a logika, amiből össze tudja rakni. Fontos pontosítás: a flash-authhoz továbbra is kell az RSA512-es auth-kulcs, illetve hozzáférési ág; itt nem ezt kerülöm meg. A poén az, hogy a másik, cal-data RSA-aláírási ág érvényes privát kulcsa nem kell, mert nem ezen az úton viszem be az ECU-t.
A felállás
A DDE626 flash 256 KB-os cal-zónája (0xC0000..0xFFFFF) tele van olyan mezőkkel, amik első ránézésre nem árulják el, melyik mit csinál. Van benne pár terminator, egy pointer-tábla, a tényleges cal-adat (mapek, paraméterek), és pár fingerprint-szerű érték a végén. A klasszikus tuner-tudás szerint a recept egyszerű: módosítod a mapeket, újraszámolod a Bosch sum-checksumot (a cal-data BE 32-bit word-jeinek összege egy fix konstansra kell kijöjjön), és kész.
Csak hát ez itt nem volt elég.
A nyomozás
Első tipp: hiányzik egy marker (AF FE 08 15) a 0xC0290 területen. Összehasonlítottam egy .0da fájlt egy ECU-ról mentett kalibrációval, és hiányzott ez a marker. Na de beleírom a fájlba, és minden okés lesz.
Az AF FE 08 15 marker a 0xC0290 területen, egy ECU-ról mentett kalibrációban.
Beleírtam. WinKFP szépen leflashelte. Az ECU programozási státusza maradt 0x06, ami a Bosch nyelvén nagyjából annyit tesz: "data-sign verify nem ment át". Tehát nem ez volt a kulcs.
WinKFP flash, a folyamat lefut, de a programozási státusz a verify miatt nem áll normálba.
Második tipp: a cal végén lévő CVN érték (egy fingerprint a 0xFD774-en) eltér a gyáritól, biztos azt ellenőrzi.
Pcap-analízis. Kiderült: a WinKFP magából a fájlból olvassa ezt az értéket, és az ECU futás közben rá se néz. Nem ez a probléma.
Harmadik tipp: van egy iflash mirror a cal-régióról, az nem konzisztens a frissen flashelt területtel.
Tisztázódott: ugyanazon az ECU-n flasheltem vissza, a mirror konzisztens volt. Megint mellé.
Negyedik tipp: van egy 64 byte-os RSA-aláírás a 0xC0100 körül.
Diff a két fájl között: ott mindkettőben csak FF. Ilyen sig nem létezik DDE626-on. A régebbi generációs Ghidra-tudás itt félrevezetett, generációs változás történt.
A felfedezés
Végül a diff a gyári kalibrációk között, azonos szoftvereken, egy konkrét helyen mutatott eltérést: egy 128 byte-os blob a 0xC0314-en. Ez minden egyes cal-on más. És itt jött a meglepetés: sikeres gyári flash után az ECU magától beírta az AFFE0815 markert a 0xC0290-re.
Ez fordította meg az egész képet:
- A
0xC0314-en lévő 128 byte az igazi cal-védelem: egy RSA-aláírás a cal-tartalom fölött. - Az
AFFE0815marker nem bemenet, hanem eredmény. Az ECU írja be, miután a verify lefutott és átment. A WinKFP ezért hagyja ki flasheléskor ezeket a címeket.
Tehát az első tippem fejjel lefelé volt: én okozatot próbáltam bemásolni ok helyett.
Mi történik az ECU-ban?
A $31 09 04 routine indítja a data-sign verify-t. Nagyvonalakban:
- Az ECU MD5 hash-t számol a cal-zóna fölött. (Mellékes, de elegáns részlet: ez standard MD5, a klasszikus RFC 1321 IV-kkel, semmi custom Bosch-varázslat a hash-ben.)
- Beolvassa a 128 byte-os aláírást a
0xC0314-ről, és elvégzi az RSA exponenciálást a beégetett publikus kulccsal. - Az eredmény egy fix formátumú plaintext: egy magic header, utána a hash (Bosch-féle endianness-konvencióval, 4-byte chunkonként megfordítva), majd zero-padding.
- Ha a hash egyezik, siker: az ECU maga írja be az
AFFE0815-öt, és a státusz0x01lesz (Normalbetrieb).
Itt a lényeg, hogy a privát kulcs az ECU-ban nincs benne (helyesen, csak a publikus van), tehát új, érvényes aláírást előállítani a tuned cal-hoz a klasszikus úton nem lehet. Ezért bukik 0x06 státuszban minden tuned cal, amit a fő verify-ágon küldenek be.
A 0x06 programozási státusz: a data-sign verify nem ment át a fő ágon.
Mit jelentenek pontosan a programozási státuszok?
A BMW NCS Dummyban van egy tábla a 09DDE604.prg fájlhoz, ami megadja az összes lehetséges állapotot. Pár fontos kódolás:
0x00Anlieferzustand, friss ECU, sosem flashelt.0x01Normalbetrieb, minden OK, normál működés (★ ezt akarjuk).0x03Speicher gelöscht, flash törölve, várja az új tartalmat.0x05Signaturprüfung PAF nicht durchgeführt, kód-aláírás verify nem ment át.0x06Signaturprüfung DAF nicht durchgeführt, cal-aláírás verify nem ment át.0x07Programmprogrammiersitzung aktiv, kód-flash folyamatban.0x08Datenprogrammiersitzung aktiv, cal-flash folyamatban.0x09,0x0EReference-mismatch hibák (hardware-program, program-data kereszthivatkozási inkonzisztenciák).0x0CProgramm nicht vorhanden, kód-flash hiányos vagy nincs.0x0FDaten nicht vorhanden, cal-flash hiányos vagy nincs.0x10,0x80Reserviert für BMW vagy Zulieferer.
A teljes PROGRAMMIERSTATUS tábla a Tool32-ből, kiolvasva a 09DDE604.prg fájlból. Itt látszik a Bosch tervezett állapotgépe a két különálló sign-verify ággal (PAF, DAF) és a köztes hibakezelési állapotokkal.
A két kulcs amivel itt találkozni, a 0x05 PAF és a 0x06 DAF. Ezek a két különálló sign-verify ág kimenete. Ha csak kalibrációs adatot módosítasz, akkor 0x06-on bukik az ECU. Ha kód is módosul, akkor lehet 0x05 is, vagy 0x06, attól függően melyik ág bukik először a verify sequence-ben.
A 0x06 szó szerint azt jelenti, hogy a Datensignaturprüfung nem futott le (nicht durchgeführt), nem azt hogy lefutott és elbukott. Ez a finomság a Bosch szövegezésben pontos: az ECU nem tud bizonyítani sikeres verify-t, ezért a marker nem íródik be, ezért a fallback ág is bukik (ha a marker eleve hiányzik), ezért az állapotgép ezen ragad.
A kiskapu: egy szándékos fallback
A nyomozás közben viszont előkerült egy második kódág. Amikor a programozási státuszt kérdezed le, az ECU nem mindig fut bele a teljes RSA-verify-be. Van egy gyorsabb, fallback útvonal:
- Megnéz egy sentinel-értéket és egy SRAM-markert (gyakorlatilag: "lefutott már korábban a verify?").
- Ha azok nem stimmelnek, akkor egy egyszerű memcmp-et csinál: összehasonlítja a
0xC0290területet egy belső referencia-template-tel. - A referencia-template az iflash-ben pontosan
AF FE 08 15+ 60×0x00. Vagyis: ha a marker bármilyen okból ott van a0xC0290-en, a memcmp átengedi.
Ez nem bug. Ez egy szándékos service-mode útvonal szerintem, dealer vagy factory-reset szcenárióra, ahol még nincs friss aláírás, de a marker megléte elég bizonyíték. A Bosch threat-modellje itt vállalhatóan pragmatikus: "a casual tunert kizárjuk a fő verify-jal, a master-szintű RE-támadót meg úgyis nehéz." Tudatos kompromisszum, nem hiba.
Miért nem megy ez WinKFP-vel?
Itt a csavar a WinKFP-ben van. A pcap szerint a gyári flash-folyamat a $34/$36/$37 write-keretekkel soha nem ír a 0xC0290 területre. Akkor sem, ha beleírod a .0da fájlba. Az erase ugyan letörli ezt a területet flash közben, de visszaírni csak az ECU írja vissza, sikeres $31 09 04 után.
Vagyis WinKFP-vel tuned cal esetén:
- A fő RSA-verify lefut, megbukik (nincs érvényes aláírás),
0x06státuszba rakja az ECU-t. - A marker nem kerül be (mert az ECU nem írta, és a WinKFP architektúrálisan nem írja).
Ez tiszta separation-of-concerns a Bosch részéről: a tool szállítja a payloadot, az ECU dönt a verify-ról és a marker beírásáról. Logikus, csak épp nem hagy kiskaput a tunernek.
Hogyan oldja meg a saját flasherem?
A teljes flash-területet maga kontrollálja, beleértve a 0xC0290-et. Tehát:
- Megírja közvetlenül a
0xC0290-re azAFFE0815markert (része a flashelt cal-zónának). - A
$31 09 04finalize ugyan kimegy, de tuned cal-on az ECUr[2]=0x00-t ad vissza: nem fut fresh verify, mert nincs érvényes aláírás, amit ellenőrizni lehetne. - Reset után az ECU a fallback memcmp-ágon megy be, ott a marker megvan, átengedi, és
0x01státuszba rakja az ECU-t, normál működésbe.
Nincs szükség a cal-data RSA privát kulcsára, és firmware-patchre sem. Az RSA512-es flash-auth ettől még megmarad belépési feltételnek a programozáshoz; a trükk csak az, hogy a másik, fő data-sign verify ág tuned cal-on nem fut le érdemben, és helyette a szándékos service-fallback dönt.
A két flow egymás mellett: gyári vs tuned
A legtisztább bizonyíték a két flash-log $31 09 04 finalize lépése. Ugyanaz a tool, ugyanaz az ECU, csak a cal tartalma más. Először a gyári, érvényes aláírású cal (egy visszaolvasott WinKFP-frissítés):
[4b] CVN Finalize ($31 $09 $04)... $31 $09 $04 OK (5.0s, 2 retries): 71 09 01 r[2]=0x01, FRESH CVN VERIFY PERFORMED (correct path) $31 $09 $04 #2 OK: 71 09 01 #2 r[2]=0x01 (fresh verify confirmed)
Itt az r[2]=0x01 azt jelenti, hogy a fő RSA-verify ténylegesen lefutott és átment. Ezután az ECU maga írja be az AFFE markert, és a státusz 0x01. Ez a "rendes" út, pont mint WinKFP-nél.
A DDE Flasher gyári cal flashelése közben: a CVN finalize 71 09 01, r[2]=0x01, fresh verify lefutott (correct path).
Most ugyanaz a finalize lépés, de tuned cal-lal (a 0xC0290-en már bent van a kézzel beírt marker):
[4b] CVN Finalize ($31 $09 $04)... $31 $09 $04 OK (5.0s, 2 retries): 71 09 00 r[2]=0x00, ECU already in app mode, NO fresh verify ran Boot-time sig check may reject the flash. $31 $09 $04 #2 OK: 71 09 00
A tool itt figyelmeztet, hogy nem futott fresh verify, és hogy a boot-time sig check elvileg elutasíthatná a flasht. De a reset utáni identify-ban:
Ugyanaz a tool tuned cal-lal: a CVN finalize 71 09 00, r[2]=0x00, a piros keret a "NO fresh verify ran" figyelmeztetést emeli ki. A flash mégis sikeres, és az ECU 0x01-be áll a fallback-ág miatt.
[IDENTIFY] $31 0A OK, Status: 0x01 Normal operation
A státusz 0x01, normál működés, annak ellenére, hogy a fő verify nem futott le (r[2]=0x00). Ez a fallback memcmp-ág a gyakorlatban: az ECU nem talált friss aláírást, viszont a 0xC0290-en megvan az AFFE marker, és ennyi elég neki. Pontosan ezt az ágat írtuk le fentebb, csak most élő logon is látszik.
r[2]=0x01 (fresh verify lefut, átmegy), tuned cal-on r[2]=0x00 (fresh verify nem fut), de a végeredmény mindkét esetben 0x01 Normal operation, mert a marker megléte a fallback-ágon elég.A kód-oldali sztori ugyanaz
A sztori nem áll meg a .0da-nál. Az ext-flash-ben négy AFFE0815 marker-blokk van, két RSA-1024 aláírással, a másik két blokk pedig benne van a 0pa fájlban is, így az nem releváns.
- A
0xC0290területen a cal-zóna marker, a0xC0314területen a 128 byte cal-RSA-aláírás. Erről szólt eddig a poszt. - A
0x010290területen a kód-zóna marker, a0x010314területen a 128 byte kód-RSA-aláírás.
Ugyanaz az architektúra: 64 byte marker (AF FE 08 15 plusz 60 byte zero), majd a tábla folytatása, majd a típuscímke (00 00 00 20), majd a 128 byte RSA-1024 aláírás. Két különálló védelmi ág, ugyanazzal a Bosch receptúrával.
A WinKFP a .0pa (Programm-Anwendung) fájlt is ugyanúgy kezeli mint a .0da-t. A $34/$36/$37 write-keretek soha nem írnak a 0x010290 területre sem. Ha módosítod a kódot, a Programm-sign-verify ugyanúgy elbukik mint a Daten-sign-verify, és az ECU 0x06 (vagy 0x05) státuszba ragad. Ugyanaz a fallback ág van itt is, csak más címen.
Ha csak a cal-t módosítod (kód érintetlen), a kód-AFFE-t a gyári dump-ból úgyis tudod átemelni, egy bytewise copy a működő ECU 0x010290 területéről. Az ECU magától nem írja vissza ezt a területet flash közben (a separation-of-concerns ott is érvényes), de a fájlban benne kell legyen.
Variációk a témára, a 6HP TCU
Mellékág, ami közben felbukkant: a ZF 6HP TCU .0da fájlokban is megtaláltam ugyanezt a mintázatot. Egy RSA-1024-es aláírási blokk után egy second-tier marker-mező, a pozíció a 0x50084 környéke, közvetlenül az aláírás után.
Egy fontos különbség viszont: a 6HP-nél a Bosch fordított konvenciót használ. A .0da fájl üres területeit nem FF-fel tölti, hanem 0x00-val, beleértve a marker-mezőt is. Tehát ami DDE6-on FF FF plusz az AFFE0815 marker, az 6HP-n 00 00 plusz FF FF marker. Egy 0x01-státuszú TCU-ból kiolvasva a 0x50084 területen FF FF van, a .0da csere fájlban 00 00. Ugyanaz a koncepció (csere-fájlban inert állapot, ECU írja a fallback markert sikeres verify után), csak az erased state és a marker pattern fordított.
Egy 6HP TCU flash-fájl hexdumpja, a 0x50004 környéki 128 byte RSA-1024 aláírás után közvetlenül a marker-mező. A jelölt területeken jól látszik a fordított Bosch konvenció.
Flashelést itt még nem futtattam, csak a fájlstruktúra-azonosítás van meg. De a logika strukturálisan azonos. Ha a tool a flash közben biztosítja, hogy a 0x50084 területen FF FF legyen, a fallback ág valószínűleg ugyanúgy átengedi a verify-t, mint a DDE6 esetében.
Architektúrálisan egyértelmű: ugyanaz a Bosch design-nyelv, csak más byte-mintával és fordított erased-state konvencióval.
A recept (ha magad akarsz toolt írni)
Egy gyári, működő cal-dumpból kiindulva így állítható elő egy 0x01-státuszú, tuned flash:
- Forrás: egy működő,
0x01állapotú ECU-ról kiolvasott teljes cal-dump. - Módosítod a cal-adatot (mapek, paraméterek).
- Bosch sum-checksum recalc a cal-data tartományra.
- A
0xC0290területre beírod:AF FE 08 15plusz 60 byte0x00. Ha kód is módosul, a0x010290területre is ugyanezt. - Flash a single-segment write logikával, mindkét régióra ha kell.
$31 09 04finalize kimehet, tuned cal-onr[2]=0x00-t kapsz (nem fut fresh verify), ez normális, nem hiba.- ECU reset, boot, fallback memcmp, státusz
0x01.
A cal-aláírás privát kulcsa sehol nem kell. Az RSA512-es auth-kulcs, illetve hozzáférés viszont kell a flashelési session megnyitásához, csak a második RSA, vagyis a cal-data signature privát kulcsa nem szükséges. A "védelem" itt a tuned oldalon nem új aláírás gyártásával dől el, hanem azon, hogy tudni kell, melyik ellenőrzési ágon megy be az ECU.
A "ne fuss bele, mint én" rész
Pár buktató, amibe én belefutottam, hogy te ne kelljen:
- Ne tévesszen meg, hogy a WinKFP
.0dafájlban a0xC0290üres. Ez normális, az ECU írja boot közben, nem a tool. - A cal-végi CVN-ek nem védelmiek, az ECU futás közben nem ellenőrzi őket. Ne pazarold rájuk az idődet.
- A "byte-pontos egyezés a működő ECU-éval" nem garancia WinKFP-n, mert WinKFP akkor sem ír a
0xC0290-re, ha benne van a fájlban. - A
0x06státusz közvetlenül flash után nem feltétlenül kudarc, lehet, hogy a$31 09 04egyszerűen még nem futott le. Adj neki időt (10+ mp), és csak utána ítélj. - A
0x06státusz ("DAF Signaturprüfung nicht durchgeführt") szó szerint azt jelenti, hogy a verify NEM FUTOTT LE, nem azt, hogy elbukott. Ha senki nem indítja a routine-t, az ECU ezen az állapoton ragad. - A finalize
r[2]=0x00("NO fresh verify ran") és a "boot-time sig check may reject" figyelmeztetés tuned cal-on normális. Ne ijedj meg tőle: a fallback memcmp-ág a marker alapján így is0x01-be viszi az ECU-t. A figyelmeztetés a gyári-flow szemszögéből szól, nem a tuned-éból. - A legfontosabb: ez a recept csak akkor működik, ha a forrás dump egy
0x01állapotú ECU-ról jött, és a0xC0290-en megvan az AFFE marker. Egy "factory fresh" ECU-ról (ami még sosem volt OK-állapotban), ahol a0xC0290üres, ez az út is megbukik. Nem mindegy, honnan jön a forrás.
Tanulság
Nem kellett firmware-t patchelni. Az egész azon múlt, hogy megértsem: az ECU-ban két különböző ellenőrző-ág van, és a WinKFP meg a saját toolom különböző ágakon viszi be az ECU-t. Az AFFE0815 markerről pedig kiderült, hogy nem a belépőjegy, hanem a pecsét, amit az ECU üt rá, ha bejutottál, és van egy oldalsó ajtó, ahol a régi pecsét felmutatása is elég.