嵌入式设备固件一般可以分为两类:
- 有文件系统(如路由器固件这种)
- 无文件系统(如STM32这种嵌入式设备固件,程序直接存放在flash中)
针对第一类固件,分析起来比较简单,直接binwalk分离。然后去逆向对应的服务对应的二进制程序即可,也不需要考虑程序的加载地址。
而对于第二类固件,往往就会比较复杂,需要分析程序的加载地址,也需要考虑外设的地址映射。
本文将从SCTF2020的题目Password Lock入手,分析STM32固件作为入门
题目信息
这是一个STM32F103C8T6 MCU密码锁, 它具有4个按键,分别为1, 2, 3, 4. 分别对应GPIO_PA1, GPIO_PA2, GPIO_PA3, GPIO_PA4.
- flag1格式为SCTF{正确的按键密码}
- 输入正确的密码, 它将通过串口(PA9–TX)发送flag2
确定固件加载地址
这里有两个方法可以确定,一个是直接分析Intel Hex文件,另一个是去看芯片对应的内存映射图(可以通过ALLDATASHEET.COM去搜索芯片信息)
分析Intel Hex文件
Intel Hex可以直接用记事本打开,我们以本题的第一行为例分析其数据格式
1 | :020000040800F2 |
- 第一个符合
:
代表没一行数据的起始,为start code - 之后的第一个byte
02
为Byte Count,代表data的长度 - 紧接着的两个字节
0000
为Address - 接下来的一个字节
04
为record type,总共有6个类型(00数据,01文件结尾,02拓展分段地址,03开始段地址,04拓展线性地址,05起始线性地址) - 接下来直到最后一个字节之前为数据data,
- 最后一个字节F2为校验和,校验和为所有字节之和的补码
这里主要record type这个字段需要注意
hex | type | Description | Example |
---|---|---|---|
00 | 数据 | 由byte count字段决定data字段的数据长度 | :0B0010006164647265737320676170A7 |
01 | 文件结尾 | 一般为hex文件最后一行,代表文件结束 | :00000001FF |
02 | 扩展分段地址 | byte count固定为02,起始地址为data段值乘以16 | :020000021200EA |
03 | 开始段地址 | 对于8086处理器指定起始执行地址 | :0400000300003800C1 |
04 | 扩展线性地址 | data段代表起始地址的高16位 | :020000040800F2 |
05 | 起始线性地址 | data表示大端的32位地址 | :04000005000000CD2A |
针对本题的起始数据为:020000040800F2
,可以很明显的看到类型为04,则可以计算出对应地址为0x08000000
查看内存映射
在上面的datasheet网站中可以搜索到:STM32F103C8T6 Datasheet本题对应的datasheet,查看memory mapping章节可以得到下图。
从图中也可以比较直观的找到flash段的地址在0x08000000,此外还可以看到一些外设的地址段PERIPHERALS,这也是后面分析时要考虑的。
IDA分析
将hex文件拖入IDA后,首先设置process type为小端,然后设置架构为arm-v7(这个可以在手册中查到),如图:
跟踪流程可以跟到sub_8000428
,基本可以确定其就是主函数main
,红色区域地址是因为IDA没有加载这个地址的段。
对照内存映射图,我们可以知道这些0x40000000的地址属于外设的地址映射,也就是对应了不同的针脚。可以在Edit -> Segment -> create segment,手动添加sram和peripherals段即可,再次F5即可看到区域变为正常黄色。
接下来就需要手动查阅手册,为每个地址改变量名为设备名称,方便后续分析,在我看来这也是最耗时最麻烦的一步。但其如果使用Ghidra和SVD-loader插件会快很多,可以直接自动识别更改,但我在尝试的时候没有成功,不知什么原因,悲 :(
改完差不多就是这个样子
1 | int __cdecl main(int argc, const char **argv, const char **envp) |
另外还要对中断向量表做处理,可以用如下IDA python脚本更改段为dword
1 | for i in range(0x8000000,0x80000eb,1):del_items(i) |
图里可以很明显的看出存在一些函数,查阅手册可以找到其为中断处理的EXTI函数
函数逻辑分析
这条也没什么捷径可走,需要一边查手册一边看mian函数内的每一步赋值和函数的意义,基本经过这个流程可以搞清楚GPIO、EXTI、USART这些作用之类的