Firmware Extraction Series - Raw NAND File Recovery

Sunday, March 10, 2019 🌐中文

Preface

This post documents the process of restoring the NAND Flash filesystem from an in-vehicle head unit.

Recovery process

The head unit is equipped with an MXIC B162711 NAND Flash storage chip and a Qualcomm CPU. Raw NAND Flash is typically not controlled directly by the host processor unless the vendor, such as Qualcomm, is highly confident in their controller/SoC implementation. During hardware analysis, I observed no anti-tamper measures on the chip, so I proceeded to desolder it using a hot-air rework station.

tearoff

The chip details are shown below; it is a 512MB SLC NAND.

features

The chip utilizes a BGA63 package with the following pinout:

bga63

The extraction process was performed using a Proman programmer:

socket

reading

I read the chip multiple times and compared the resulting dumps. To mitigate the impact of bit flips and ensure data integrity, I applied a majority-voting algorithm, retaining the most frequent byte values.

bytes_diff

The head unit is powered by a Qualcomm CSR3703 SoC.

CSR3703

The datasheet for this chip is not publicly available. Despite exhaustive searches, I was unable to obtain it, as the documentation is kept strictly confidential.

Even after a manual teardown, the specific chip variant could not be identified solely from the BGA layout.

CSR3703pin

Through black-box analysis, I confirmed that the SoC’s memory mapping table resides within the NAND controller. Consequently, the logical block order cannot be directly determined from the raw firmware dump.

soc_block_diagram

As a result, I had to analyze the extracted firmware without knowledge of the mapping. By examining the raw binary data from the NAND dump, I was able to carve out some low-level system components, including U-Boot.

binwalk

While U-Boot can be reverse-engineered, the critical information lies within the system partition.

CSR Visor analysis (128KB):

dts1

The Device Tree indicates that the console is ttySiRF1 running at 115200 baud.

dts2

NAND controller configuration details:

dts3

Since the erase block distribution in the first head unit’s NAND dump appeared overly random, I decided to desolder and read the NAND Flash from a second, identical head unit.

reball1 reball2

To restore the head unit to working condition, I reballed the chip and reattached it using an optical rework station for precise alignment.

reball3

After further research, I suspected the Wear Leveling (WL) mechanism might be using a hybrid mode. I reviewed publicly available FTL (Flash Translation Layer) implementations but could not find matching mapping logic. I hypothesized that the FTL table might be stored in the NAND controller’s ROM.

compact_flash_system

By analyzing U-Boot, I determined it utilizes a proprietary NANDDisk driver which uses ioctl for NAND read/write operations. The mapping algorithm, however, was not implemented within the driver itself.

nand_driver

Consequently, I could only recover fragmented files rather than a complete filesystem. To verify if the mapping table was stored inside the SoC, I performed a controlled experiment using two identical head units. I swapped their NAND chips (reballing and resoldering each time). Neither head unit would boot with the other’s NAND Flash. However, when the original chips were returned to their respective units, both booted successfully. This confirmed that the mapping logic or table is tied to the specific SoC.

reball_again

After multiple comparisons, I managed to extract a RAMdisk with over 50% integrity, allowing me to successfully read the first half of the data.

binalk_again

hex_workshop

I confirmed the use of a Linux EXT filesystem. The MBR information indicated three partitions:

010editor

Partition 1: 30MB

Partition 2: 400MB

Partition 3: 61.75MB

The remaining files were primarily resources and fragmented ELF binaries. Due to the fragmentation, proper reverse engineering of the ELF files was not possible.

files_1

I attempted to manually deduce the erase-block ordering logic by splitting and analyzing each block. However, inconsistencies between the initial and subsequent OOB areas misled my initial analysis. I only managed to recover the page order, which allowed reconstruction of the smaller zImage and ramdisk, but the overall mapping logic remained elusive.

files_2

I also failed to locate a map table within the NAND dump. Quick filesystem recovery was impossible; I needed to analyze the block patterns to derive the underlying mapping logic.

I decided to remove and read the NAND Flash from the second head unit. Unfortunately, the two units were running different system versions, which hindered further comparative analysis.

XXXXX_IHU_LOW_A7_LINUX_18.0F40

XXXXX_IHU_LOW_A7_LINUX_18.0F43

The kernel and ramdisk build timestamps and page layouts were inconsistent, making it impossible to infer the mapping algorithm through comparison.

modification_time modification_time2

Suspecting a hybrid mapping scheme, I obtained a firmware image of the same version via a USB HID “GetShell” exploit. Analyzing this known-good firmware allowed me to derive the mapping algorithm.

struct

Below is the script I used to reconstruct the NAND dump. Looking back, I don’t recall the specific details of the logic, but it was effective at the time.

#!/usr/bin/env python3
import sys
import binascii
import struct

if len(sys.argv) < 3:
    print("Usage: fuckftl.py ftl.bin raw.bin")
    sys.exit(1)

def bin2hex(bin):
    x = str(binascii.b2a_hex(bin), "utf-8")
    return x

def hex2int(hex):
    x = int('0x' + hex, 16)
    return x

def get_hex_tens_place(num):
    x = int((num / 0x10) % 0x10) * 0x10 + int((num / 1) % 0x10)
    return x

def get_le_int16(be):
    return struct.unpack("<H", be)[0]

proman_file_path = sys.argv[1]
raw_file_path = sys.argv[2]
nanddisk_path = sys.argv[3]

readable_block_addr = {}

try:
    with open("sp_.bin", 'wb') as sp:
        with open(proman_file_path, 'rb') as proman_file:
            promanbin = proman_file.read()
            proman_file.close()
            with open(raw_file_path, 'wb') as raw_file:
                for x in range(0, len(promanbin), 0x840):
                    pbuffer = promanbin[x:x+0x840]
                    page_a = pbuffer[:0x400]
                    page_b = pbuffer[0x415:0x800]
                    page_c = pbuffer[0x816:0x82B]

                    sparea_b = pbuffer[0x800:0x816]

                    if sparea_b[0:4] == b'\xFF\x42\x00\x00' or (sparea_b[0:4] == b'\xFF\x41\xFF\xFF' and sparea_b[6:8] == b'\x00\x00'):
                        x_addr = get_le_int16(sparea_b[4:6])
                        if x_addr in readable_block_addr:
                            cur_wl_version =  get_le_int16(sparea_b[6:8])
                            if readable_block_addr[x_addr][1] > cur_wl_version:
                                readable_block_addr[x_addr] = [int(x / 0x840 * 0x800), get_le_int16(sparea_b[6:8])]
                        else:
                            readable_block_addr[x_addr] = [int(x / 0x840 * 0x800), get_le_int16(sparea_b[6:8])]

                    pbuffer = page_a + page_b + page_c
                    raw_file.write(pbuffer)
                    sp.write(sparea_b[:10])
            raw_file.close()
    sp.close()
except Exception as e:
    print(e)

readable_block_addr_sorted = sorted(readable_block_addr.items(),key=lambda x:x[0])

with open(raw_file_path, 'rb') as raw_file:
    rawbin = raw_file.read()
    raw_file.close()
    cur_index = -1
    with open(nanddisk_path, 'wb') as nand_file:
        for (k,v) in readable_block_addr_sorted:
            if k<0xfff:
                print("fix block {:x}, off {:x}".format(k, v[0]))
                # print(hex(k))
                skip = k-cur_index
                if (skip) == 1:
                    nand_file.write(rawbin[v[0]:v[0]+0x20000])
                cur_index = cur_index + skip
        nand_file.close()

Ultimately, I successfully reconstructed the firmware. Given the bit-flip characteristics of NAND flash, I applied Hamming ECC for error correction. Since the system had undergone many reboot cycles, some data remained inconsistent, resulting in the significant differences shown in the comparison below.

fixed_diff

The file sizes and types were correct.

fdisk

The size of the reconstructed firmware matched the 496.6MiB reported by the live system.

nand_info

Upon mounting the second partition, the directory structure was displayed correctly.

fixed

However, some files were likely corrupted due to uncorrectable bit flips. Due to time constraints, I did not pursue further analysis.

Firmware Extraction SeriesNANDFTLOOBFlash
Table of Contents

Firmware Extraction Series - SATA HDD Unlock

Visteon Firmware Repacking