一、概括8 P2 Z1 t$ u6 o% w$ S2 O 用的很久之前做的一个电风扇的课设来做实验。 , G, q- y9 w- s 4 g7 y' \) \9 C- A 用到了外部中断,定时器中断,pwm。MCU是STM32F103ZET6。 : m9 N- O }7 G4 V 1 \+ U% A; d2 Z; K& x. a, l% { 1 `$ z, z9 k# D8 ~ t 在keil的设置中可以看到固件的起始地址,默认情况下为0x8000000。另外,keil生成的是hex文件,hex文件是带有基地址信息的bin文件,如果我们要生成bin文件,需要进行如下设置: e3 I- `* r P0 B: ?. f" c 1 O+ H" O2 L3 L( Y- |9 a 另外,烧录到mcu中的固件是不带符号表等调试信息的,即hex和bin文件都不带调试信息,因为mcu的内存有限,但keil会单独生成一个带有调试信息的axf文件,我们可以以这个axf文件为基准,对照着恢复bin文件或者hex文件,下面我们来观察一下这三个文件的区别:6 r( W+ |5 D' U/ Z0 u- ^ 首先是axf文件0 g8 d3 |/ W8 U8 S% T( w7 y8 b- | 8 V2 q* L% f/ K1 k, T; Y+ C 几乎和源代码一摸一样,除了一些外设名用地址代替了。 再看到hex文件:) D7 p& W/ T: Y$ t; f8 W5 T' R, s ( T( L; [. V- J( z) T 首先需要将架构修改为arm小端序,再修改一下架构版本,stm32f103zet6是cortex-m3内核的,armv7架构。 6 G% n7 _( i" q1 J- J2 Z 0 S( ^; ]6 i, ]- L8 w, P. R& A 修改完后加载hex文件。8 Y; g( R* I; H3 f7 [" D0 w ' s' }9 O$ N1 ^% ]* W4 J % h2 q4 _' o, s * ]$ T8 v" g' L4 J% Y9 H3 | IDA会自动识别出基地址,能识别出不少函数了,但伪代码是真的难看。1 J+ T, v x% }0 E# D* e& C* b 0 l/ n& |2 s2 ]& K7 h 9 x5 C w8 t6 V0 ? 基本上是看不出个什么东西来。 ( A+ E' U) d% E 最后是bin文件。/ g l0 @" H b! O3 L" f Q 3 e' O+ X; V. N& j/ e 4 j( O2 e' D% I/ x5 _- o ( `( ~1 T/ L* X/ { bin文件去除了地址信息的hex文件,需要手动设置基地址。9 y2 ~0 k' O- T3 z e 3 |; r7 A2 E: j7 }- y' D1 w # f- P, s/ ~+ D5 Z- x3 r 二、手动设置固件基地址0 B$ A$ I0 ^ F: W 我们的分析将以bin文件为目标。 3 r- v% e* s6 k 将基地址重设为0x8000000,这个地址怎么来的?可以看stm32f103zet6的datasheet。 9 d# U$ w( e1 y) F: k, j7 S* a' ] - f: x _8 G" b2 a* @ ; N! o, P: J4 {) ] b& ~ 从0x8000000开始就是flash的空间。 2 `' o4 F d& k) F' g: r5 _ 重新设置加载地址之后再将其按照代码解析就变成了hex文件的样子。: F O& O, L& ]: b# z! q/ l9 } " f8 {3 x4 C& Y3 I4 j' s 但此时存在很多标红的地址,这是因为这些地址在IDA中并没有设置,因此IDA将其解析为非法地址,标红了,我们现在要做的就是手动添加一些段。 三、添加SRAM 段在axf文件中,IDA解析出了如下几个段: 单片机内存被总分为flash(rom)和sram(ram),flash里面的数据掉电可保存,sram中的数据掉电就丢失,sram的执行速度要快于flash,flash容量大于sram上方的最低内存地址,最高地址,都是在flash和sram中。- B$ i- N S; _; W7 X. e 5 c0 R& V, y# c) W1 ] 我们正常下载程序都是下载存储进flash里面,这也是为什么断电可保存的原因。' W# I$ ?0 ]0 v- h . ]& g+ w0 N; ?. j g* ^ 单片机的程序存储分为code(代码存储区)、RO-data(只读数据存储区)、RW-data(读写数据存储区) 和 ZI-data(零初始化数据区) Flash 存储 code和RO-data Sram 存储 RW-data 和ZI-data 在datasheet中一样可以找到sram的起始地址,为0x20000000,结束地址为0x2000ffff,我们自己添加段不必像IDA自动分析的那样详细,一个sram的段即可。) M% t l3 Q/ [ 也可以在IDA启动时设置好固件加载地址和sram的开始以及结束地址。! Z k$ x+ s; M% e 添加了sram段之后,红色的非法地址消失了,如下: 7 e5 b. n. x: Y; I+ }& B3 K8 ` 7 w! ~/ u% }( g ~* w f 四、恢复符号表 下一步,我们需要恢复符号表,由于bin文件不存在调试信息,所以IDA生成的伪代码几乎没眼看,为了方便我们的逆向,我们需要恢复函数名以及导入一些结构体。在这里就需要用到axf文件。 : S9 B( T& v( S 在前面我们看到axf文件几乎和源代码是一样的效果,里面有丰富的调试信息,当然,我们不使用这个课设的axf文件来恢复bin,这样就太刻意了。$ }1 B5 @6 H C; }6 N2 ~ ( P' v$ r0 J/ b9 _, w, i* T9 `, o 由于我这个课设使用的是库函数来开发的,所以就不能使用stm32cube生成的hal库来恢复。具体该怎么恢复?我们采用bindiff来恢复符号表。, O3 b( C' G4 d8 X6 ~) Y8 T$ \ 如果有闲工夫或者是对stm32的开发非常上手,就可以自己写一个demo,尽可能多的使用到各种库函数,然后编译出一个axf文件。我这里的话,由于好久没有用stm32了,开发起来有些生疏,所以就不自己手写了,我选择捡现成的项目。我学习stm32的时候买的是xx原子家的开发板,他们提供的例程也是非常丰富的,有五十多个,由易到难,涵盖了stm32开发的方方面面,因此我就直接拿这些例程中的一部分,编译出axf文件。比如下面这个内存分配的例程: + [( b% j+ `; b1 Y! e / A; M0 Y) _/ f; Q5 }1 H1 l1 D 7 H) D6 c8 v$ k7 Y T' X 又比如这个pwm的例程:" w, ?6 J) J$ f2 V; _ 可以多选几个例程,能涵盖更多的库函数,将这些axf文件用IDA打开,然后生成idb文件。然后在我们的目标bin文件中,使用bindiff加载idb文件。9 W6 g: E% c" u9 a; ? 1 a$ f: @& K2 O+ \9 e. Z3 g* T 5 N2 R/ u0 Q- v- \3 O# m; ] % S6 j" v @8 {, L/ T 选择一个idb文件,然后会出现这样一个比较界面: 8 k% v0 t6 \$ ^* s4 _ / }; Q) j' e: n 选取similarity大的函数导入到bin文件中。 % y i3 D' K& z* r( V( \ * {7 z+ Q/ L' D5 W7 c9 T9 m 导入之后实际上就能恢复大部分的函数名了。 " a5 V; Y J* W' p9 j7 u 1 l% d U$ M7 o! ~2 D8 W/ j" k 2 i4 C: E9 W# j( R 但实际上,由于我这个课设的开发中使用了一些xx原子封装好的初始化函数(相当于xx原子对原本的库函数做了二次开发),例如uart_init,delay_init这两个函数均是二次封装后的函数,所以恢复出来的函数会比一般情况下多一点。. y. r5 j. G9 n' W/ p- W+ z" H 多使用几个idb文件进行恢复后,已经可以看了。6 }2 ~7 L* ?5 ~$ `" I' Q* ]. V ( L- x* f/ y' x4 l # t% ]( }! l$ b1 o( i W$ S 五、导入SVD文件,恢复外设结构* v3 N" T5 I: F / ?3 I8 r' \! d: y6 y+ o 但这还不够,我们还需要导入SVD文件,啥是SVD文件捏?, y# V2 O$ u# z+ t' u: N) U / D+ E$ o9 ]3 I0 [2 m8 [' C 在IDA7.5以后,就自带SVD文件加载插件了,如下图: 9 g8 C- l3 z5 _) Q & Z1 I" }4 @3 z1 ~ 打开之后如下: , w' l5 o! Z' c) n1 f& \ # `" z4 U2 }, ]+ Y5 v9 | 我们可以自行下载相应的SVD文件,或者加载GitHub上的仓库,我这里选择自行下载然后在本地加载。下载链接是这个:cmsis-svd5 c: T9 Y! Y/ C 选中想要加载的svd文件之后,IDA就会自动恢复bin文件中的外设结构,体现在伪代码中就是这样:4 E0 y) G/ w S5 q: K% v" s : |* J5 m# H x5 x) n( ?- {( w # f% i% w6 {" T. {: x 这样:% g! i8 W$ g1 v" P3 ? 3 d2 B: k3 s, q! _ 4 F9 l S! p' W0 m 4 q# O$ n3 Q8 z( O3 q& e* @: Q 4 u; Q- x4 G) g 当然,也不是所有的外设都能恢复,和axf文件中的肯定是有差别的,但对我们的逆向已经起到了很大的作用了。' ~' B+ E/ _, S: k' U: ] " _, b! {1 k4 r 到目前位置我们已经做了如下几步:加载bin文件->设置固件加载基地址->添加sram段->恢复符号表->恢复外设信息。程序还原工作已经接近尾声,还剩下最后一步,恢复结构体。; i8 F1 H- K7 d1 `. W1 t+ V ' F0 }$ Y d! |2 f- J7 b 六、恢复结构体) J* W; ?' ^ J0 a$ S 打开某个例程的axf文件,可以查看下其中使用的结构体。 这些结构体中,有TIM_TimeBaseInitTypeDef,NVIC_InitTypeDef,GPIO_InitTypeDef,USART_InitTypeDef,GPIO_TypeDef等是我们经常使用的,用来进行定时器初始化,中断初始化,IO口初始化等等。9 i! W1 j3 b2 I$ c 9 Y9 Q; Q! s% H$ }* C 我们生成c header file。& W+ y8 ]8 n4 f; o3 v( n- A7 A ' ]$ s' ]; i( N% N# p: G0 O! | 然后在bin文件中解析生成的头文件: Z3 B2 `" [2 I1 b2 {) ` 然后在local type中就能看到不少结构体了。& z$ R" @. T! r! ^ ( i3 ^" ]7 j( q k! n! H" y 光导入了结构体还不够,我们还得手动将IDA的伪代码中的变量重新设置类型,怎么确定某个变量的类型是什么呢?需要结合库函数的变量类型来设置。1 _9 G; N9 v* s, p# Z0 V7 D* E+ N' F 以GPIO_Init这个函数为例:. s' Z" }- |% s7 t/ v & i1 Z9 s$ X) F; A6 ~3 ` 1 l! J) ~3 k3 f3 D4 C9 l 我们打开固件库使用手册,找到GPIO_Init的原型。 8 |' t/ e( v" C8 s, S; b; b- ^ 可以看到原型为: ! h5 |! }& D0 i: k7 {! Y void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)' H( L$ C' }7 W' o 9 l" \) F+ i. X+ T$ }/ W- h2 ~. n 第二个参数为GPIO_InitTypeDef类型的,因此我们将伪代码中的v10的类型修改为GPIO_InitTypeDef,剩下的结构体我们也一样可以使用这样的方法来恢复,只要有固件库函数,我们就能够恢复对应的结构体,效果如下:3 Y# p6 P% i( o4 T% T2 U7 O 8 R0 K2 N9 u x. w% X1 u, @5 w . k" D1 C9 |: W8 r; ], n 7 D: l# A9 A' B: i4 | 效果还不错。% g4 ^% R9 A3 y9 u8 Q1 a / l* p; R' Z9 o- J 七、总结 此次逆向是基于我的一个小课设,功能不多,而且由于我的课设用到了xx原子的例程二次开发的一些库,而我用来恢复符号表时使用的axf文件也是xx原子的例程编译出来的,所以恢复出来的符号表的比例会偏高。如果能确定使用芯片型号,以及使用的是固件库还是hal库,那么按照此方法大致上能在一定程度上恢复固件,使其可读性大大增加。 : z3 y* F" a9 y: V: [$ j, ?4 q 转载自:Lpwn, ~4 w* R& g3 `$ ] ; \1 Z. X1 g% k2 |4 P { |
基于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调试与处理