BL602 EFlash Loader: Reverse Engineered with Ghidra

📝 2 Feb 2022

Pine64 PineDio Stack BL604 RISC-V Board

Pine64 PineDio Stack BL604 RISC-V Board

Something special happens when we flash firmware to BL602 and BL604 RISC-V boards…

It starts a tiny program inside the board to make flashing possible: The EFlash Loader.

Step by step we shall uncover what’s inside EFlash Loader, thanks to Ghidra the popular tool for Software Reverse Engineering.

Why are we doing this?

This is my first time using Ghidra so this might be a fun and educational exercise!

(But please bear with my ignorance 🙏)

Pine64 PineCone BL602 RISC-V Board

Pine64 PineCone BL602 RISC-V Board

§1 About EFlash Loader

How does EFlash Loader flash firmware to BL602?

Here’s what happens when we run a Firmware Flasher on our computer to flash BL602…

EFlash Loader Flow

  1. Firmware Flasher sends the EFlash Loader executable to BL602

    (Via USB UART, in 4 KB chunks)

  2. BL602 receives and starts the EFlash Loader

    (Assuming BL602 is in Flashing Mode)

  3. Firmware Flasher sends the Flashing Image to EFlash Loader

    (In 8 KB chunks)

  4. EFlash Loader writes the Flashing Image to BL602’s Embedded Flash

  5. Firmware Flasher verifies with EFlash Loader that the Flashing Image was written correctly

    (With SHA256 hashing)

Flashing firmware to BL602 with blflash looks like this…

$ blflash flash nuttx.bin  \
  --port /dev/ttyUSB0

Start connection...
5ms send count 55
handshake sent elapsed 252µs
Connection Succeed

Sending eflash_loader...
Finished 2s 11KiB/s

5ms send count 500
handshake sent elapsed 5ms
Entered eflash_loader

Erase flash addr: 10000 size: 346432
Program flash... 
Program done 4s 82KiB/s
Success

(Source)

We see that blflash sends the EFlash Loader to BL602, followed by the Flashing Image.

(Which gets written to BL602’s Embedded Flash by EFlash Loader)

We have Source Code for everything except EFlash Loader… What’s really happening inside EFlash Loader?

ELF Executable for EFlash Loader

(Source)

§1.1 Thank You ELF

Can we uncover the inner workings of EFlash Loader?

Yes we can!

Bouffalo Lab (creator of BL602) has recently uploaded the ELF Executable for EFlash Loader (pic above). Which makes Reverse Engineering much easier.

(Because of the debugging symbols inside)

Let’s decompile the EFlash Loader ELF with Ghidra!

EFlash Loader decompiled with Ghidra

(Source)

§2 Decompile with Ghidra

This is how we decompile the EFlash Loader ELF eflash_loader.elf with Ghidra

(Works for any ELF file actually)

  1. Install Java Dev Kit (JDK) 11 (64-bit)

  2. Download a Ghidra Release File.

    Extract the Ghidra Release File.

  3. Launch Ghidra…

    ## For Linux and macOS
    ./ghidraRun
    
    ## For Windows
    ghidraRun.bat
  4. The Ghidra Help Window appears, with plenty of useful info that’s not available elsewhere.

    Minimise the Ghidra Help Window for now.

    (But remember to browse it when we have the time!)

  5. In the Ghidra Main Window, click FileNew Project

    For Project Type: Select Non-Shared Project

    For Project Name: Enter “My Project”

  6. Click FileImport File

    Select our ELF File: eflash_loader.elf

  7. Ghidra detects that our RISC-V Executable is RV32GC.

    Click OK and OK again.

  8. Double-click our ELF File: eflash_loader.elf

    The CodeBrowser Window appears.

    (With a dragon-like spectre)

  9. When prompted to analyze, click Yes and Analyze.

    Ignore the warnings.

    (We’ll browse the decompiled C code shortly)

And we’re done with the decompilation! (Screenshot above)

In case of problems, check these docs…

Also check the Ghidra Help Window that we have minimised.

Export to C

§2.1 Export To C

Ghidra has decompiled our ELF File into C code. To export the C code to a file…

  1. In the CodeBrowser Window, click FileExport Program

  2. For Format: Select C / C++

  3. Click OK

    (Pic above)

We’ll get a C Source File with roughly 10,000 lines of code

Which is rather cumbersome to navigate, so we’ll use the Ghidra CodeBrowser to browse our C code in a while.

§2.2 RV32GC vs RV32IMACF

Ghidra says our executable is RV32GC. Shouldn’t it be RV32IMACF?

BL602 Executables are compiled for the RV32IMACF RISC-V Instruction Set and Extensions…

DesignationMeaning
RV32I32-bit RISC-V with Base Integer Instructions
MInteger Multiplication + Division
AAtomic Instructions
CCompressed Instructions
FSingle-Precision Floating-Point

(Source)

Ghidra thinks our executable is RV32GC, which is all of the above plus Double-Precision Floating-Point.

That’s probably OK for our Reverse Engineering, since our executable won’t have any Double-Precision Floating-Point instructions.

(If we import an ESP32-C3 RISC-V ELF, will Ghidra say it’s RV32IMC? Lemme know!)

Ghidra Symbol Tree

§3 Decompiled Main Function

Let’s locate the Main Function in our decompiled code…

  1. In the CodeBrowser Window, look for the Symbol Tree Pane at left centre

    (Pic above)

  2. Expand “Functions”

  3. Double-click on “entry”

    Watch the video on YouTube

entry is located at 0x2201 0000, the start address of executable code. Thus it’s the first thing that runs when EFlash Loader starts.

In the Decompile Pane (pic above, right side), we see the decompiled code for the entry function: eflash_loader.c

//  EFlash Loader starts here
void entry(void) {

  //  Init BL602 hardware
  SystemInit();

  //  Init BL602 memory
  start_load();

  //  Run the EFlash Loader
  main();

Aha we found the Main function! Double-click on “main”.

The Decompile Pane jumps to the Main Function: eflash_loader.c

//  Main Function for EFlash Loader
int main(void) {
  //  Init BL602 Clock
  HBN_Set_ROOT_CLK_Sel(...);
  ...
  //  Init EFlash Loader
  bflb_eflash_loader_interface_init();
  bflb_set_low_speed_flash_clock();
  ...
  //  Init Embedded Flash
  SFlash_Init(...);
  bflb_spi_flash_init(...);
  SFlash_GetJedecId(...);
  SFlash_Qspi_Enable(...);
  ...
  //  Run the EFlash Loader
  bflb_eflash_loader_main();

The Decompiled Main Function is surprisingly readable (pic below). Kudos to the Ghidra Team!

This code suggests that all the exciting action happens inside bflb_eflash_loader_main. Which we’ll examine in a while.

Main Function

(Source)

§3.1 Call Graph

All this verbose code hurts my eyes. Can we browse the code graphically?

Yes we can! Follow these steps to render the Call Graph for our Decompiled Function…

  1. Click GraphCalls

  2. Click the Arrangement drop-down box

    (Second drop-down from the right)

  3. Select “Compact Radial”

We’ll see the Call Graph below. Which kinda suggests that something exciting happens inside bflb_eflash_loader_main.

Let’s go there now!

Call Graph

§4 Decompiled Main Loop

Let’s continue the trail from the Main Function.

In the Decompile Pane (right pane), double-click on bflb_eflash_loader_main.

Inside the decompiled function we see a loop that receives and executes Flashing Commands: eflash_loader.c

//  Main Loop for EFlash Loader
int32_t bflb_eflash_loader_main(void) {    
  //  Do Handshake
  do {
    i = boot_if_handshake_poll(...);
  } while (i == 0xfffe);

  //  If Handshake is OK...
  if (i == 0) {

    //  Init Flashing Commands
    bflb_eflash_loader_cmd_init();

NextCommand:
    do {
      //  Receive Flashing Command over UART
      do {
        boot_if_recv(...);
      } while (...);

      //  Execute Flashing Command
      i = bflb_eflash_loader_cmd_process(...);

      //  Process next command
      goto NextCommand;

    } while (...);
  }
}

The code above calls bflb_eflash_loader_cmd_process to execute the Flashing Command received over UART (from the Firmware Flasher).

Let’s find out how it executes Flashing Commands.

Main Loop

(Source)

§4.1 Execute Flashing Command

Double-click on bflb_eflash_loader_cmd_process. This code appears: eflash_loader.c

//  Execute a Flashing Command with the specified Command ID and parameters
int32_t bflb_eflash_loader_cmd_process(uint8_t cmdid, uint8_t *data, uint16_t len) {
  
  //  Omitted: Lookup the Command ID 
  //  in list of Flashing Commands
  ...

  //  If Flashing Command is enabled...
  if (eflash_loader_cmds[i].enabled == 1 && ...) {
    
    //  Execute the Flashing Command
    ret = (*eflash_loader_cmds[i].cmd_process)();
    return ret;
  }

Interesting! We see that EFlash Loader has a list of Flashing Commands: eflash_loader_cmds.

The code above looks up eflash_loader_cmds for the Flashing Command (by Command ID). And executes the command by calling cmd_process.

Execute Flashing Command

(Source)

What are the Flashing Commands supported by EFlash Loader? We’ll find out next.

Flashing Commands

§5 Decipher Flashing Commands

Recall that eflash_loader_cmds defines the list of Flashing Commands supported by EFlash Loader.

In the Decompile Pane (right pane), double-click on eflash_loader_cmds.

This appears in the Listing Pane (centre pane)…

24 Flashing Commands

Hover our mouse over eflash_loader_cmds.

Ghidra says that 24 Flashing Commands are defined inside the array. Let’s decipher them…

Flashing Commands deciphered by Ghidra

  1. Expand the array eflash_loader_cmds to see all 24 Flashing Commands

    (See pic above)

  2. For each Flashing Command, hover our mouse as shown above

    (Or double-click it)

  3. Ghidra reveals the function that handles the Flashing Command

    (Like bflb_eflash_loader_cmd_get_bootinfo)

Now we know all 24 Flashing Commands. Neat!

§5.1 List of Flashing Commands

Here are all 24 Flashing Commands supported by EFlash Loader, as decoded by Ghidra from eflash_loader_cmds

IDASCIIFlashing Command
10LF*_get_bootinfo
21!*_reset
300*_erase_flash
311*_write_flash
3F?*_write_flash_with_decompress
322*_read_flash
344*_xip_read_flash
3A:*_write_flash_check
3B;*_set_flash_para
3C<*_flash_chip_erase
3D=*_readSha_flash
3E>*_xip_readSha_flash
40@*_write_efuse
41A*_read_efuse
42B*_read_mac_addr
50P*_write_mem
51Q*_read_mem
71q*_read_log
60`*_xip_read_flash_start
61a*_xip_read_flash_finish
366*_read_jedec_id
377*_read_status_register
388*_write_status_register
333*_flash_boot

(* denotes bflb_eflash_loader_cmd)

7 of the above Flashing Commands are documented in the BL602 ISP Protocol

IDDocumented Command
10Get Boot Info
3CChip Erase
30Flash Erase
31Flash Program
3AFlash Program Check
32Flash Read
3DSHA256 Read

The other 17 Flashing Commands are undocumented.

(Which might be interesting for future exploration!)

Flashing States

§6 Flashing States

You can’t tell which way the train went by looking at the tracks… So let’s switch over to the (already documented) Firmware Flasher and understand how it calls the Flashing Commands.

The Firmware Flasher works like a State Machine. Each Flashing State triggers a Flashing Command…

Flashing States

(Source)

Below are the Flashing States and Flashing Command IDs derived from util_program.go

Flashing StateIDOn Success
ConfigReset*Reset
*Reset*ShakeHand
*ShakeHand55*BootInfo
*BootInfo10*BootHeader
*BootHeader11*SegHeader
*SegHeader17*SegData
*SegData18*CheckImage
*CheckImage19*RunImage
*RunImage1A*Reshake
*Reshake55*LoadFile
*LoadFile*EraseFlash^
*EraseFlash30*ProgramFlash
*ProgramFlash31*ProgramOK^
*ProgramOK3A*Sha256
*Sha2563D*LoadFile
*ProgramFinish55*ProgramFinish

* denotes Cmd (like CmdReset)

^ denotes multiple states

(See the complete table)

Now that we have the Flashing States and the Flashing Commands, let’s match them.

Match Flashing States and Commands

§7 Match Flashing States and Commands

Right now we have two interesting lists…

Let’s match the two lists and find out which Flashing Commands are actually called during flashing

IDASCIIFlashing Command
10LFGet Boot Info
*_get_bootinfo
300Flash Erase
*_erase_flash
311Flash Program
*_write_flash
3A:Flash Program Check
*_write_flash_check
3D=SHA256 Read
*_readSha_flash

(* denotes bflb_eflash_loader_cmd)

Out of 24 commands, only 5 Flashing Commands are actually called during flashing!

(3C Chip Erase and 32 Flash Read aren’t used while flashing BL602, according to BLOpenFlasher)

And out of the 5 Flashing Commands, only 1 looks interesting…

Let’s study the Decompiled Code and find out how it writes to the Embedded Flash.

Match Flashing States and Commands

(Source)

§8 Flash Program

In the Symbol Tree Pane (left centre), enter this into the Filter Box

bflb_eflash_loader_cmd_write_flash

Double-click on the function bflb_eflash_loader_cmd_write_flash.

This is the decompiled Flashing Command that writes the Flashing Image (received via UART) to Embedded Flash: eflash_loader.c

//  Flashing Command that writes Flashing Image to Embedded Flash
int32_t bflb_eflash_loader_cmd_write_flash(uint16_t cmd,uint8_t *data,uint16_t len) {

  //  Write Flashing Image to Embedded Flash
  bflb_spi_flash_program(...);

  //  Return result to Firmware Flasher
  bflb_eflash_loader_cmd_ack(...);

The code above calls bflb_spi_flash_program to write the Flashing Image to the Embedded Flash.

Let’s look inside the function…

Flashing Command that writes Flashing Image to Embedded Flash

(Source)

§8.1 Write To Flash

In the Decompile Pane (right pane), double-click on bflb_spi_flash_program. This appears: eflash_loader.c

//  Write Flashing Image to Embedded Flash
int32_t bflb_spi_flash_program(uint32_t addr,uint8_t *data,uint32_t len) {
  
  //  Call BL602 ROM to write to Embedded Flash
  SFlash_Program(...);

This function calls SFlash_Program to write to Embedded Flash.

SFlash_Program is defined in the BL602 ROM

Source Code is available in the BL602 IoT SDK

We’re all done with our Reverse Engineering of BL602 EFlash Loader! 🎉

Write Flashing Image to Embedded Flash

(Source)

§9 How The Train Goes

Thanks to Ghidra we now know everything about EFlash Loader…

Over the past year we speculated on the inner workings of EFlash Loader…

Finally we know what’s inside!

What happens after the Flashing Image has been written to Embedded Flash?

The Flashing Image is compressed with XZ Compression.

The image is decompressed and mapped to XIP Memory (Executable in Place) by the BL602 Bootloader…

And the new firmware starts running on BL602.

§10 What’s Next

I had fun reverse enginnering the BL602 EFlash Loader… My first time using Ghidra!

And I hope you found this article useful for real-world reverse engineering with Ghidra.

Many Thanks to my GitHub Sponsors for supporting my work! This article wouldn’t have been possible without your support.

Got a question, comment or suggestion? Create an Issue or submit a Pull Request here…

lupyuen.github.io/src/loader.md

§11 Notes

  1. This article is the expanded version of this Twitter Thread

  2. Many thanks to BraveHeartFLOSSDev for the inspiration! We previously collaborated on this article…

    “Reverse Engineering WiFi on RISC-V BL602”

  3. There are 2 versions of the EFlash Loader ELF File…

    eflash_loader.elf (17 Jan 2022)

    eflash_loader.elf (1 Nov 2021)

    Might be interesting to compare the decompiled code and discover the changes!

    (Here’s why)

  4. Does Firmware Flasher send the EFlash Loader ELF to BL602?

    Nope it sends the stripped binary for the EFlash Loader, which is easier to load and run on BL602: eflash_loader_40m.bin

    Bouffalo Lab used to provide only the stripped binary for EFlash Loader, not the ELF…

    bl_iot_sdk/flash_tool/chips/ bl602/eflash_loader

    But since Nov 2021 they started uploading the ELF. Which is how we did the reverse engineering with Ghidra. Lucky us ;-)