一、概括/ B. W$ |: s; x+ q3 D0 K2 A7 _ 用的很久之前做的一个电风扇的课设来做实验。: ^% \) K7 f# W# Q4 ` ) G+ p' |9 t& q7 u) G3 m : O0 Q4 w X% K$ [( y 用到了外部中断,定时器中断,pwm。MCU是STM32F103ZET6。( W, Z+ p; G1 w# J" r , O* U4 d% g- j6 _; w: r2 p 在keil的设置中可以看到固件的起始地址,默认情况下为0x8000000。另外,keil生成的是hex文件,hex文件是带有基地址信息的bin文件,如果我们要生成bin文件,需要进行如下设置: 9 l. u9 e4 ^7 }: @9 x ( V. f1 E1 o! Q2 G/ Z 2 h6 F- _$ C4 X* }- n9 ~9 h 另外,烧录到mcu中的固件是不带符号表等调试信息的,即hex和bin文件都不带调试信息,因为mcu的内存有限,但keil会单独生成一个带有调试信息的axf文件,我们可以以这个axf文件为基准,对照着恢复bin文件或者hex文件,下面我们来观察一下这三个文件的区别: 首先是axf文件9 M4 x( ^( }! @4 q! K1 e 1 Y# C# J: X( y. t* b8 Q 几乎和源代码一摸一样,除了一些外设名用地址代替了。 , w8 L$ ^) D: b- o, n 再看到hex文件:5 h; ?' ?7 j" |" q2 @ # D6 ~6 D8 P- U 首先需要将架构修改为arm小端序,再修改一下架构版本,stm32f103zet6是cortex-m3内核的,armv7架构。 ) a! m* y! N) F8 n 修改完后加载hex文件。6 K C) w! u6 T R! y 2 @. D- k! ^) u( o IDA会自动识别出基地址,能识别出不少函数了,但伪代码是真的难看。 1 j' V* \8 J1 R7 V |. N. B 基本上是看不出个什么东西来。 6 m. p# C6 q8 I! E; `; Z/ | 最后是bin文件。 4 x# w7 m5 y! O7 O ( l1 g' a! s% ^* }' [, } v , N H6 S( G H2 Y bin文件去除了地址信息的hex文件,需要手动设置基地址。 # N/ a* n, |! W1 } w , E: ?0 T0 q/ l: n7 ? 二、手动设置固件基地址( U6 ` x, Z O) j / j- q2 f' J, S& ` 我们的分析将以bin文件为目标。6 y- F& V5 P; P+ S0 f 1 R. @8 K. j, U4 n# v) x! \& x 将基地址重设为0x8000000,这个地址怎么来的?可以看stm32f103zet6的datasheet。- a ]4 Y2 q& H! l9 ?7 R ( e6 j0 L e( Z" J# u7 Q6 h 从0x8000000开始就是flash的空间。3 J9 e0 S" G3 c/ w% H3 z- D# W 重新设置加载地址之后再将其按照代码解析就变成了hex文件的样子。 % r4 C6 l4 L5 g# K) L : r2 o4 C2 X$ n& |/ N' U3 M: ?" d 但此时存在很多标红的地址,这是因为这些地址在IDA中并没有设置,因此IDA将其解析为非法地址,标红了,我们现在要做的就是手动添加一些段。" C q& f5 R6 V* C/ d ! W) X3 W) I/ p- d0 ? ! d1 V" g2 Y$ E1 F1 y 三、添加SRAM 段在axf文件中,IDA解析出了如下几个段:2 e A2 K5 J# e8 \7 b8 M0 E! ~5 V 2 C9 ~. Z I7 ~1 P$ @5 {& s3 S2 y 单片机内存被总分为flash(rom)和sram(ram),flash里面的数据掉电可保存,sram中的数据掉电就丢失,sram的执行速度要快于flash,flash容量大于sram上方的最低内存地址,最高地址,都是在flash和sram中。9 e# O' X# p* v% i. \ 5 E; g- ^$ S4 B4 R$ u 我们正常下载程序都是下载存储进flash里面,这也是为什么断电可保存的原因。 : E; E7 Q: M _- O5 ?; y- j 单片机的程序存储分为code(代码存储区)、RO-data(只读数据存储区)、RW-data(读写数据存储区) 和 ZI-data(零初始化数据区) Flash 存储 code和RO-data( Z- @4 b% G- Z' L Sram 存储 RW-data 和ZI-data 6 t) o1 _3 S6 |2 ]+ `0 k 在datasheet中一样可以找到sram的起始地址,为0x20000000,结束地址为0x2000ffff,我们自己添加段不必像IDA自动分析的那样详细,一个sram的段即可。' J2 V$ O. l6 a- M1 r * J( N- i: c) t/ D. K& ] 也可以在IDA启动时设置好固件加载地址和sram的开始以及结束地址。# a1 V$ D$ ]1 a' }6 D8 F$ o" H 添加了sram段之后,红色的非法地址消失了,如下: ! p F7 T2 h7 Y- i 四、恢复符号表 下一步,我们需要恢复符号表,由于bin文件不存在调试信息,所以IDA生成的伪代码几乎没眼看,为了方便我们的逆向,我们需要恢复函数名以及导入一些结构体。在这里就需要用到axf文件。+ u" e# d& b. t 在前面我们看到axf文件几乎和源代码是一样的效果,里面有丰富的调试信息,当然,我们不使用这个课设的axf文件来恢复bin,这样就太刻意了。9 U* s1 D6 B% G3 |6 L* {8 T3 X$ K 由于我这个课设使用的是库函数来开发的,所以就不能使用stm32cube生成的hal库来恢复。具体该怎么恢复?我们采用bindiff来恢复符号表。 如果有闲工夫或者是对stm32的开发非常上手,就可以自己写一个demo,尽可能多的使用到各种库函数,然后编译出一个axf文件。我这里的话,由于好久没有用stm32了,开发起来有些生疏,所以就不自己手写了,我选择捡现成的项目。我学习stm32的时候买的是xx原子家的开发板,他们提供的例程也是非常丰富的,有五十多个,由易到难,涵盖了stm32开发的方方面面,因此我就直接拿这些例程中的一部分,编译出axf文件。比如下面这个内存分配的例程:% U0 N: H2 A% v. J % K2 F* F- E* B0 ? 又比如这个pwm的例程:9 z- ~- D) L0 A" N+ i5 K & c4 V- F* n5 `' k9 G% H1 n1 T, d 可以多选几个例程,能涵盖更多的库函数,将这些axf文件用IDA打开,然后生成idb文件。然后在我们的目标bin文件中,使用bindiff加载idb文件。# p' a) V* Y) @ 9 [- u8 \" P1 u. ^8 @9 w- K# K& _/ @ 选择一个idb文件,然后会出现这样一个比较界面: 选取similarity大的函数导入到bin文件中。 * V3 J7 K1 d! Q6 O7 R; A 6 b/ _2 L1 K! U+ S3 q2 ~ + z, f/ C+ L$ b8 E* O, E 导入之后实际上就能恢复大部分的函数名了。 " o8 V$ Q. W1 x6 b6 }- V ) `9 _: h: a) D! X/ y# m+ k ' T8 O, M; n7 W* A) [# W5 p 但实际上,由于我这个课设的开发中使用了一些xx原子封装好的初始化函数(相当于xx原子对原本的库函数做了二次开发),例如uart_init,delay_init这两个函数均是二次封装后的函数,所以恢复出来的函数会比一般情况下多一点。 多使用几个idb文件进行恢复后,已经可以看了。# P: G: S, B$ J8 M9 U/ z% C + m/ o8 t- E* E. \ ' |" i" P# J4 D 五、导入SVD文件,恢复外设结构7 Z/ h* B6 Z m1 l7 n 但这还不够,我们还需要导入SVD文件,啥是SVD文件捏? 5 ~5 C5 ^1 _2 r8 G) {: H ]5 \ 3 ]" [5 z& e- `6 s 在IDA7.5以后,就自带SVD文件加载插件了,如下图:; f# R9 e4 L7 K4 t j% h : g. i) {; G& N6 V7 q, x9 k+ x 打开之后如下: 6 e; B8 s& L% D - w( R. K7 d4 l. e( s 我们可以自行下载相应的SVD文件,或者加载GitHub上的仓库,我这里选择自行下载然后在本地加载。下载链接是这个:cmsis-svd B" I2 J2 v: Z) _ 选中想要加载的svd文件之后,IDA就会自动恢复bin文件中的外设结构,体现在伪代码中就是这样: # W$ m+ c. k/ O5 ? * N) ]8 U6 }; k4 L4 n& c 这样: , m4 P+ R3 C7 C/ B$ S ' e' w5 n: }5 M' {7 \2 L X 7 ]6 E7 z. V0 X' G: ? 当然,也不是所有的外设都能恢复,和axf文件中的肯定是有差别的,但对我们的逆向已经起到了很大的作用了。 ?+ t+ S0 v8 T9 {# Y 到目前位置我们已经做了如下几步:加载bin文件->设置固件加载基地址->添加sram段->恢复符号表->恢复外设信息。程序还原工作已经接近尾声,还剩下最后一步,恢复结构体。 六、恢复结构体* w& H! R4 L, o5 m. d 打开某个例程的axf文件,可以查看下其中使用的结构体。# o ^; b9 `" ]( e 9 E. g8 [ k' y& B- O# k 这些结构体中,有TIM_TimeBaseInitTypeDef,NVIC_InitTypeDef,GPIO_InitTypeDef,USART_InitTypeDef,GPIO_TypeDef等是我们经常使用的,用来进行定时器初始化,中断初始化,IO口初始化等等。 我们生成c header file。 $ W" |1 v: q3 L4 _5 E2 J8 n8 i1 O 然后在bin文件中解析生成的头文件: 然后在local type中就能看到不少结构体了。2 b$ Q9 w0 h2 L2 _5 V . l' k! z0 z* J: D/ l5 x' A 光导入了结构体还不够,我们还得手动将IDA的伪代码中的变量重新设置类型,怎么确定某个变量的类型是什么呢?需要结合库函数的变量类型来设置。 以GPIO_Init这个函数为例:6 @0 f- t; q3 N 9 [9 k# ]9 A, ^, ~ 我们打开固件库使用手册,找到GPIO_Init的原型。 8 w4 R9 o0 e, `; G. o : C5 R5 S% F5 A( C3 q3 R. v2 D 可以看到原型为: : b* `8 |/ U3 k* F" K8 E void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)5 Q4 W, x3 W! B5 a* U) f 第二个参数为GPIO_InitTypeDef类型的,因此我们将伪代码中的v10的类型修改为GPIO_InitTypeDef,剩下的结构体我们也一样可以使用这样的方法来恢复,只要有固件库函数,我们就能够恢复对应的结构体,效果如下:$ k0 g' n# S6 d' X" Y 4 i, A. v# K; e " b; l" G4 l) j8 s# ^8 r/ M 效果还不错。 9 E7 s. |3 }0 b 七、总结4 y6 }# _* V, ~0 C! \ 此次逆向是基于我的一个小课设,功能不多,而且由于我的课设用到了xx原子的例程二次开发的一些库,而我用来恢复符号表时使用的axf文件也是xx原子的例程编译出来的,所以恢复出来的符号表的比例会偏高。如果能确定使用芯片型号,以及使用的是固件库还是hal库,那么按照此方法大致上能在一定程度上恢复固件,使其可读性大大增加。( G& \1 M6 ]2 e, n8 ~# l 0 l' l4 y: n; }' k5 |( f) h 转载自:Lpwn 9 B) M: n, o' z! K& q- j 7 u; O7 |2 m- Y( a |
基于STM32的BootLoader经验分享
基于STM32如何选择 S2-LP 的外部晶体经验分享
基于是STM32的BLE 设备地址经验分享
基于ToF传感器的3D手势识别
NUCLEO-U545RE-Q评测(2)运行环境建立
NUCLEO-U545RE-Q评测 (3)驱动OLED
NUCLEO-U545RE-Q评测 (6)FFT
NUCLEO-U545RE-Q评测 (5)DAC_DMA
NUCLEO-U545RE-Q评测 (4)ADC_DMA_转换
基于STM32 TrustZone 开发HardFault调试与处理