嵌入式系统基础:MCU与SoC架构、常见外设与启动流程
做固件逆向,第一步得搞清楚你面对的是什么硬件。我见过不少新手,拿到一个二进制文件就急着反汇编,结果连芯片型号都没看——这就像修车不看发动机型号,直接拆螺丝,多半要翻车。
嵌入式系统里,最核心的两个概念就是MCU和SoC。很多人觉得它们差不多,其实差别挺大。我简单说说我的理解。
MCU vs SoC:到底有啥区别?
MCU(微控制器),说白了就是一块芯片上集成了CPU、内存、外设接口。它是个完整的微型计算机系统。你想想看,一个智能手环里的主控芯片,通常就是MCU。它资源有限,但功耗极低,实时性好。
SoC(片上系统),则更复杂。它除了CPU核心,还可能集成GPU、DSP、NPU、视频编解码器等等。手机里的骁龙芯片就是典型的SoC。SoC的启动流程比MCU复杂得多,往往有多个启动阶段。
我个人习惯这样区分:MCU跑裸机或RTOS,SoC跑Linux或Android。当然这不是绝对的,但八九不离十。
核心要点:逆向MCU固件,你主要关注的是寄存器、外设地址、中断向量表。逆向SoC固件,你还要关注BootROM、引导加载程序(如U-Boot)、设备树等。
常见外设:UART / I2C / SPI
这三个接口,是嵌入式系统里最常用的通信方式。做逆向时,识别它们能帮你快速定位关键代码。
UART(通用异步收发器)
UART是最简单的串行通信接口。两根线:TX(发送)、RX(接收)。异步,意味着没有时钟线,双方得约定好波特率。
我在项目中遇到过,很多IoT设备出厂时,UART口是默认开启的。如果你能找到板子上的UART测试点,用逻辑分析仪抓一下,往往能直接拿到调试信息,甚至进入shell。嗯,这里要注意:有些厂商会故意把UART引脚复用成GPIO,或者干脆在BootROM里禁用掉。
逆向小技巧:在固件二进制中搜索连续的0x55或0xAA字节序列,这通常是UART的空闲或同步字节。另外,中断向量表中UART中断处理函数的地址,也能帮你定位UART驱动代码。
I2C(Inter-Integrated Circuit)
I2C用两根线:SCL(时钟)、SDA(数据)。它支持多主多从,每个设备有唯一的7位或10位地址。速度不快(标准模式100kHz,快速模式400kHz),但胜在简单,常用于连接传感器、EEPROM、RTC等。
我记得有一次逆向一个智能门锁,发现它用I2C连接了一个触摸芯片。通过分析I2C读写函数,我找到了触摸数据的处理逻辑,进而还原了密码验证的流程。说白了,I2C的驱动代码通常就是几个固定的模式:起始信号、发送地址、读写数据、停止信号。
SPI(串行外设接口)
SPI比I2C快得多,全双工,四根线:SCK(时钟)、MOSI(主出从入)、MISO(主入从出)、CS(片选)。它常用于Flash、SD卡、LCD显示屏等高速设备。
做固件逆向时,SPI Flash尤其重要。因为很多MCU的固件就存在外部SPI Flash里。你如果能从板子上把Flash吹下来,用编程器读出内容,那整个固件就到手了。我曾经用这个方法,从一个路由器里提取出了完整的Linux内核和文件系统。
| 接口 | 线数 | 速度 | 典型用途 | 逆向关注点 |
|---|---|---|---|---|
| UART | 2 | 慢(115200bps常见) | 调试、日志、控制台 | 波特率、中断处理、字符串输出 |
| I2C | 2 | 中(100-400kHz) | 传感器、EEPROM、RTC | 设备地址、读写时序、寄存器配置 |
| SPI | 4+ | 快(数十MHz) | Flash、SD卡、LCD | 片选信号、DMA传输、Flash命令集 |
存储器映射与启动流程
这是嵌入式逆向的基石。不理解存储器映射,你连代码在哪儿跑的都搞不清楚。
存储器映射
每个MCU或SoC,都会把Flash、RAM、外设寄存器映射到统一的地址空间里。比如STM32F103,Flash从0x08000000开始,RAM从0x20000000开始,外设从0x40000000开始。
你拿到一个固件,第一件事就是查芯片手册,找到它的存储器映射图。然后根据固件大小和加载地址,判断它是从Flash直接运行,还是需要拷贝到RAM里运行。
注意:有些芯片支持“零等待”Flash访问,有些则不行。如果代码在Flash里运行太慢,启动代码会把它搬到RAM里。这种“搬运”逻辑,往往是逆向分析的关键入口点。
启动流程
MCU的启动流程相对简单。以ARM Cortex-M为例:
- 上电复位后,CPU从地址0x00000000读取栈指针(MSP)初始值。
- 从地址0x00000004读取复位向量,即第一条指令的地址。
- 跳转到复位向量执行,通常是启动代码(startup_xxx.s)。
- 启动代码初始化全局变量、设置时钟、初始化外设,最后跳转到main()。
SoC的启动流程就复杂多了。以全志V3s为例:
- 芯片上电,BootROM(固化在芯片内部)开始执行。
- BootROM从SD卡、NAND Flash或SPI Flash中读取SPL(Secondary Program Loader)。
- SPL初始化DRAM,然后从存储介质中加载U-Boot。
- U-Boot加载设备树(DTB)和Linux内核。
- Linux内核启动,挂载根文件系统。
你想想看,逆向SoC固件时,如果你能找到BootROM的代码(有些芯片会开放),那就能理解整个启动链的信任根。但大多数时候,BootROM是保密的,你只能从SPL或U-Boot入手。
我的经验:分析启动流程时,重点关注“从哪里加载”、“加载到哪个地址”、“跳转到哪里”。这三个问题搞清楚了,整个固件的脉络就清晰了。我曾经逆向过一个智能摄像头,发现它的U-Boot里有一段校验逻辑,专门检查内核镜像的签名。绕过这个校验,就能刷入自定义固件。
好了,这一章的内容就到这里。嵌入式系统的基础,说白了就是搞清楚芯片怎么启动、外设怎么通信、内存怎么布局。这些概念搞明白了,后面分析固件才能得心应手。
公众号:蓝海资料掘金营,微信deep3321