Simos18 ECUs provide the Application Software with "NVRAM" which is emulated via the Tricore CPU's DFlash area.
This NVRAM is organized into 127 "channels" which can each store specific information - for example, adapted and learned values, variant coding, immobilizer data, and serial numbers.
The NVRAM system is built to minimize writes to the DFlash area - generally, changes are buffered and written out to the DFlash at a negative power latch phase (on ECU shutdown).
6 channels of NVRAM data are available to the "simple NVRAM" system in CBOOT - this is used to enable CBOOT to read critical data at boot, like the VIN and part number revisions.
Some NVRAM channels are intended to persist across a software flash, while others are intended to be discarded. Additionally, some channels are written twice to the second DFlash controller, while most are persisted only once.
Every stored record carries two CRC-16 checksums, both polynomial 0x8005:
-
An inner content CRC, present depending on the channel's
record_way. Init 0xABCD, computed over the channel's plaintext content and stored little-endian as a 2-byte prefix. On encrypted channels it lives inside the Hitag2 ciphertext.record_way == 0has no prefix. This is computed at the application payload layer. -
An outer record CRC (the "trailing checksum"). Init 0xA55A, computed over the record payload followed by an 8-byte trailer (below) and appended to the record. This is computed at the EEPROM emulation / DFlash layer.
The immobilizer data uses CCITT checksums internally.
Optinally, some NVRAM channels can be encrypted with what appears to be the Hitag2 algorithm (attempted implementation here) with altered f4/f5 values.
The Hitag-encrypted channels are all encrypted with a shared key and IV generated using the Tricore Device ID. In this way, cloning the DFlash from one ECU to another is prohibited.
The layout of NVRAM in DFlash appears to be versioned. Searching for CH 46 seems to be the most reliable way to locate a record in NVRAM so far.
Some sample records are provided here:
42203131 31534338 46304838 30300000 00000000 00000004 0B239003 01464DC6
This record is Channel 1 due to: 0146
It contains 0x3 * 0x8 bytes of data due to the prior byte being 03:
42203131 31534338 46304838 30300000 00000000 00000004
Because the record_way is 1, the first two data bytes are the inner content CRC16: poly 0x8005, init 0xABCD, stored little-endian. It covers the channel's content only, not the whole 22-byte block:
CRC16 (LE prefix 4220 = 0x2042): crc16(init=0xABCD, "3131 31534338 46304838 3030 0000") = 0x2042 ✓
The trailing checksum: polynomial 0x8005, init 0xA55A, over the channel data followed by an 8-byte trailer.
7E 05 00 00 | 0D | 00 | 46 | 06
<gen:4LE> <len:1> <00> <marker> <channel>
<gen:4LE> is the flash generation counter (see below), little-endian.
The firmware CRCs the channel's logical data size, not the full record bytes; if there are leftover bytes, it's padded up to the 8 byte alignment used by the underlying flash emulation driver.
There are three ways the record can end up padded into the flash emulation:
- left-pad
data[0:data_size] - right-pad
data[len*8-data_size:] - split : the record straddles a block boundary. In this case, a block-management byte is interleaved in the dump but not in the CRC stream; for example, channel 6's 103 logical bytes are stored as 72 + (1 marker) + 31. The split offset depends on underlying page alignment rather than record type.
Let's look at a simple record:
00600C00 55550000 0AF51001 2A4650AE
This record is record number 2A , as we know from the record specifier 2A 46 . It is 8 bytes long, as specified by length 01 . It has checksum 50AE, which matches when we calculate the CRC16 init A55A poly 8005 over 600c00555500007e0500000100462a .
The value in the trailer is the flash generation counter. Records live in a page, and when a page fills the live records are copied into a freshly-erased page (garbage collection). The firmware keeps a 32-bit counter, which is incremented on each page switch.
The counter is written into every record's 0xA55A trailer as part of the CRCed material. We can recover it either from the FFFE page header or by brute-forcing it from any valid channel CRC. dflash.py does both and cross-checks them.
The 0AF510 field next to the trailer CRC is just the CRC (50AE) XOR FFFF, shifted left 4 bits within a 24-bit value.
dflash.py renders a dump as a block with ASCII boxes per record.
uv run python dflash.py PMU0_DFlash.bin # full ASCII block view
uv run python dflash.py PMU0_DFlash.bin --summary # one line per channel
uv run python dflash.py PMU0_DFlash.bin --channel=9 # just one record
uv run python dflash.py PMU0_DFlash.bin 4480051118a04829020c0020 # decrypt enc. channels (incl. immo)
A record box looks like:
+ ch 0x09 (9) @0x03748 len=1 (8B) -----------------------------------------+
| raw : de 83 00 ff 80 55 00 00 |
| |
| DATA ([T] covers data[0:]) |
| [P] crc16 : de 83 = 0x83DE init ABCD over 4 B [OK] |
| content : 00 ff 80 55 |...U |
| pad : 00 00 |
| TRAILER |
| inv_crc : 0F95C0 ((crc^FFFF)<<4) |
| len/id : 01 / 46 09 x8=8B, marker/ch |
| [T] crc16 : 06A3 init A55A, seed 7e05 0000 01 00 46 09 [OK] |
+----------------------------------------------------------------------------+
[P] is the inner content CRC (init 0xABCD), [T] the outer record CRC (init
0xA55A).
These are records 6, 7, and 8.
Here's a sample:
AAC98552 8F0000D4 3A0000F8 8B005967 F8FBF7AF 634F17CF 7865F183 24C33156 57415437 41333146 43303232 39313501 6AAA2735 00000000 A5050000 03000000 BF800000 00000000 00000000 00000000 00000031 56574154 37413331 46433032 32393135 9738D4B9
Each 6, 7, and 8 record contains 3 sub-records: datStat, datDat, and the VIN written again, I think for use by CBOOT.
datStat and datDat need to be identical across all 3 records.
[0:13] datStat
[13:66] datDat
[66:100] third field = [17 reserved/zero][17 ASCII VIN copy]
[100:104] padding to dflash block size
The structure of each is as follows:
AAC985528F0000D43A0000F88B
`AA` or `55` depending on if authPreVld was set, should be AA
Next 4 bytes C985528F are the first 4 values of imoRand
Next 2 bytes 0000 are just 0.
Next 4 bytes D43A0000 are some kind of counter incremented when the data task is called.
Next 2 bytes F88B are CRC16-CCITT of this subrecord.
005967F8FBF7AF634F17CF7865F18324C33156574154374133314643303232393135016AAA27350000000A505000003000000BF80
00 is static
Next 16 bytes are the AES key CS/noKeySecu: 5967F8FBF7AF634F17CF7865F18324C3
Next 17 bytes are the VIN noVeh: 31565741 54374133 31464330 32323931 35 -> 1VWAT7A31FC022915
Now we have a bunch of data:
016AAA27 35000000 00A50500 00030000 00BF80
01 -> ctDatBasFazit , prefix means it is a counter of some kind, always 1 in files I have seen
6A -> PClass byte.
AA -> Internal state, same across everything I have seen. A5 and 5A are also values here, not sure exactly what they do here yet.
2735 -> noKeyMst , This is the PIN for the immobilizer master participant (instrument cluster).
00 -> tiDlyDown, this one is a diff, I think it's the time delay for download requests
00 -> noDlyDown, download counter
00 -> tiAccDown, another Download time interval
00 -> noAccDown, another Download counter
A5 -> bInhAcsMem == true, same across all immos I have seen
05 -> bLock == false, same across all immos I have seen
00 -> bTrigFctDi == false, no idea what this means
00 -> seems to never be written, always 0
03 00 00 00 -> this is a counter of some kind and it's the same across most immos I have seen.
BF80 -> CRC16-CCITT
[66:83] 17 bytes, reserved / zero in every dump seen so far
[83:100] 17-byte ASCII VIN in plaintext.