第4章 固件加载与初步分析:把nRF52请进反汇编工具
拿到一份nRF52的固件,第一件事是什么?不是急着看代码,而是把它正确地加载到反汇编工具里。这一步要是错了,后面全是白费功夫。我见过不少新手,固件加载完发现反汇编出来的指令全是乱码,还以为是固件加密了——其实就是架构选错了。
这一章,我带你走一遍完整的加载流程。咱们用Ghidra和IDA Pro都演示一下,毕竟这两个工具在实际项目中都会用到。
4.1 选择正确的处理器架构
nRF52系列用的是ARM Cortex-M4内核,支持Thumb-2指令集。这里有个坑:Cortex-M4只支持Thumb模式,不支持ARM模式。你加载固件时如果选了ARM模式,反汇编出来的代码全是错的。
关键点:nRF52832、nRF52840、nRF52810等所有nRF52系列芯片,处理器架构统一选择ARM Cortex-M4,指令集选Thumb-2。
我个人习惯在Ghidra里这样配置:
Language: ARM
Variant: Cortex-M4
Endian: Little
Instruction Set: Thumb-2
在IDA Pro里,新建工程时选择:
Processor type: ARM
CPU: ARMv7-M (Cortex-M4)
Instruction set: Thumb-2
为什么一定要选Little Endian?nRF52的Flash和RAM都是小端模式。你想想看,如果选了大端,读出来的32位数据高低字节是反的,那还分析什么?
4.2 内存映射配置:让工具认识芯片的“地图”
固件不是凭空运行的,它依赖特定的内存布局。nRF52的内存映射是固定的,我们需要告诉反汇编工具:Flash在哪、RAM在哪、外设寄存器在哪。
先看一张我手绘的内存映射图,这样你脑子里有个整体印象:
4.3 在Ghidra中配置内存映射
Ghidra加载固件后,默认不会自动识别nRF52的内存布局。你需要手动添加内存块。我一般按这个步骤来:
- 打开Memory Map面板:Window → Memory Map,或者按快捷键Ctrl+Shift+M。
- 删除默认块:Ghidra会把整个固件当成一个连续块,先删掉它。
- 添加Flash块:点击Add Block,设置如下:
| 参数 | 值 |
|---|---|
| Name | FLASH |
| Start Address | 0x00000000 |
| Length | 0x80000 (512KB,以nRF52840为例) |
| Permissions | Read, Execute |
| Type | DEFAULT |
- 添加RAM块:
| 参数 | 值 |
|---|---|
| Name | RAM |
| Start Address | 0x20000000 |
| Length | 0x40000 (256KB) |
| Permissions | Read, Write, Execute |
| Type | RAM |
- 添加外设寄存器区域(可选,但强烈建议):
| 参数 | 值 |
|---|---|
| Name | PERIPHERALS |
| Start Address | 0x40000000 |
| Length | 0x100000 (1MB) |
| Permissions | Read, Write |
| Type | OTHER |
我的经验:外设寄存器区域建议映射为Read-Write,但不要勾Execute。有些固件会通过函数指针跳转到外设地址,如果勾了Execute,Ghidra会尝试反汇编那些寄存器地址,产生大量垃圾代码。
4.4 在IDA Pro中配置内存映射
IDA Pro的操作稍微不同。加载固件时,IDA会弹出一个对话框让你设置ROM和RAM的起始地址和大小。我通常这样填:
ROM start address: 0x00000000
ROM size: 0x80000
RAM start address: 0x20000000
RAM size: 0x40000
如果加载完成后发现还需要调整,可以进Edit → Segments → Rebase Program来修改。不过我个人建议一开始就填对,后面改来改去容易出问题。
IDA Pro还有一个好处:它内置了ARM Cortex-M的外设寄存器描述文件(SVD文件)。你可以通过File → Load File → Load SVD File来加载Nordic官方提供的nRF52840.svd。这样反汇编时,外设寄存器地址会自动显示为寄存器名称,比如0x40000500会显示为UART0->TXD,分析起来方便很多。
注意:如果你用的是破解版IDA Pro,SVD加载功能可能被阉割。我遇到过这种情况,最后只能手动给寄存器地址加注释。嗯,有条件的话还是支持正版吧。
4.5 固件入口点分析:找到程序从哪里开始跑
固件加载完了,内存映射也配好了,接下来最关键的一步:找到入口点。
Cortex-M4的启动流程和ARM7、ARM9不一样。它不是从0x00000000开始执行第一条指令,而是从0x00000000读取栈指针(MSP),从0x00000004读取复位向量(Reset_Handler)。说白了,真正的入口点在向量表的第二个字里。
我一般这样找入口点:
- 跳到地址0x00000000,看到的是栈指针的初始值,比如0x20004000。
- 跳到地址0x00000004,看到的是复位向量的地址,比如0x00000123。
- 注意:这个地址的最低位必须是1(表示Thumb模式)。如果看到0x00000122,说明固件有问题或者加载错了。
- 跳转到复位向量地址(去掉最低位的1,即0x00000122 → 0x00000122 & 0xFFFFFFFE = 0x00000122),这里就是固件的入口点。
举个例子,我手头有一个nRF52840的固件,加载后看到:
0x00000000: 0x20004000 (栈指针初始值)
0x00000004: 0x00000123 (复位向量,Thumb模式)
那么入口点就是0x00000122。跳过去一看,果然是复位处理函数的开头:
0x00000122: ldr r0, =0x40000400
0x00000124: ldr r1, [r0]
0x00000126: orr r1, r1, #1
0x00000128: str r1, [r0]
... (时钟初始化代码)
核心要点:入口点不是0x00000000,也不是0x00000004,而是向量表第二个字指向的地址。这个地址的最低位必须是1,否则固件无法启动。
4.6 验证加载是否正确
配置完成后,别急着开始分析。先做几个快速验证,确保工具认识你的固件:
- 检查向量表:0x00000000到0x00000040之间应该是连续的异常向量,每个都是合法的Thumb地址(最低位为1)。如果看到0xFFFFFFFF或者0x00000000,说明固件可能被截断了。
- 检查代码密度:Cortex-M4的Thumb-2指令是16位或32位混合的。随便看几个地址,应该能看到合理的指令序列,而不是大片的0x00或0xFF。
- 检查字符串:在固件里搜索常见的ASCII字符串,比如"nRF52"、"SoftDevice"、"Nordic"等。如果能找到,说明加载基本正确。
我曾经遇到过一个固件,加载后向量表全是0xFFFFFFFF。折腾了半天才发现是固件文件本身被加密了,需要先解密才能分析。嗯,那是另一个故事了。
4.7 本章小结
固件加载这一步,说白了就是让反汇编工具认识你的芯片。架构选Cortex-M4 Thumb-2,Flash从0x00000000开始,RAM从0x20000000开始,入口点从向量表里找。这三件事做对了,后面的分析才能顺利进行。