3、固件获取与提取:从芯片读取固件的方法(JTAG/SWD、Bootloader、芯片解密)、固件文件格式(BIN/HEX/ELF)解析

固件提取是嵌入式安全评估与逆向工程的第一步,也是决定后续分析深度与成本的关键环节。在实际攻防场景中,目标固件往往被存储在MCU内部Flash或外部SPI NOR Flash中,而读取这些存储介质的方式直接决定了提取的完整性、速度与风险。本章将系统梳理三类主流固件读取路径——JTAG/SWD调试接口、Bootloader固件升级通道、芯片解密/物理攻击手段,并深入解析BIN、HEX、ELF三种固件文件格式的内部结构与转换逻辑,帮助学员建立从“物理芯片”到“二进制文件”的完整认知链路。

固件获取与提取路径总览 目标MCU/SoC 内部Flash / ROM 外部SPI NOR Flash OTP / eFuse区域 (固件存储介质) JTAG / SWD 调试接口 TDI/TDO/TCK/TMS 或 SWDIO/SWCLK Bootloader 固件升级通道 UART / USB / SPI / I2C / CAN 芯片解密 / 物理攻击 FIB / 激光 / 化学腐蚀 / 侧信道 原始固件 二进制镜像 (BIN/HEX) 格式解析 BIN HEX ELF 关键决策点 优先尝试JTAG/SWD → 其次Bootloader → 最后考虑芯片解密(成本/风险递增)

上图展示了固件提取的三条主要路径及其与目标芯片存储介质的关系。在实际项目中,选择哪条路径取决于芯片的调试接口是否锁定、Bootloader是否开放、以及安全防护等级。下面将逐一展开每种方法的技术细节与操作要点。

3.1 JTAG/SWD调试接口提取

核心原理

JTAG(Joint Test Action Group,IEEE 1149.1)和SWD(Serial Wire Debug)是嵌入式芯片最常用的调试接口。JTAG通过TDI(数据输入)、TDO(数据输出)、TCK(时钟)、TMS(模式选择)四根线实现边界扫描与调试访问;SWD则仅需SWDIO(数据)和SWCLK(时钟)两根线,是ARM Cortex-M系列的标准调试接口。调试接口的底层逻辑是:调试器通过访问芯片内部的Debug Access Port(DAP),进而控制AHB-AP(总线访问端口)直接读写Flash控制器寄存器,从而绕过CPU执行代码,直接读取Flash内容。

从安全视角看,JTAG/SWD接口的开放程度分为三个等级:完全开放(可直接读写Flash)、受限开放(仅允许调试寄存器,禁止访问Flash)、完全锁定(接口被禁用或需要密钥认证)。多数MCU通过烧写Option Byte或配置字来设置调试接口权限,例如STM32的RDP(Read Protection)级别。

操作流程与工具链

  1. 硬件连接:使用J-Link、ST-Link、OpenOCD+FT2232等调试器,连接目标板的调试接口引脚。注意目标板供电电压(1.8V/3.3V/5V)与调试器IO电平匹配。
  2. 识别芯片:通过调试器软件(如J-Flash、STM32CubeProgrammer)读取芯片ID,确认型号与Flash大小。
  3. 尝试连接:若芯片未锁定,可直接连接;若锁定,尝试使用unlock命令(如J-Link的unlock Cortex-A)或检查是否有后门。
  4. 读取Flash:设置起始地址(通常为0x08000000或0x00000000)和长度,执行读取并保存为BIN文件。
# OpenOCD 读取STM32F103 Flash示例
openocd -f interface/stlink.cfg -f target/stm32f1x.cfg
# 在OpenOCD telnet终端中执行:
> flash read_bank 0 firmware.bin 0x08000000 0x10000
> reset halt
> dump_image firmware.bin 0x08000000 0x10000

案例拆解:某智能门锁MCU固件提取

某智能门锁产品使用STM32L151芯片,安全评估时发现其RDP级别为Level 0(无保护)。我们使用J-Link连接SWD接口,通过J-Flash软件读取0x08000000起始的256KB Flash,耗时约30秒,成功获取完整固件。随后用binwalk分析固件,发现其中包含明文存储的WiFi密码和云平台。该案例说明,未启用读保护的芯片,固件提取成本极低,产品开发时应至少设置RDP Level 1。

风险提示:部分芯片在读取Flash时会触发硬件保护机制(如ECC校验失败自动擦除),导致固件永久丢失。建议在读取前先备份芯片的Option Bytes,并使用只读命令(如OpenOCD的flash read_bank)而非擦除命令。

常见误区与复盘

  • 误区1:认为JTAG/SWD接口被禁用就无法提取。实际上,部分芯片可通过电压毛刺(Glitch)或时钟毛刺绕过锁定。
  • 误区2:忽略调试接口的供电要求。调试器与目标板电平不匹配会导致通信失败,甚至损坏芯片。
  • 误区3:读取时未考虑Flash的字节序和地址映射。例如,部分芯片的Flash起始地址为0x00000000而非0x08000000,需查阅数据手册确认。
调试接口引脚数速度适用芯片安全风险
JTAG4-510-50MHzARM、MIPS、RISC-V等高(可完全控制)
SWD24-10MHzARM Cortex-M系列高(可完全控制)
cJTAG210MHz部分汽车MCU中(需兼容模式)

3.2 Bootloader固件升级通道提取

核心原理

Bootloader是芯片上电后最先执行的代码,负责初始化硬件并引导主应用程序。多数Bootloader支持固件升级功能,通过UART、USB、SPI、I2C或CAN等接口接收新固件并写入Flash。从安全角度看,Bootloader是一个“合法”的固件读取通道——如果Bootloader没有对读取操作进行身份验证或加密,攻击者可以伪装成升级主机,发送读取命令来导出Flash内容。

常见的Bootloader类型包括:STM32系统Bootloader(ROM中固化,支持USART/USB/CAN)、NXP的ISP(In-System Programming)、以及厂商自定义的OTA Bootloader。其中,ROM Bootloader通常无法被修改,但其命令集是公开的(如STM32的AN3155文档),可直接利用。

判断方法与操作流程

  1. 识别Bootloader入口:查阅芯片数据手册,确认Boot引脚电平设置(如STM32的BOOT0=1进入系统Bootloader)。
  2. 确定通信协议:根据芯片型号选择对应接口,如STM32F103使用USART1(PA9/PA10),波特率通常为115200或460800。
  3. 发送命令帧:以STM32为例,使用“0x7F”同步命令建立握手,然后发送读取命令(0x11)和起始地址/长度参数。
  4. 接收数据:Bootloader会分块返回Flash内容,每块通常为256字节,需按协议校验CRC。
# 使用Python脚本通过UART读取STM32 Flash
import serial
ser = serial.Serial('COM3', 115200, timeout=1)
ser.write(b'\x7F')  # 同步命令
resp = ser.read(1)
if resp == b'\x79':  # ACK
    ser.write(b'\x11\xFF\xFF')  # 读取命令 + 补码
    addr = 0x08000000
    data = b''
    for i in range(0, 0x10000, 256):
        cmd = bytes([addr >> 24 & 0xFF, addr >> 16 & 0xFF, addr >> 8 & 0xFF, addr & 0xFF])
        ser.write(cmd + bytes([~sum(cmd) & 0xFF]))
        block = ser.read(256)
        data += block
        addr += 256
    with open('firmware.bin', 'wb') as f:
        f.write(data)

案例拆解:某IoT模块Bootloader提取

某WiFi模块使用ESP8266,其ROM Bootloader支持UART下载模式。我们通过拉低GPIO0进入下载模式,使用esptool.py工具执行read_flash 0x00000 0x100000 firmware.bin,成功读取1MB固件。该固件中包含阿里云物联网平台的ProductKey和DeviceSecret,攻击者可利用这些凭证仿冒设备接入云端。此案例表明,Bootloader通道的开放程度直接决定固件暴露风险,厂商应在量产固件中禁用不必要的Bootloader命令。

经验提示:部分Bootloader支持“读保护”命令(如STM32的0x82),可临时禁止后续读取操作。在提取固件前,先发送该命令观察响应,判断Bootloader是否支持安全功能。

常见误区与总结

  • 误区1:认为Bootloader只能升级不能读取。实际上,多数Bootloader的“读取”命令与“写入”命令并存,只是文档中未强调。
  • 误区2:忽略Bootloader的版本差异。不同版本的Bootloader命令集可能不同,需通过芯片ID或Bootloader版本号适配。
  • 误区3:未考虑通信接口的电气特性。例如,UART的TX/RX电平需与芯片IO匹配,否则无法建立握手。

3.3 芯片解密与物理攻击手段

核心原理

当调试接口被锁定且Bootloader不提供读取功能时,攻击者只能诉诸物理攻击。芯片解密的核心思路是绕过芯片的物理防护层(如金属屏蔽层、氧化层、多晶硅层),直接探测或读取Flash存储单元的电荷状态。常见手段包括:聚焦离子束(FIB)——通过切割芯片表面走线,绕过读保护逻辑;激光攻击——使用激光照射特定晶体管,改变其逻辑状态;化学腐蚀——用酸液去除芯片封装,暴露裸片后用微探针台读取;侧信道攻击——通过功耗或电磁辐射分析,推断Flash内容。

芯片解密的成本与风险极高:FIB设备单次使用成本数千至数万美元,且可能损坏芯片;化学腐蚀需要专业实验室环境;侧信道攻击则需要大量样本和统计分析。因此,该方法通常仅用于高价值目标(如汽车ECU、金融安全芯片)。

操作流程与工具

  1. 芯片开盖:使用发烟硝酸或硫酸去除环氧树脂封装,暴露裸片。
  2. 去除钝化层:使用等离子刻蚀或反应离子刻蚀(RIE)去除表面SiO2/Si3N4钝化层。
  3. 定位Flash阵列:通过光学显微镜或扫描电子显微镜(SEM)识别Flash存储单元区域。
  4. 读取数据:使用FIB切割控制线或使用微探针台接触位线/字线,逐位读取电荷状态。
法律与安全警示:芯片解密可能侵犯知识产权,且涉及危险化学品和精密设备操作。本课程仅用于安全研究和合规测试,严禁用于非法用途。实际操作前需获得芯片所有者的书面授权。

案例拆解:某汽车ECU固件提取

某车型的发动机ECU使用英飞凌TC277芯片,其调试接口被永久锁定,Bootloader不开放。安全团队采用FIB方案:首先通过化学方法开盖,然后用FIB切割连接Flash控制器的保护逻辑走线,使读保护失效。随后使用JTAG调试器连接内部测试点,成功读取2MB固件。整个过程耗时3天,成本约2万美元。该固件中提取出发动机标定参数和排放控制算法,为后续漏洞分析提供了基础。

常见误区与复盘

  • 误区1:认为芯片解密是“万能钥匙”。实际上,现代芯片(如ARM TrustZone、NXP i.MX系列)采用多层防护,解密成功率低且成本高。
  • 误区2:忽略解密后的数据完整性。物理攻击可能损坏部分存储单元,导致固件数据不完整,需结合CRC校验和多次读取比对。
  • 误区3:未评估解密的经济性。对于低价值芯片,解密成本可能超过固件本身价值,应优先考虑其他提取路径。
攻击手段成本成功率风险适用场景
FIB切割高($10k+)中芯片损坏高价值汽车/工业芯片
激光攻击中($5k+)低-中逻辑扰动安全芯片
化学腐蚀+微探针中($3k+)低芯片报废老式MCU
侧信道分析低($1k+)低需大量样本对称加密固件

3.4 固件文件格式解析:BIN/HEX/ELF

核心原理与格式对比

提取或编译得到的固件文件通常有三种格式:BIN(Binary)——纯二进制数据,无任何附加信息,文件内容与Flash存储内容一一对应;HEX(Intel HEX)——文本格式,每行包含地址、数据长度、数据类型和校验和,适合烧录器逐行解析;ELF(Executable and Linkable Format)——包含代码段、数据段、符号表、调试信息等,是编译器的输出格式,需通过objcopy转换为BIN/HEX后才能烧录。

三种格式的核心区别在于:BIN是“裸数据”,HEX是“带地址的文本数据”,ELF是“带符号和段信息的结构化文件”。在固件逆向中,BIN最常用(可直接反汇编),HEX用于烧录验证,ELF用于调试和符号恢复。

格式解析方法

# 查看ELF文件信息
readelf -h firmware.elf
readelf -S firmware.elf
# 查看符号表
nm firmware.elf
# 将ELF转换为BIN
arm-none-eabi-objcopy -O binary firmware.elf firmware.bin
# 将BIN转换为HEX
arm-none-eabi-objcopy -I binary -O ihex firmware.bin firmware.hex
# 解析HEX文件(Python示例)
with open('firmware.hex', 'r') as f:
    for line in f:
        if line[0] == ':':
            byte_count = int(line[1:3], 16)
            address = int(line[3:7], 16)
            record_type = int(line[7:9], 16)
            data = bytes.fromhex(line[9:9+byte_count*2])
            print(f'Addr: 0x{address:04X}, Type: {record_type}, Data: {data.hex()}')

案例拆解:从ELF到BIN的固件分析流程

某智能摄像头固件以ELF格式发布,我们使用readelf发现其包含.text(代码段)、.rodata(只读数据)、.data(初始化数据)和.bss(未初始化数据)四个主要段。通过objcopy提取BIN后,使用Ghidra加载BIN文件,设置基地址为0x08000000(根据芯片Flash起始地址),成功反汇编出主逻辑。进一步分析发现,固件中硬编码了AES-128密钥,用于加密视频流。该案例说明,ELF格式的符号信息可大幅加速逆向分析,而BIN格式则更接近实际运行状态。

格式选择建议:在固件提取阶段,优先保存BIN格式(保留完整Flash内容);在分析阶段,若获得ELF文件,保留符号表可辅助定位关键函数;在烧录验证阶段,使用HEX格式便于按地址校验。

常见误区与总结

  • 误区1:混淆BIN和HEX的地址映射。BIN文件没有地址信息,需手动指定加载地址;HEX文件自带地址,但可能包含非连续区域。
  • 误区2:忽略ELF的段对齐。ELF段可能包含填充字节(Padding),直接转换BIN后会产生多余数据,需使用objcopy的--gap-fill参数处理。
  • 误区3:未检查HEX文件的校验和。HEX每行末尾的校验和用于检测传输错误,解析时需验证,否则可能得到错误数据。
格式结构地址信息符号信息典型用途
BIN纯二进制无无固件烧录、反汇编
HEX文本行有(每行)无烧录器、校验
ELF段+节有(段地址)有调试、符号恢复