Skip to content

Latest commit

 

History

History
111 lines (80 loc) · 13 KB

patch.md

File metadata and controls

111 lines (80 loc) · 13 KB

ASW Update Payload

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 .