Skip to content

Instantly share code, notes, and snippets.

@mnaberez
Last active April 3, 2024 20:24
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mnaberez/ea3c3feb3a1619393b997bfb5e7de35f to your computer and use it in GitHub Desktop.
Save mnaberez/ea3c3feb3a1619393b997bfb5e7de35f to your computer and use it in GitHub Desktop.
NEC 78K0 Flash Vulnerability

NEC 78K0 Flash Vulnerability

Overview

The microcontroller that I studied was removed from a Volkswagen car radio manufactured by Delco (now Delphi). The chip had only Delco markings on the package. It was decapsulated and the markings "NEC D78F0831Y" were found on the die. I reverse engineered the device definition files for the NEC toolchain (RA78K0) and determined that the uPD78F0831Y is a subset of a documented chip, the uPD78F0833Y. The '833Y adds 3 more timers, extended I2C functionality, and adds registers that allow firmware running on the '833Y to reprogram the flash ("self-programming"). Otherwise, the '831Y and '833Y are the same.

The uPD78F0831Y uses the NEC 78K0 core. Note that NEC (now Renesas) produced a number of incompatible cores with similar names such as 78K0S and 78K0R. Those were not studied. The 78K0 is an 8-bit core with a CISC instruction set that is similar to the Intel 8080 but not compatible with it. It is a von Neumann architecture with a single 64K memory area for everything. The uPD78F0831Y has 60K of flash for program data at the bottom of memory, 3K RAM near the top, and peripheral registers at the very top. Other 78K0 parts are similar.

The uPD78F0831Y is an early secure microcontroller. It stores all of its program data in flash and has no documented way for an external device programmer to read it out. I found that the "write" command could be abused to non-destructively identify bytes in the flash that could be patched. I also found that the "write" command could patch those bytes without disturbing others.

I patched the flash with a trojan that dumps all of the memory space out on the I/O lines. The patch requires overwriting some areas, so the contents of those areas are lost. Using two identical microcontrollers removed from two different car radios, I was able to patch different areas on each and extract the entire firmware between them. The chips were then erased, reprogrammed with the extracted firmware, and both car radios worked again.

Highlights

  • The flash is programmed via SPI using a documented protocol. Note that the document disappeared from the Renesas (NEC) website at some point.

  • The "erase" command only erases the entire 60K flash. The flash is not divided into smaller blocks as is common in other microcontrollers. Erasing sets every bit in the flash to a "1".

  • There is no "read" command, which is a security feature. There is only a "verify" command that does an internal comparison. It requires sending an entire 60K image to the chip and it only answers good/bad for the whole image at the end, making most brute force attacks infeasible.

  • The "write" command can overwrite a single byte anywhere in the flash without disturbing neighboring bytes. However, it can only change a bit from a "1" to a "0". Any byte can be changed to 0x00, which is the 78K0 opcode for NOP. NOP slides can be created anywhere.

  • The "write" command with a single byte of 0xFF can non-destructively test if any byte in the flash is 0xFF. Once areas of 0xFF are found, they can be overwritten with any other bytes (trojan code).

Hardware Setup

I removed the uPD78F0831Y from the target and mounted it on a board of my own design. Removing the microcontroller from the target makes all pins available for any purpose.

The board contains power, ground, clock, and a DS1813 reset generator. One I/O port is used to output 8 data bits, a second I/O port is used to output a /STROBE signal to indicate when data is valid. The trojan code will dump all memory out these I/O ports where it will be captured with a logic analyzer. I chose this approach because it only required a very small amount of code in the trojan.

A flash programmer that supports 78K0 is needed. NEC recommended the PG-FP3 ("Flashpro III") which I couldn't find anywhere. I found that the PG-FP3 was actually made by NDK Corporation and they also sold it as FL-PR3. There were several FL-PR3 units on eBay from surplus dealers. I was able to buy one inexpensively.

I made two discoveries about the FL-PR3 that made using it possible for the project:

  • The GUI only programs the entire flash from 0x0000-0xEFFF. However, the FL-PR3 uses RS-232 serial and the commands are plaintext. I found that the "PRG" (program) command will program any subset of addresses.

  • The FL-PR3 normally writes in batch sizes of 256 bytes. The batch size used with the "write" command must be 1 byte for the methods described here. The FL-PR3 uses device definition files with a .PRC extension. These are INI files. I found that the batch size could be changed in the .PRC file (look for "0100" and change it to "0001").

I confirmed with a logic analyzer that the FL-PR3 only sends the documented SPI commands. Building a DIY programmer would not be difficult.

Finding Areas to Patch

In the beginning, the contents of the flash will be completely opaque. Using the "write" command and a batch size of 1 byte, write 0xFF to each location from 0x0000-0xEFFF. Record the results for each location.

Writing to any location that already contains 0xFF will succeed. A location containing any other byte will fail but the byte will not be distrubed.

Record all locations containing 0xFF. Find the largest consecutive groups of 0xFF in the list. Some groups of 2 bytes or more will be needed in order to patch in the trojan.

Writing the Trojan

Some variation of this trojan code must be patched into the flash:

7B 1E                   di              ;disable interrupts
13 24 00                mov pm4, #0     ;port 4 = all bits output   (8 data bits)
13 25 00                mov pm5, #0     ;port 5 = all bits output   (/strobe on bit 0)

                    loop:
0A 05                   set1 p5.0       ;/strobe = high
87                      mov a, [hl]     ;read byte from memory
F2 04                   mov p4, a       ;write it to the port
0B 05                   clr1 p5.0       ;/strobe = low
86                      incw hl         ;increment to next memory address
9B xx xx                br !loop        ;loop forever ("!" forces absolute address)

As every firmware will have 0xFF in different locations, there is no single recipe. It is a manual process where locations containing 0xFF are identified and then trojan code is written to fit in those areas.

The firmware being patched may not have enough consecutive 0xFF bytes for the trojan. The firmware that I extracted did not. However, any byte of flash can be overwritten with 0 (the 78K0 opcode for NOP). Pieces of the trojan can be written into different areas and then the gaps filled with NOP.

Obtaining Code Execution

Patching the trojan into the flash alone is unlikely to lead to its execution. The program counter must enter the trojan code area at some point aligned with the start of an instruction. Putting a NOP slide in front of the trojan will increase the chances that it executes. The size of the NOP slide can be increased until code execution is achieved.

It may be possible to control execution from the first instruction. The 78K0 reset vector is 0x0000-0x0001. By overwriting these two bytes with 0, reset will jump to address 0x0000 and then execute two NOPs (opcode 0). Successive bytes can be overwritten to form a NOP slide that executes after reset.

In the firmware that I worked on, I found that I could control 3 bytes at 0x02b3. This was not enough for the trojan code but it was enough for an absolute jump instruction. I wrote the trojan code somewhere else, high in memory, where more space was available. I filled 0x0000-0x02b2 with 0, which created a NOP slide from reset until 0x02b3. I put an absolute jump to the trojan at 0x02b3.

Having control of execution from the first instruction is desirable because it means no part of the original firmware will run before the trojan. Code that could potentially interfere with the trojan, such as setting up interrupts or the watchdog, will not be run.

Dumping

If the trojan executes successfully, it will continuously dump all memory out the I/O ports. This must be captured by a logic analyzer. The dumps must then be separated. Since the trojan is written to a known address, locating these bytes will show where to split the dumps.

If the same dump is extracted multiple times, it means the dump is good. If there are differences, something is interfering with the trojan (e.g. interrupts).

Combining Multiple Samples

Since this process requires overwriting parts of the flash, multiple chips will be needed to extract the entire firmware. The trojan will have to be patched into different non-overlapping areas, then the dumps combined.

Once the first chip is successfully trojaned, some part (hopefully a large part) of the firmware will be revealed. These known bytes can be used to develop the trojan for the next chip.

In the firmware that I extracted, there was only a small number of 0xFF areas and only one practical place to write the trojan. Writing a second trojan at a non-overlapping location seems impossible. However, after the first successful dump, I found large areas filled with 0xBF (the BRK instruction). I was able to write the second trojan into those areas with careful coding (no byte could have bit 6 set) and two jumps out to the 0xFF areas.

When enough non-overlapping areas have been dumped from enough chips, the dumps can be combined to make the complete firmware. Any target devices bricked in the process can then be fixed by re-flashing them with the extracted firmware.

Contact

I do not provide consulting services and I can't tell you how to dump a particular chip. I am not able to respond to such inquiries. Feel free to contact me if you have done your own technical work.

This page was written by Mike Naberezny. Some photos of the project are also available.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment