第二章 嵌入式系统基础: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 |
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总线,看主设备在访问哪些从设备地址。这能帮你快速定位外设型号和配置寄存器。
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架构、总线协议和存储器映射的关系串起来了。你想想看,固件逆向时,我们就是在这些层次之间来回穿梭。
好了,这一章的内容就这些。MCU和SoC的区别、三大总线协议、存储器映射——这些是嵌入式逆向的“地基”。地基打不牢,后面分析漏洞时很容易跑偏。记住,多看芯片手册,多动手抓波形,比什么都强。