As we know from reading docs.md , we have a full arbitrary code execution primitive accomplished by simply overwriting a flash memory block which has already been written.
This primitive comes with restrictions: we can only flip bits upwards (as they're in unerased flash blocks), and we must copy data 8 bytes at a time to avoid an ECC mismatch.
We'd like to escape this constrained ASW-patching environment to flash complete arbitrary code to Simos18. To do so we need two things: a way to patch CBOOT, and a patch for CBOOT which removes protection. Both are surprisingly easy. As documented in "docs," we could flash arbitrary code directly, we could patch CBOOT in flash, or we could patch CBOOT in RAM and jump into it.
Patching CBOOT in RAM is much safer and easier for several reasons:
- By patching in RAM, we can patch whatever we want in the code that executes - we are not limited to performing a full erase-and-copy or only flipping bits upwards.
- By using CBOOT to flash a new CBOOT directly, we maintain a degree of integrity checking. While we disable signature checking, CRC validation is still active and a terminated flashing session, dead battery, or other adverse condition is unlikely to brick an ECU.
- By patching in RAM, the patch becomes idempotent. We don't need to check whether or not the patch has been applied already, we don't risk the ECU bricking if the end-user reboots the ECU willy-nilly, and we have a lot of opportunity to develop and debug the patch.
- The size of the patch can be made very small, saving painstaking time transferring block data.
For the rest of this exercise, we will use the CBOOT from 8V0906259H__0001.frf . This CBOOT has header information SC841-111SC8E0
and responds to the CBOOT Version PID with SC8.1 E0 02 SC8.
We will also use the corresponding ASW, which has software structure/revision O20
. To apply this information to another CBOOT and ASW combination, we will need to locate the engineering mode / verification disable functions, which can be located by searching for XREFs to B000218C, we will need to re-locate a suitable nop area to inject our patch, and we will need to adjust the location of the function calls in the patch (to enable supervisor mode, alter the vector table, and so on). To do this, I recommend starting with a disassembly of 8V0906259H__0001 and re-applying the patches accordingly using a disassembly of the other software. Some more automated "needle" based patching is coming as most procedures are quite recognizable, but of course is always fraught with peril.
Back to patching: we need to learn to load CBOOT into RAM, but this is easy as CBOOT does it on its own. The mechanics are documented line-by-line below, but are really quite simple and consist of resetting the peripheral controllers, resetting the task context base, moving the trap vector, and finally copying the new CBOOT and jumping into it. Because we essentially "stop" the rest of ASW execution, we don't have to be overly careful about where we jump into the patch from, and because most of the code is available to us in CBOOT, we don't have to do a whole lot.
Next, we need to figure out how to disable RSA validation. Once we load CBOOT into RAM in our disassembly tool of choice, it becomes easier to follow and we can start to locate the validity checking mechanisms. We know the ECU performs the validation when the Checksum Remote Routine is invoked, so we can follow the Checksum Remote Routine handler. Eventually, we land on a remarkably simple function at 80024cf6, which quite literally sets a flag to 0
or 1
. If this flag is set to "1," most validation is simply bypassed, in several locations. How convenient!
Upon some additional inspection of this function and its references, this is the "Sample" flag check, to determine whether an ECU is a series-production ECU or a Sample one. The function itself applies a summation algorithm against the Device ID bytes loaded into RAM at boot, compares them to the values in the OTP area, and set a flag if they match a specific value. This has some additional side-effects as well, for example the VW Logical Software Block Version
will respond with X999
for each block's version. It may be appealing to turn Sample mode on in ASW as well, but this will not work without some additional hacking as in the ASW, Sample Mode changes the DFLASH / emulated EEPROM encryption strategy. In CBOOT, however, turning sample mode on seems to be a safe way to enable free reflashing.
The nice part about this Sample mode function is that while it lives in many places across different CBOOT revisions, it seems to always reference b000218c and set it to the "Sample" value. So, by searching for XREFs to b000218c and patching the resultant function, you can enable this "engineering/development" mode freely in pretty much any CBOOT revision.
There is the same function repeated earlier in the CBOOT as a library function in PMEM. We need to patch this method too, in the next CBOOT we pass in. However, in our CBOOT-on-CBOOT world, this function isn't even loaded into RAM and we can't (and don't need) to patch it this time around, so only one patch is necessary.
So, here's the code to load, patch, and jump into an RSA Off CBOOT from a running ASW. Now it needs a home. We can write-without-erasing any ASW block, and because the CBOOT-loader resets task execution, we can jump into it from most places in ASW. Due to the glacially slow pace at which our write primitive operates, it would be best if we could choose the smallest ASW block - that's ASW3
.
There's a giant sea of free space at the end of ASW3, so finding a place for the function to live is easy, especially given it's so small. We can pick an arbitrary location on a 256-byte block boundary, like 808fd00. We need to place the code on a block boundary because the flash is programmed in 256-byte "assembly pages." While we are transmitting "no change" (0 areas) to be flashed, we can stuff the entire assembly page buffer, but to make sure the bit flips validate correctly with ECC since we didn't erase flash, we need to "slow down" when we reach the area we are overwriting and flash it only 8 bytes at a time.
Next up, we need to write a "hook" to jump into our new function from running ASW code. In my O20 version ASW3, there's a nice task initialization function starting at 8088962c with a big long sled of nop at 8088965c. Searching for isync and dsync instructions is useful to locate these sort of initialization functions which are likely to also contain nop sleds. This particular nop sled is so long that you can put pretty much whatever you want there, really - either a short position-dependent call instruction or even a full blown load-and-call.
Also importantly, this function already disables interrupts. If you move your patch elsewhere, you will need to add a disable
instruction to the beginning to prevent ASW scheduled tasks from interfering with our CBOOT load-and execution.
Assuming we pick 808fdd00 as the free space to overwrite with the function and we want to boundary-align our patch,
80889660 91 00 09 f8 movh.a a15,#0x8090
80889664 d9 ff c0 4d lea a15,[a15]-0x2300
80889668 2d 0f 00 00 calli a15=>FUN_808fdd00
functions as our hook, with. This "hook" assembler is also position independent and can be made to live in any available nop area if you wish, with the caveat that a disable
may be required at another hook location.
Please check your CBOOT carefully for strings that look like this:
-
10310096AA------NB0
: This is the hardware correlation identifier required by SBOOT. It should be the same for most SIMOS18 ECUs. -
SC841-111SC8E0
: This is the software correlation identifier required to build a full ASW. If your CBOOT has this header, there's a good chance it matches the sample used in this exercise. -
SC8.1 E0 02 SC8.
: This is the CBOOT software identifier. This confirms that the CBOOT in use is an E0 CBOOT.
If you plan to use this exact hook, please also check you are patching over ASW version O20
as well. This can be identified from the other part of the software correlation identifier - for example, at the end of: 111SC8E0O20
808fdd00 91 20 00 c8 movh.a a12,#0x8002 // we're gonna use relative addressing from 0x80020000, set it up
808fdd04 91 00 00 4d movh.a a4,#0xd000 // about to set task context base to DAT_d0000440
808fdd08 d9 44 40 10 lea a4=>DAT_d0000440,[a4]0x440 // d0000440
808fdd0c d9 cf 42 ae lea a15,[a12]-0x197e // setup call location
808fdd10 2d 0f 00 00 calli a15=>SUB_8001e682 // SET_TASK_CONTEXT : this function manipulates the PCXI register to set the task context for the running task
808fdd14 d9 cf 52 3e lea a15,[a12]-0x1b2e // reset STM (timer) peripheral function location
808fdd18 3b 00 20 41 mov d4,#0x1200 // 0x1200 -> reboot into Programming Mode argument
808fdd1c 2d 0f 00 00 calli a15=>SUB_8001e4d2 // CONFIGURE_TIMERS
808fdd20 d9 cf 9a 7e lea a15,[a12]-0x1626 // load Reset MSC peripheral function location
808fdd24 3b 00 20 41 mov d4,#0x1200 // 0x1200 -> arg for Programming Mode
808fdd28 2d 0f 00 00 calli a15=>SUB_8001e9da // RESET_MSC_PERIPHERAL
808fdd2c d9 cf 28 df lea a15,[a12]-0xc98 // ENABLE_ENDINIT location
808fdd30 2d 0f 00 00 calli a15=>SUB_8001f368 // ENABLE_ENDINIT: this function sets the Tricore ENDINIT bit using the watchdog password
808fdd34 0d 00 c0 04 isync
808fdd38 91 00 00 fc movh.a a15,#0xc000 // reconfigure trap vectors to point to 0xc0007b00
808fdd3c d9 ff 80 c7 lea a15,[a15]0x7b00
808fdd40 80 f0 mov.d d0,a15
808fdd42 0d 00 80 04 dsync
808fdd46 cd 40 e2 0f mtcr #0xfe24,d0 // 0xfe24 is the Trap Vector register
808fdd4a 0d 00 c0 04 isync
808fdd4e 0d 00 c0 04 isync
808fdd52 d9 cf 0e ef lea a15,[a12]-0xc72 // DISABLE_ENDINIT location
808fdd56 2d 0f 00 00 calli a15=>SUB_8001f38e // DISABLE_ENDINIT: this function disables ENDINIT permissions
808fdd5a 91 20 00 28 movh.a a2,#0x8002 // Copy source address: 80022000
808fdd5e d9 22 00 02 lea a2,[a2]0x2000 // low half of source addr
808fdd62 91 10 00 fd movh.a a15,#0xd001 // Copy dest address: d0008000
808fdd66 d9 ff 00 08 lea a15,[a15]-0x8000 // low half of dest addr
808fdd6a 91 10 00 50 movh.a a5,#0x1 // 0x162FF bytes to copy
808fdd6e d9 55 3f b6 lea a5,[a5]0x62ff // lower half of 0x162FF
808fdd72 09 22 01 00 ld.b d2,[a2+]0x1=>DAT_80022000 // Load data from Flash
808fdd76 24 f2 st.b [a15+]=>DAT_d0008000,d2 // store data to RAM
808fdd78 fc 5d loop a5,LAB_808fdd72 // loop until done
808fdd7a 91 10 00 fd movh.a a15,#0xd001 // Address to patch in RAM to enable Sample Mode: D000AD5E
808fdd7e d9 ff dc 5a lea a15,[a15]-0x52a4 // low half of RAM addr
808fdd82 c6 00 xor d0,d0 // easy way to set to 0
808fdd84 74 f0 st.w [a15]=>DAT_d000ad5c,d0 // perform patch
808fdd86 3b 00 20 41 mov d4,#0x1200 // set 0x1200 Programming Mode parameter
808fdd8a 91 10 00 fd movh.a a15,#0xd001 // upper part of entry point address
808fdd8e 99 ff 00 08 ld.a a15,[a15]-0x8000=>DAT_d0008000 // load entry point address
808fdd92 2d 0f 00 00 calli a15 // jump in
808fdd96 00 90 ret
808fdd98 6B 6A 01 3E// This value fixes the CRC for the block
Finally, we need to do one more thing: we need to fix the CRC of the patched ASW block, as it is verified even with the Valid blocks still set.
To do this, we use the tool crchack
- we copy our patched bytes into the ASW3 binary, and then point crchack
to a free data region, provide it the desired checksum for the block, and let it run. In this case, for example, copy the checksummed region of the file (0x300 - 0x7f9ff) and then run crchack -x 00000000 -i 00000000 -w 32 -p 0x4c11db7 -b 514712:514716 ASW3.bin 0x7BA98379 > ASW3_Patched.bin
Then we need to diff the binary with the original and copy the calculated CRC "fixer" value back in.
For this exact FRF file as the starting file, the "corrected" CRC value if applied immediately after the code patch is 6B 6A 01 3E
, so we add it immediately after the data in the patch.
For Simos18.10, the principles and process are identical but addresses differ - the CBOOT which is loaded into RAM consists of 0x1C000 bytes of data copied from 0x80803BE0 (the new location of CBOOT in Simos18.10) to 0xB0004000 (LMURAM rather than Data Scratchpad RAM - presumably because there is such a massive amount of free LMURAM on the CPUs used in Simos18). A patch to 5G0906259Q__0005
is provided as patch_1810.bin
.