第二章 嵌入式系统基础:MCU与SoC架构、总线协议与存储器映射

做固件逆向,说白了就是跟芯片的“五脏六腑”打交道。你得先搞清楚它长什么样,怎么说话,数据存在哪。这一章,我把这些年摸过的芯片底层的那些事儿,给你捋一遍。

2.1 MCU vs SoC:你面对的到底是哪种芯片?

拿到一块板子,第一件事就是看主芯片。是MCU还是SoC?这决定了你后续的分析思路。我个人习惯,先看封装和外围电路——MCU通常引脚少,外围干净;SoC往往带着DDR颗粒和PMIC,板子复杂得多。

对比维度 MCU (微控制器) SoC (片上系统)
典型代表 STM32, ESP32, NXP LPC Allwinner, Rockchip, Qualcomm
存储架构 内部Flash + SRAM,通常<2MB 外部DDR + eMMC/NAND,可达GB级
运行频率 几十MHz ~ 几百MHz 几百MHz ~ 数GHz
操作系统 裸机或RTOS (FreeRTOS, uC/OS) Linux, Android, 或RT-Linux
逆向关注点 固件直接读Flash,分析中断向量表 需从eMMC/NAND提取文件系统,分析Bootloader
我的经验: 遇到MCU固件,我习惯先找它的中断向量表。复位向量指向哪,代码就从哪开始跑。SoC则复杂得多——你得先找到SPL(Secondary Program Loader),它才是真正的“第一把钥匙”。

2.2 常见总线协议:芯片间的“对话语言”

芯片内部和外设之间怎么通信?靠总线。逆向分析时,你经常需要监听这些总线上的数据。我见过太多人,拿着逻辑分析仪却不知道抓什么信号。下面这三种,你必须烂熟于心。

2.2.1 UART:最“亲民”的调试口

UART是异步串行通信。说白了,就两根线——TX和RX。没有时钟线,靠约定好的波特率来同步。为什么说它亲民?因为几乎所有芯片都会留UART作为调试口,而且很多固件会通过它打印启动信息。

  • 关键参数: 波特率(9600, 115200最常见)、数据位(8位)、停止位(1位)、校验位(无)。
  • 逆向技巧: 用逻辑分析仪抓TX脚,看波形宽度算波特率。我有个笨办法——先设成115200,如果乱码,再试57600、38400...总能蒙对。
  • 避坑指南: 我曾经遇到过一块板子,UART的TX和RX在PCB上标反了。折腾了半天,最后发现是硬件设计的问题。所以,别完全相信丝印,用万用表量一下电平最靠谱。
// 典型的UART初始化代码(伪代码)
void uart_init(uint32_t baudrate) {
    // 设置波特率寄存器
    UART->BRR = SYSTEM_CLOCK / baudrate;
    // 使能发送和接收
    UART->CR1 |= (1 << 3) | (1 << 2); // TE, RE
}

2.2.2 I2C:多设备“共享一条线”

I2C只有两根线:SCL(时钟)和SDA(数据)。所有设备都挂在这两根线上,靠地址区分。速度不快(标准模式100kHz,快速模式400kHz),但胜在简单,常用于配置传感器或读取EEPROM。

  • 通信流程: 起始条件 → 发送设备地址+读写位 → 应答 → 数据字节 → 应答 → ... → 停止条件。
  • 逆向关注点: 抓取I2C总线,看主设备在访问哪些从设备地址。这能帮你快速定位外设型号和配置寄存器。
注意: I2C的地址是7位或10位。7位地址左移1位,再加上读写位(0为写,1为读),组成第一个字节。很多新手在这里搞混,导致分析出来的地址不对。

2.2.3 SPI:高速“全双工”通道

SPI比I2C快得多,而且支持全双工——可以同时收发数据。它需要4根线:SCLK(时钟)、MOSI(主出从入)、MISO(主入从出)、CS(片选)。每个从设备独占一根CS线。

特性 I2C SPI
线数 2 (SCL, SDA) 4+ (SCLK, MOSI, MISO, CS)
速度 最高几MHz 几十MHz甚至更高
通信方式 半双工 全双工
设备寻址 软件地址 硬件片选
典型用途 EEPROM, 传感器, RTC Flash, LCD, SD卡, ADC

嗯,这里要注意。SPI的时序模式(CPOL和CPHA)有4种组合。抓波形时,如果数据全是乱的,八成是模式没选对。我一般会先试模式0(CPOL=0, CPHA=0),不行再换模式3。

2.3 外设与存储器映射:芯片的“地址地图”

芯片为什么能控制LED、读取按键、发送数据?因为它把外设的寄存器映射到了特定的内存地址上。你往那个地址写数据,就等于在控制硬件。这张“地址地图”,就是固件逆向的导航图。

2.3.1 存储器映射结构

以经典的ARM Cortex-M3为例,它的4GB地址空间被划分成几个大区:

  • 0x00000000 - 0x1FFFFFFF: 代码区(Flash),通常从这里启动。
  • 0x20000000 - 0x3FFFFFFF: SRAM区,运行时数据存放处。
  • 0x40000000 - 0x5FFFFFFF: 外设区,所有外设寄存器都在这里。
  • 0xE0000000 - 0xE00FFFFF: 系统控制区(NVIC, SysTick等)。
核心思路: 逆向时,如果你在反汇编代码中看到类似 0x4000xxxx 的地址,基本可以断定它在操作外设寄存器。查一下芯片手册,就能知道它在干什么。

2.3.2 外设寄存器操作实例

举个例子。控制一个GPIO引脚输出高电平,通常需要操作三个寄存器:时钟使能、模式配置、输出数据。

// 以STM32F103为例,点亮PC13引脚上的LED
// 1. 使能GPIOC时钟 (RCC_APB2ENR 地址: 0x40021018)
*(volatile uint32_t*)0x40021018 |= (1 << 4);

// 2. 配置PC13为推挽输出 (GPIOC_CRH 地址: 0x40011004)
//    CRH寄存器控制高8位引脚(Pin8~15),PC13对应第20-23位
*(volatile uint32_t*)0x40011004 &= ~(0xF << 20); // 先清零
*(volatile uint32_t*)0x40011004 |= (0x2 << 20);  // 设置为推挽输出,50MHz

// 3. 设置PC13输出低电平 (GPIOC_ODR 地址: 0x4001100C)
//    注意:很多开发板LED是低电平点亮
*(volatile uint32_t*)0x4001100C &= ~(1 << 13);

你看,就这么几个地址操作。逆向时,如果你在固件里看到类似的地址序列,基本就能还原出这段代码的功能。

2.4 知识体系总览:一张图看懂本章

下面这张图,把MCU/SoC架构、总线协议和存储器映射的关系串起来了。你想想看,固件逆向时,我们就是在这些层次之间来回穿梭。

嵌入式系统固件逆向知识体系(第2章) MCU 微控制器 SoC 片上系统 芯片内部/外部总线协议 UART (异步串行) I2C (同步半双工) SPI (同步全双工) 存储器映射 (Memory Map) Flash (代码区) SRAM (数据区) 外设寄存器区 系统控制区 逆向目标:提取固件 → 分析代码 → 定位漏洞 核心逻辑:从芯片类型出发,通过总线协议与外设交互,最终落实到存储器地址的操作
我的习惯: 每次拿到一个新固件,我会先建一个“地址字典”。把反汇编中出现的所有外设地址都列出来,然后对照芯片手册标注功能。这个字典,就是后续漏洞挖掘的“藏宝图”。

好了,这一章的内容就这些。MCU和SoC的区别、三大总线协议、存储器映射——这些是嵌入式逆向的“地基”。地基打不牢,后面分析漏洞时很容易跑偏。记住,多看芯片手册,多动手抓波形,比什么都强。