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 részlet: ez standard MD5, a klasszikus RFC 1321 IV-kkel)
- 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
AFFE0815markert a flashbe, és a saját EEPROM-jába beállítja a0x01(Normalbetrieb) programozási státuszt.
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.
0xC0290-en lévő AFFE0815 tehát nem maga a státusz, hanem az a jelzés, amit az ECU a verify után a flashbe ír; a státuszt, a 0x01-et az ECU a saját EEPROM-jába írja. Ez lesz a lényeg lentebb: a DDE-n az EEPROM-státuszt nem tudom kívülről, OBD-n írni (ellentétben a VGSG-vel), csak rávenni az ECU-t, hogy ő maga írja 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. (Bár ha lefuttatod a rutint, és elbukik az aláírás ellenőrzés, akkor is ezt a státuszt írja be az EEPROM-ba) Ez 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.
Dokumentált megerősítés: a hivatalos SGBD
Eddig ez a kép megfigyelésből állt össze: CAN logolás flashelés közben, diff, Ghidra. De a BMW diagnosztikai leírás, a 10DDE626.prg SGBD (EDIABAS objektum, a stringjei XOR 0xF7-tel kódolva) feketén-fehéren alá is támasztja. Dekódolva a job-definíciók pontosan azt mondják, amit eddig láttam.
A FLASH_SIGNATUR_PRUEFEN job ugyanazt a routine-t hívja, amit a CAN logban, és megmutatja a két ág eredetét is:
JOBNAME: FLASH_SIGNATUR_PRUEFEN
JOBCOMMENT: KWP2000: $31 StartRoutineByLocalIdentifier
JOBCOMMENT: $09 CheckSignature
ARG: BEREICH ('Programm' | 'Daten')
ARG: SIGNATURTESTZEIT (Zeit in Sekunden)
RESULT: JOB_STATUS (OKAY, wenn fehlerfrei)
Vagyis a $31 09 CheckSignature egyetlen routine, és a BEREICH argumentum dönti el, hogy a Programm (kód, PAF-ág) vagy a Daten (cal, DAF-ág) aláírását ellenőrzi. A két külön státusz (0x05 PAF / 0x06 DAF), amit a flashelés CAN logjából következtettem ki, itt egyetlen job két bemenetén jön ki, a Bosch kétágú modellje dokumentálva, nem sejtésből.
És a marker hiánya a diagnosztikában is bizonyíték, nem hiányosság: a teljes DDE626 SGBD-ben nulla marker író vagy olvasó job van, és sehol egy AFFE konstans. Nincs olyan diagnosztikai parancs, amivel a 0xC0290 markert kívülről írni lehetne, mert az tisztán belső iflash-mechanizmus, amit az ECU maga ír. Pontosan ezért nem ír rá a WinKFP sem.
Végül a teljes PROGRAMMIERSTATUS tábla, közvetlenül a SGBD STATUS_TEXT táblájából, a gyártói szöveggel, nem parafrazálva:
| Kód | SGBD-szöveg (10DDE626.prg) |
|---|---|
0x00 | Anlieferzustand |
0x01 | Normalbetrieb ★ |
0x02 | nicht benutzt |
0x03 | Speicher gelöscht |
0x04 | nicht benutzt |
0x05 | Signaturprüfung PAF nicht durchgeführt |
0x06 | Signaturprüfung DAF nicht durchgeführt |
0x07 | Programmprogrammiersitzung aktiv |
0x08 | Datenprogrammiersitzung aktiv |
0x09 | Hardwarereferenzeintrag fehlerhaft |
0x0A | Programmreferenzeintrag fehlerhaft |
0x0B | Referenzierungsfehler Hardware → Programm |
0x0C | Programm nicht vorhanden oder nicht vollständig |
0x0D | Datenreferenzeintrag fehlerhaft |
0x0E | Referenzierungsfehler Programm → Daten |
0x0F | Daten nicht vorhanden oder nicht vollständig |
0x10 | Reserviert für BMW |
0x80 | Reserviert für Zulieferer |
A 0x06 sora itt a legbeszédesebb: "Signaturprüfung DAF nicht durchgeführt" , a $31 09 CheckSignature, azaz a verify rutinnem futott le, (vagy lefutott és elbukott).
A kiskapu: egy 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, és ekkor az ECU beírja a0x01Normalbetrieb-et a saját EEPROM-jába.
Ez a kulcs a DDE6-nál: a fallback nem közvetlenül a flashből "olvassa" a státuszt, hanem az ECU-t veszi rá, hogy ő maga írja be az EEPROM-státuszt 0x01-re. A flashbeli marker csak a trigger; a státusz kapcsoló az EEPROM-ban billen át. Ez lesz fontos, amikor lentebb a VGSG-vel és a 6HP-vel összevetem.
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, elbukik (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 az ECU a saját EEPROM-jába írja be a
0x01Normalbetrieb-et. Én az EEPROM-ot nem érintem; az ECU írja saját magának.
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 billenti át az EEPROM-státuszt. Vagyis a DDE6-on közvetve állítom be az EEPROM-státuszt: nem OBD-írással (arra itt nincs mód), hanem úgy, hogy az ECU-t a flashbeli marker + fallback ágon ráveszem, hogy ő maga írja be, szemben a VGSG-vel, ahol a $31 28 EEPROM-write-tal közvetlenül beírom.
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ána egy second-tier marker-mező, közvetlenül az aláírás után. Első ránézésre kézenfekvő volt a következtetés: ugyanaz, mint a DDE6, csak más címen.
Egy különbség rögtön szembeötlött: 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. Ami DDE6-on FF FF plusz az AFFE0815 marker, az 6HP-n fordított byte-mintaként jelenik meg. Egy 0x01-státuszú TCU-ból kiolvasott érték és a .0da csere-fájlbeli érték is épp fordítva viszonyul egymáshoz, mint a DDE6-on.
Egy 6HP TCU flash-fájl hexdumpja, a 128 byte RSA-1024 aláírás után közvetlenül a marker-mezőnek gondolt rész.
Csakhogy időközben visszafejtettem a Siemens VDO alapú xDrive transfer-case modult (a fentebb már emlegetett VGSG-t), és ott állt össze a teljes kép. A VGSG-nél a programozási státusz ugyanúgy a modul EEPROM-jában él, mint a DDE6-nál, de ott van rá közvetlen OBD-út egy diagnosztikai EEPROM-write szolgáltatással a 0x01 Normalbetrieb-et közvetlenül, kívülről beírható az EEPROM-ba. Nincs flash-fájlbeli marker.
Ez is bizonyítja: ott is az EEPROM-státusz a valódi kapcsoló, csak nincs hozzá OBD-írási út. A DDE626 SGBD-jében nincs olyan job, amivel az EEPROM-státuszt kívülről billenteni lehetne. Ezért kell a DDE6-nál a kerülőút: a flashbeli AFFE0815 markerrel + a fallback memcmp-ággal az ECU-t veszem rá, hogy ő maga írja be a 0x01-et az EEPROM-jába. A cél mindkét modulnál ugyanaz (EEPROM-státusz 0x01), csak a DDE6-on közvetve, a VGSG-n közvetlenül érem el.
Ez a 6HP-re vonatkozó hipotézisemet is a helyére teszi.Szinte biztos, hogy a 6HP is EEPROM-státuszú, mint a másik kettő, és az összes BMW modul, a fájlban talált aláírás-blokk és marker-mező inkább a struktúra maradványa, a tényleges kapcsoló a TCU EEPROM-jában lévő programozási státusz. A nyitott kérdés ott már csak az, hogy melyik írási úton lehet a 0x01-et beállítani: közvetlen OBD EEPROM-write-tal (mint a VGSG), vagy az ECU-t rávéve (mint a DDE6).
Fontos, hogy ez a 6HP-nél egyelőre hipotézis: a fájlstruktúra megvan, az xDrive-analógia erős, de éles 6HP-flasht ezen az úton még nem futtattam, és a pontos EEPROM-mechanizmust szándékosan nem részletezem itt. Architektúrálisan viszont letisztult a kép: mindhárom modulnál az EEPROM-ban él a programozási státusz ugyanaz a Bosch/ZF/Siemens állapotgép-gondolat. A különbség nem "flash-image vs EEPROM", hanem hogy hogyan jut be a 0x01 az EEPROM-ba: a VGSG-n közvetlen OBD-írással, a DDE6-on az ECU-t rávéve (marker + fallback), a 6HP pedig egyelőre nyitott, de az egyik útra fog illeszkedni.
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: a marker megvan, az ECU beírja a
0x01-et a saját EEPROM-jába, Normalbetrieb.
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 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.