逆向分析STM32程序?从Keil生成的.asm和反汇编代码说起(以ARMCC为例)

张开发
2026/6/7 11:10:26 15 分钟阅读
逆向分析STM32程序?从Keil生成的.asm和反汇编代码说起(以ARMCC为例)
逆向工程实战从Keil生成的.asm与反汇编代码透视STM32程序逻辑当我们需要诊断一段STM32程序为何在特定条件下崩溃或是试图理解第三方固件中某个关键算法的实现细节时逆向工程技能就显得尤为重要。不同于常规的从源码到二进制开发流程逆向分析要求我们能够从编译后的机器代码回溯程序逻辑。本文将聚焦Keil MDK环境下生成的.asm列表文件和反汇编代码揭示它们如何成为我们窥探程序内部运作的窗口。1. 理解编译器输出的中间产物在Keil MDK的编译过程中ARMCC编译器会生成多种中间文件其中.asm列表文件是最接近源代码的汇编表示。要启用这个功能需要在Options for Target → Output选项卡中勾选Browse Information和Assembly Listing同时指定生成的文件路径。与最终烧录到芯片的二进制文件不同.asm文件保留了符号名称和源代码注释。例如下面这段代码展示了C语言与生成汇编的对应关系// 原始C代码 int calculate_checksum(uint8_t *data, uint32_t len) { uint32_t sum 0; for(uint32_t i0; ilen; i) { sum data[i]; } return (sum 0xFFFF); }对应的.asm文件片段可能如下calculate_checksum PROC PUSH {r4,r5,lr} MOVS r4,#0 ; sum 0 MOVS r5,#0 ; i 0 |L0.4| CMP r5,r1 ; 比较i与len BCS |L0.20| ; 循环结束跳转 LDRB r3,[r0,r5] ; 加载data[i] ADDS r4,r4,r3 ; sum data[i] ADDS r5,r5,#1 ; i B |L0.4| ; 继续循环 |L0.20| UXTH r0,r4 ; 截断为16位 POP {r4,r5,pc} ENDP这种一一对应的关系使得.asm文件成为理解编译器行为的第一手资料。值得注意的是.asm文件展示的是编译器最初生成的汇编代码而实际烧录的二进制可能还会经过链接器的进一步处理。2. 反汇编窗口的深度解析当我们需要分析没有源代码的二进制时Keil的Debug模式下的反汇编窗口就成为主要工具。与.asm文件不同反汇编展示的是最终加载到内存中的机器码对应的汇编指令。理解这两者的差异对准确分析至关重要。反汇编代码通常缺少符号信息但通过一些特征可以识别常见结构函数序言/尾声PUSH {registers}和POP {registers}通常标志函数开始和结束循环结构B{condition}指令配合CMP往往指示循环控制内存访问LDR/STR系列指令暴露了数据访问模式以下是一个实际案例中的反汇编片段我们尝试还原其逻辑00000200: B510 PUSH {r4,lr} 00000202: 1C04 ADDS r4,r0,#0 00000204: 2000 MOVS r0,#0x00 00000206: 2200 MOVS r2,#0x00 |L0.8| 00000208: 4291 CMP r1,r2 0000020A: D904 BLS |L0.18| 0000020C: 5D13 LDRB r3,[r2,r4] 0000020E: 18C0 ADDS r0,r0,r3 00000210: 3201 ADDS r2,#0x01 00000212: E7F9 B |L0.8| |L0.18| 00000214: B280 UXTH r0,r0 00000216: BD10 POP {r4,pc}通过分析寄存器使用模式r0作为累加器r2作为计数器和分支结构我们可以推断这很可能是一个校验和计算函数与前面.asm示例功能相似但寄存器分配不同。3. 编译器优化行为的逆向识别现代编译器会对代码进行各种优化这给逆向工程带来了挑战也提供了线索。在ARMCC中常见的优化级别包括-O0无优化、-O1基本优化、-O2较强优化和-O3激进优化。优化特征对比表优化级别典型特征逆向分析难度-O0严格保持源码顺序大量栈操作低-O1移除冗余操作简单内联中-O2循环展开指令调度高-O3激进内联自动向量化很高例如同样的循环结构在不同优化级别下的表现// 原始代码 for(int i0; i4; i) { buffer[i] 0; }-O0级别编译MOVS r1,#0x00 STR r1,[r0,#0x00] MOVS r1,#0x00 STR r1,[r0,#0x04] MOVS r1,#0x00 STR r1,[r0,#0x08] MOVS r1,#0x00 STR r1,[r0,#0x0C]-O2级别编译可能变为MOV r1,#0x00 STM r0!,{r1,r1,r1,r1}识别这些模式有助于我们判断编译器的优化策略进而更准确地还原原始意图。一个实用的技巧是关注内存访问模式——激进优化通常会合并连续的内存操作。4. 实战案例固件漏洞分析假设我们在分析一个物联网设备的固件时发现其网络协议处理模块存在异常行为。通过反汇编我们定位到以下可疑代码段0000A310: B570 PUSH {r4-r6,lr} 0000A312: 0005 MOVS r5,r0 ; r5 输入缓冲区 0000A314: 2400 MOVS r4,#0x00 ; 初始化计数器 |L0.16| 0000A316: 7828 LDRB r0,[r5,#0x00] ; 读取输入字节 0000A318: 2800 CMP r0,#0x00 ; 检查NULL终止符 0000A31A: D007 BEQ |L0.32| 0000A31C: 1C60 ADDS r0,r4,#1 ; 计数器1 0000A31E: B2C4 UXTB r4,r0 ; 限制为8位 0000A320: 1C68 ADDS r0,r5,#1 ; 指针1 0000A322: 1C05 MOVS r5,r0 0000A324: E7F6 B |L0.16| ; 继续循环 |L0.32| 0000A326: 0020 MOVS r0,r4 ; 返回长度 0000A328: BD70 POP {r4-r6,pc}这段代码看似是一个简单的字符串长度计算函数但存在严重问题它没有检查输入长度仅通过NULL终止符判断结束。如果攻击者提供不带NULL字节的长数据计数器会回绕UXTB限制为8位导致缓冲区溢出。通过这个案例可以看出逆向分析不仅能帮助我们理解程序行为还能发现潜在的安全隐患。在实际操作中建议对关键函数绘制控制流图标记所有外部输入点特别注意循环边界和内存操作使用交叉引用查找函数调用关系5. 高效逆向的工作流程建议建立系统化的逆向工作流程可以显著提高效率。以下是我在实际项目中总结的实用步骤工具准备阶段Keil MDK用于基础反汇编IDA Pro或Ghidra高级静态分析J-Link或ST-Link硬件调试Python脚本自动化分析典型分析流程提取固件二进制通过SWD接口或OTA包使用fromelf工具生成初步反汇编fromelf --text -c -d --outputdisasm.txt firmware.axf在IDA中加载并识别关键函数通过交叉引用追踪数据流对可疑代码段进行动态调试验证寄存器使用速查表寄存器常规用途调用约定r0-r3参数传递/临时调用者保存r4-r8变量保存被调用者保存r9平台相关可变r10栈帧指针被调用者保存r11帧指针可选r12临时调用者保存r13栈指针特殊r14链接寄存器特殊r15程序计数器特殊掌握这些约定对快速理解函数调用关系至关重要。例如看到PUSH {r4-r6,lr}就能推断这是一个非叶函数因为它需要保存链接寄存器。逆向工程既是科学也是艺术需要结合技术知识和经验直觉。当面对一段晦涩的反汇编代码时不妨尝试先识别其结构模式如循环、条件分支再分析数据流向最后推测其高级语义。随着实践积累这种从下到上的分析过程会变得越来越自然。

更多文章