stm-re
stm32逆向入门
0x00 背景知识
针对SCTF2020的STM32门锁固件题目firmware.hex
。题目链接为:链接。题目相关信息:STM32F103C8T6 MCU密码锁,具有4个按键,分别为1、2、3、4,分别对应GPIO_PA1、GPIO_PA2、GPIO_PA3、GPIO_PA4。有两个flag。flag1:门锁密码; flag2:UART输出的信息。
intel hex文件格式
这是一个intel hex的格式。Intel Hex文件是遵循Intel Hex文件格式的ASCII文本文件。在Intel Hex文件的每一行中都包含了一个hex记录,这些记录是由一些代表机器语言代码和常量的16进制数据组成。
Intel Hex文件常用来传输要存储在ROM、EPROM或者Flash中的程序和数据。大部分的EPROM编程器都能使用Intel Hex文件。Intel Hex由任意数量的十六进制记录组成。每个记录包含6个域。
(1)ROM(Read-Only Memory)是只读存储器,它的内容在制造时被固定下来,无法被修改。ROM 通常用于存储程序代码、固件、启动代码和常量数据等不需要经常修改的信息。
(2)EPROM(Erasable Programmable Read-Only Memory)是可擦写可编程只读存储器,它的内容可以被擦除和重新编程。EPROM 需要使用紫外线擦除器擦除,因此擦除后再次编程需要重新安装到设备中。EPROM 通常用于开发过程中需要不断修改的程序代码。
(3)Flash 是一种非易失性的电子存储器,它的内容可以被擦除和重新编程,但不需要使用紫外线擦除器,而是通过电子擦除方式实现。Flash 通常用于存储程序代码和数据,它的可擦除性使得它比 ROM 更加灵活,同时也比 EPROM 更加方便。
在 STM32 开发中,Flash 是一个非常重要的存储器类型,它用于存储程序代码和数据。对于一些需要经常修改的程序代码,可以使用 Flash 存储器进行存储。此外,STM32 还具有一些专用的存储器,如EEPROM(Electrically Erasable Programmable Read-Only Memory)和OTP(One-Time Programmable)存储器等,它们用于存储一些关键的参数和配置信息,如设备 ID、序列号、加密密钥等。
记录按以下格式排列:
例如:
(1)Start Code(冒号): 每个 Intel HEX 记录都由冒号开头
(2)Byte count(本行数据长度):是数据长度域,它代表记录当中数据字节的数量
(3)Address(本行数据的起始地址):是地址域,它代表记录当中数据的起始地址
(4)Record type(数据类型): 是代表HEX记录类型的域,它可能是以下数据当中的一个:
00-数据记录
01-文件结束记录
02-扩展段地址记录
03-开始段地址记录
04-扩展线性地址记录
05-开始线性地址记录
(5)Data(数据): 是数据域,一个记录可以有许多数据字节。记录当中数据字节的数量必须和数据长度域中指定的数字相符
(6)Checksum(校验码): 是校验和域,它表示这个记录的校验和.校验和的计算是通过将记录当中所有十六进制编码数字对
的值相加,以256为模进行补足
例如上述题目,其内容如下:
1 | :02 0000 04 0800 F2 |
上述代码解释如下:
(1)第一行记录了扩展线性地址,其实就是程序的加载基址:0x08000000
(2)倒数第二行记录了程序入口地址:0x080000ED
(3)最后一行标志文件结束
(4)其余行全部是文件数据
芯片信息查找
手册更是STM32开发者必不可少的法宝。查找手册的网站(必须知道芯片型号):https://www.alldatasheet.com/
。然后就找到了对应芯片的信息:https://www.st.com/resource/en/datasheet/stm32f103c8.pdf
。
从内存映射可以得到此代码的信息:
- 0x08000000-0x0801FFFF: Flash 128k,用于来存储程序代码,不用把程序拷贝到RAM中,而直接在Flash中执行,这种技术叫XIP
- 0x20000000-0x20004FFF: SRAM 20k,用于程序运算时存放变量
- 0x40000000-0x40023FFF: Peripherals 144k,外设寄存器的映射地址,通过读写这些内存地址实现对外围设备的控制
在之前的hex文件中,我们知道程序的加载基址是0x08000000
,也是Flash的起始地址,所以这里直接就是从Flash中执行程序。
补充:NOR Flash与NAND Flash
(1)NOR Flash是一种串行存储器,它的读取和写入操作可以同时进行。NOR Flash的存储单元通常是按字节进行编址的,因此适合于存储代码和执行指令等需要随机访问的应用场景。NOR Flash的读取速度比较快,但写入速度和存储密度相对较低。
(2)NAND Flash是一种并行存储器,它的读取和写入操作不能同时进行。NAND Flash的存储单元通常是按页或块进行编址的,因此适合于存储大量数据的应用场景,如图像、视频和音频等。NAND Flash的存储密度比较高,但读取速度和可靠性相对较低。
IDA配置
由上面分析可知,内存分为Flash
、SRAM
、Peripherals
。Flash段中的程序除了包括代码,还包括中断向量表。用IDA加载这个hex文件,必须还要进行相关的配置。选用ida7.7
32位,由于CPU内核系列是Cortex M3,指令集是ARMv7-M,因此可以进行配置。
0x01 题目分析
入口地址
上面分析过,入口地址为0x080000ED
(从hex文件中分析出来的)。那如果没有这个信息怎么办?利用中断向量表来找到复位时的中断处理函数,跟着函数找就可以找到入口地址。
之前分析的,代码基地址为0x08000000
(Flash起始地址),跟踪此地址,得到:
在ARM里,如果跳转到一个奇数的地址上,则是切换处理器为THUMB模式(指令的长度为2个字节,原来是4个字节)。nullsub什么都没有做。之后跳入start函数,然后从start,往后跟一会就找到了main函数(sub_8000428
),如下所示:
补充:BLX与BX指令
(1)BLX(Branch with Link and Exchange)和BX(Branch and Exchange)是ARM处理器中的两种跳转指令,用于在不同的指令集(ARM指令集和Thumb指令集)之间进行跳转。
(2)BX指令用于在ARM指令集和Thumb指令集之间进行跳转。当CPU处于ARM状态下时,BX指令可以跳转到Thumb状态的代码段,而当CPU处于Thumb状态下时,BX指令则可以跳转到ARM状态的代码段。
(3)BLX指令除了可以实现BX指令的功能外,还可以将跳转地址存入链接寄存器(Link Register,LR)中,从而实现函数调用的功能,即BLX指令实现了跳转并链接(Branch with Link and Exchange)的功能。
补充:LR寄存器
(1)Link Register是ARM Cortex-M系列处理器中的一种寄存器,它主要用于存储函数返回地址。当一个函数被调用时,LR寄存器会自动保存调用该函数的指令的地址,当函数执行完毕时,程序会从LR寄存器中读取该地址,返回到调用该函数的指令处继续执行。
(2)在STM32的汇编指令中,LR寄存器通常使用LR或者R14表示。在函数调用时,使用汇编指令BL(Branch with Link)跳转到子函数中执行,此时LR寄存器会自动保存调用该函数的指令的地址。在子函数执行完毕返回时,使用汇编指令BX LR(Branch and Exchange)跳转到LR寄存器中保存的地址,返回到调用该函数的指令处继续执行。
接着分析,为什么上图会有很多标红?红色的位置其实就是IDA没有创建的内存段,因为加载程序hex的时候的地址以及程序大小只让IDA加载了0x8000000附近的内存,而我们之前获得SRAM以及Peripherals的地址信息都没有告诉IDA,所以接下来就创建内存段让这些红色消失。
segment建立
根据上面分析的SRAM与Peripherals的地址,可以创建新的段。SRAM创建如下:
修复中断向量表
与其说是修复中断向量表,不如说是:配置ida,让ida可以识别出中断向量表的数据结构。
程序入口之前都是中断向量表,即中断向量表的范围:0x08000000-0x080000EC
。
IDApython脚本如下(其实手动也可以):
1 | for i in range(0x8000000,0x80000eb,1): |
得到如下:
其中,0x800016D;0x80001AD;0x80001E5;0x800022D;0x8000149
对应的是有函数的,其对应的地址分别为:0800005C;08000060;08000064;08000068;08000078
。查询手册,对应的中断为(此图是copy的链接):
对这些地址create_function
创建函数。博客原话:分析这五个函数,对应到他们的功能,感觉前四个就是密码按键中断的对应的处理函数,最后一个DMA相关暂时不知道是干什么的。总之这一趟忙活下来,IDA基本就彻底看懂了这个题目的intel hex到底是个啥了。但是我们目前还不是很懂,接下来就是对着STM32的文档,研究程序是怎么用的外设寄存器,并分析程序函数并解题了。
0x03 解题
由于之前选过嵌入式这门课,所以有一点点小了解。之后,用MDK加载题目中的hex文件。步骤具体在《CTF特训营》P460-P465。
由于题目与GPIO_PA1、GPIO_PA2、GPIO_PA3、GPIO_PA4这几个引脚相关,首先先了解一下。而我们想让程序从USART1输出,因此也要用到PA9引脚。如下所示:
补充:引脚
(1)PA、PB、PC、PD等每组都是16个GPIO引脚
(2)PA、PB、PC、PD等每组都可能有引脚和其他功能复用
(3)通过设置每个引脚的配置寄存器来决定是否复用
我们需要设置PA1、PA2、PA3、PA4的输入,PA9设置复用为UART的输出。通过STM32F10xxx参考手册,可以配置程序中的寄存器名称。最终得到:
但是调试的时候一直卡在USART1_SEND
的循环中,搜索了一下,可能有三个原因:
(1)确保在程序入口点 main()
中正确地初始化了 USART1串口,并且初始化后USART1的状态为可用状态。
(2)确保在发送数据之前USART1接收缓冲区为空。可以通过读取USART1_SR寄存器的RXNE位来检查是否有未处理的数据,如果有,则需要先读取USART1_DR寄存器中的数据。
(3)如果USART1串口的波特率设置不正确,可能会导致通信错误,也会导致卡在 while ((USART1_SR & 0x80) == 0)
。请确保波特率设置正确。
排查了一下错误,发现配置时应该这样:
之后点击debug,然后load firmware.hex
,最后reset
就好啦。结果如下图所示:
参考链接:https://xuanxuanblingbling.github.io/iot/2020/07/08/stm32/
留言
- 文章链接: https://wd-2711.tech/
- 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-ND 4.0 许可协议。转载请注明出处!