STMCU小助手
发布时间:2022-12-1 18:00
|
一、概括6 A* o! r1 ?. @, W* y8 \ 用的很久之前做的一个电风扇的课设来做实验。4 u5 c w9 C- W# f. M 2 l9 E+ ^. t5 }- P
用到了外部中断,定时器中断,pwm。MCU是STM32F103ZET6。
& o2 p3 R6 t" i* C( s0 M7 R 在keil的设置中可以看到固件的起始地址,默认情况下为0x8000000。另外,keil生成的是hex文件,hex文件是带有基地址信息的bin文件,如果我们要生成bin文件,需要进行如下设置: ( b, F" v' r! }. V) j - |( }% _& }+ G2 Q9 @3 A r
另外,烧录到mcu中的固件是不带符号表等调试信息的,即hex和bin文件都不带调试信息,因为mcu的内存有限,但keil会单独生成一个带有调试信息的axf文件,我们可以以这个axf文件为基准,对照着恢复bin文件或者hex文件,下面我们来观察一下这三个文件的区别:& H# I7 Q X. `8 J3 ?% p 首先是axf文件 ) E* b- h5 D H ` r1 X& Z
9 ]" R4 x+ K& @% D ` 几乎和源代码一摸一样,除了一些外设名用地址代替了。) E6 K. \2 C7 ?" u5 W' g ) L, `) d$ F% Z$ b" H! `8 X 再看到hex文件:7 B, }+ l) Y0 p/ s4 c e
1 J% Z: s) z* |4 W# Z2 q& | 首先需要将架构修改为arm小端序,再修改一下架构版本,stm32f103zet6是cortex-m3内核的,armv7架构。' |; F6 w! }. K! u+ n4 {$ h $ A$ c- M, D( ?+ S$ p6 G7 y
* S6 m9 }# f; {- Z y% B ' M7 Y9 V6 K, J$ |4 j& H 修改完后加载hex文件。8 e& i; e& v* R- H3 D9 H
i+ p; \* A" L% m1 w9 z8 r Z x- ?; P' {; }) l' c IDA会自动识别出基地址,能识别出不少函数了,但伪代码是真的难看。# b( n* ^5 |5 {7 z* f/ U " x# v0 @( b: f( g: e- E
# q0 Q$ X7 X4 G' l. @3 l 基本上是看不出个什么东西来。# \/ l" U$ ]6 R3 p0 k; e 5 U1 ?% H4 r$ \) a 最后是bin文件。 8 n. o" ?2 K4 @3 e' l9 H" T* y
bin文件去除了地址信息的hex文件,需要手动设置基地址。 ' U0 b) _9 [5 s: j* k& [" @0 i 二、手动设置固件基地址, v7 k# F& a1 N7 H4 n( ? 我们的分析将以bin文件为目标。) J9 c$ q$ Z ]1 ^6 n 将基地址重设为0x8000000,这个地址怎么来的?可以看stm32f103zet6的datasheet。 & B8 P) G9 q( P+ J- [ c
从0x8000000开始就是flash的空间。$ }) m( l' A7 N* y- {3 J0 o2 ^7 q, ` 2 f n0 ~5 t" S2 b' U+ y+ t 重新设置加载地址之后再将其按照代码解析就变成了hex文件的样子。) N" F a9 h3 B! z2 D' D( C% j2 i C3 f7 O$ @7 |: J% U j6 p4 z1 u% T
" T) d- U- ~7 {" T1 U0 S- [( t+ O. \ 但此时存在很多标红的地址,这是因为这些地址在IDA中并没有设置,因此IDA将其解析为非法地址,标红了,我们现在要做的就是手动添加一些段。 * W! P i. z+ x 三、添加SRAM 段在axf文件中,IDA解析出了如下几个段: ( Q: O2 j; t8 O
单片机内存被总分为flash(rom)和sram(ram),flash里面的数据掉电可保存,sram中的数据掉电就丢失,sram的执行速度要快于flash,flash容量大于sram上方的最低内存地址,最高地址,都是在flash和sram中。 $ i. G* Q) n/ w3 X3 Y9 s) @. @ 我们正常下载程序都是下载存储进flash里面,这也是为什么断电可保存的原因。 单片机的程序存储分为code(代码存储区)、RO-data(只读数据存储区)、RW-data(读写数据存储区) 和 ZI-data(零初始化数据区)4 L K) E& n+ Y% J$ b6 H9 Y) ~$ c Flash 存储 code和RO-data6 V- G2 X3 A; t5 k( M' l# X( @" k Sram 存储 RW-data 和ZI-data 在datasheet中一样可以找到sram的起始地址,为0x20000000,结束地址为0x2000ffff,我们自己添加段不必像IDA自动分析的那样详细,一个sram的段即可。 也可以在IDA启动时设置好固件加载地址和sram的开始以及结束地址。/ N+ h2 }# A# S" \ d 添加了sram段之后,红色的非法地址消失了,如下: - {; I- }: N' D! y% X$ g0 a2 a2 Z
四、恢复符号表 下一步,我们需要恢复符号表,由于bin文件不存在调试信息,所以IDA生成的伪代码几乎没眼看,为了方便我们的逆向,我们需要恢复函数名以及导入一些结构体。在这里就需要用到axf文件。- Q' U; }* A$ ^ 在前面我们看到axf文件几乎和源代码是一样的效果,里面有丰富的调试信息,当然,我们不使用这个课设的axf文件来恢复bin,这样就太刻意了。 ) _3 d4 b8 q0 l6 N9 C 由于我这个课设使用的是库函数来开发的,所以就不能使用stm32cube生成的hal库来恢复。具体该怎么恢复?我们采用bindiff来恢复符号表。 如果有闲工夫或者是对stm32的开发非常上手,就可以自己写一个demo,尽可能多的使用到各种库函数,然后编译出一个axf文件。我这里的话,由于好久没有用stm32了,开发起来有些生疏,所以就不自己手写了,我选择捡现成的项目。我学习stm32的时候买的是xx原子家的开发板,他们提供的例程也是非常丰富的,有五十多个,由易到难,涵盖了stm32开发的方方面面,因此我就直接拿这些例程中的一部分,编译出axf文件。比如下面这个内存分配的例程:3 x" F" Y( A* B) K5 f& k2 V! d + z+ s& _" j1 S5 x7 C" l 又比如这个pwm的例程:& w' A8 m# z/ Y' S% {" }9 q
可以多选几个例程,能涵盖更多的库函数,将这些axf文件用IDA打开,然后生成idb文件。然后在我们的目标bin文件中,使用bindiff加载idb文件。 n9 Z: O) u4 P/ N- \ 2 E; ~, ] |# g2 @" a4 m
- N2 O+ U* D2 g 7 F: E, M. v& w8 f+ c5 C 选择一个idb文件,然后会出现这样一个比较界面:
3 T- w. b, i/ e) Q3 s" O 0 _7 b( T$ }" r3 i k9 e k 选取similarity大的函数导入到bin文件中。0 ?7 k0 _# }( j9 J1 S% R/ |
( z: S3 l) R- q: [+ t 导入之后实际上就能恢复大部分的函数名了。% p- }+ m+ v& a
但实际上,由于我这个课设的开发中使用了一些xx原子封装好的初始化函数(相当于xx原子对原本的库函数做了二次开发),例如uart_init,delay_init这两个函数均是二次封装后的函数,所以恢复出来的函数会比一般情况下多一点。# t; a7 ]- G, [; i 7 G, h0 J9 M3 k+ \( o 多使用几个idb文件进行恢复后,已经可以看了。! f7 ^& B1 H1 f) G4 W
+ {% r% U) }+ l2 l2 { . X7 Q' \0 |" S* _( M& h 五、导入SVD文件,恢复外设结构 但这还不够,我们还需要导入SVD文件,啥是SVD文件捏?, S( r: K( x D) |5 `
$ [% d) n, y) u 在IDA7.5以后,就自带SVD文件加载插件了,如下图: h. E* i. I/ c3 C2 P6 p) D( K 4 n* ~9 C K# b/ u% `
3 G; K9 d' v1 X1 Q0 V. m 打开之后如下:: }3 N( i) P/ K. {; K8 k
我们可以自行下载相应的SVD文件,或者加载GitHub上的仓库,我这里选择自行下载然后在本地加载。下载链接是这个:cmsis-svd 选中想要加载的svd文件之后,IDA就会自动恢复bin文件中的外设结构,体现在伪代码中就是这样:9 m( F! U9 N1 X* c. {; I ' N8 s; d" X) [; D, Q% y' S
3 A$ w0 \) @ ]& ~ 这样:
5 o ]/ l1 }% h1 m 当然,也不是所有的外设都能恢复,和axf文件中的肯定是有差别的,但对我们的逆向已经起到了很大的作用了。 1 n- H+ `1 S1 R) ~" G* Z 到目前位置我们已经做了如下几步:加载bin文件->设置固件加载基地址->添加sram段->恢复符号表->恢复外设信息。程序还原工作已经接近尾声,还剩下最后一步,恢复结构体。 六、恢复结构体9 _! u' g4 R: a) q1 y 打开某个例程的axf文件,可以查看下其中使用的结构体。 : T% `# |2 c$ [; \3 t; Y. L
这些结构体中,有TIM_TimeBaseInitTypeDef,NVIC_InitTypeDef,GPIO_InitTypeDef,USART_InitTypeDef,GPIO_TypeDef等是我们经常使用的,用来进行定时器初始化,中断初始化,IO口初始化等等。 我们生成c header file。* w" I5 G% D' m8 f6 m+ F
3 C6 I9 H9 g+ L) ?0 q# E$ j& i 然后在bin文件中解析生成的头文件: + u4 F$ g: r5 z
+ Q# W9 z4 b( s8 w5 e4 ] 然后在local type中就能看到不少结构体了。
光导入了结构体还不够,我们还得手动将IDA的伪代码中的变量重新设置类型,怎么确定某个变量的类型是什么呢?需要结合库函数的变量类型来设置。, B3 }+ d& M7 m5 A: f6 l' { $ Z: m/ n, P7 q! ^; J2 s) f5 k- V 以GPIO_Init这个函数为例:
; J0 s$ o) y9 g 我们打开固件库使用手册,找到GPIO_Init的原型。( E1 F, }( S3 n4 j) x# }( y % |2 d- Y# Z9 \$ C9 z- M O' X/ L+ `6 w8 ~% u- \" b
, o3 n, R l% Z/ A0 l7 ]# \7 l 可以看到原型为:4 o' \$ m& x# K) l, A- A ( S9 f1 a" a! f* ~2 T' \ void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct); p5 v: G+ j4 f! t0 b 第二个参数为GPIO_InitTypeDef类型的,因此我们将伪代码中的v10的类型修改为GPIO_InitTypeDef,剩下的结构体我们也一样可以使用这样的方法来恢复,只要有固件库函数,我们就能够恢复对应的结构体,效果如下: ; S' ?6 |1 K8 A9 K # r8 W, W2 Q6 C 6 F- n3 @3 ?2 M6 H# q5 Y 3 S' H; V0 F& V8 V4 Y& n/ }1 l 效果还不错。! D [1 _, }; Y ( d, h0 H. z" ^5 ]9 K% s 七、总结3 t% L! l! |% ` P, I4 W 此次逆向是基于我的一个小课设,功能不多,而且由于我的课设用到了xx原子的例程二次开发的一些库,而我用来恢复符号表时使用的axf文件也是xx原子的例程编译出来的,所以恢复出来的符号表的比例会偏高。如果能确定使用芯片型号,以及使用的是固件库还是hal库,那么按照此方法大致上能在一定程度上恢复固件,使其可读性大大增加。 转载自:Lpwn |
【STM32H735G-DK 测评】STM32H735G-DK External Loder 编译和测试
《MCUBoot课程》学习笔记+从信任链原理到量产实践
Keil-MDK 在线调试设置断点的高级用法
请教下STM32N6570 怎么把分辨率改成320x240并可以全屏显示
【福利三:逢7发帖赢大礼】想要充分利用STM32H743VI片内的RAM空间(但并不顺利)
基于stm32平台上的IC-MU磁绝对值编码器SPI通讯和码盘数据处理
使用STM32 MCU和MPU连接PDM数字麦克风
分享STM32资料-采用 STM32U031R8 MCU 的 STM32 Nucleo-64 开发板,官方资料,数据手册等资料
好用软件推荐-TouchGFX Designer
HRTIM 指南
微信公众号
手机版