你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

STM32固件逆向

[复制链接]
STMCU小助手 发布时间:2023-2-21 21:06
一、概括

. G) y5 c1 B9 x  k9 ]. u
用的很久之前做的一个电风扇的课设来做实验。

6 W1 B2 r* _) L* n# O9 `: f
微信图片_20230221210619.png
0 o4 A) ?+ V, O7 k1 v4 u# K; U
用到了外部中断,定时器中断,pwm。MCU是STM32F103ZET6。

: f0 l6 H8 E# u' v! v; L
微信图片_20230221210616.png

* v+ w% h3 c# i  |) K
在keil的设置中可以看到固件的起始地址,默认情况下为0x8000000。另外,keil生成的是hex文件,hex文件是带有基地址信息的bin文件,如果我们要生成bin文件,需要进行如下设置:
% o" b( U/ Q3 F4 f0 |6 D1 |
微信图片_20230221210612.png

1 Z- K& Y8 K! z1 k9 |
另外,烧录到mcu中的固件是不带符号表等调试信息的,即hex和bin文件都不带调试信息,因为mcu的内存有限,但keil会单独生成一个带有调试信息的axf文件,我们可以以这个axf文件为基准,对照着恢复bin文件或者hex文件,下面我们来观察一下这三个文件的区别:
8 R! h6 ~+ S* d' B  r+ ]
首先是axf文件

% z0 Q- r3 X: G
微信图片_20230221210608.png

" b( c. w2 Y& b: S9 [4 x4 W8 @ 微信图片_20230221210603.png
) S5 N6 x+ D7 K5 b0 m7 S几乎和源代码一摸一样,除了一些外设名用地址代替了。
( n4 Z3 r5 L( _9 J& O% W  _

9 e9 M0 l- o, @! R
再看到hex文件:

0 _- S0 o) ~$ }* Q% K
微信图片_20230221210600.png
- e; Z0 h9 b$ A
首先需要将架构修改为arm小端序,再修改一下架构版本,stm32f103zet6是cortex-m3内核的,armv7架构。

6 |) K% b9 `. M1 T( U& y6 ]
微信图片_20230221210552.png
- i+ O0 X- d# e, m, `
6 Z- z9 u' l& |/ ^
修改完后加载hex文件。

& ]; n- u% p5 v- v6 ]
微信图片_20230221210550.png
& ?+ ?" `5 }$ M( L- @4 Z
IDA会自动识别出基地址,能识别出不少函数了,但伪代码是真的难看。

" o2 ]5 W% m0 ~5 \# X
微信图片_20230221210547.png

: H! C1 U! E+ z% I+ {& Z
基本上是看不出个什么东西来。

) Q1 Q( Z6 M: `最后是bin文件。
$ y4 a: ?0 d- H+ D3 u# j
微信图片_20230221210543.png ( y+ j& w! q+ s# d7 K4 L

7 z$ A$ F% G( b! S2 U
bin文件去除了地址信息的hex文件,需要手动设置基地址。
% D' ]2 _( @3 v( }1 Z0 G

& G7 b+ F) |; r. p+ r1 t/ c
二、手动设置固件基地址
) d2 E1 w" j$ e2 K7 k# c9 B# B. Z
我们的分析将以bin文件为目标。
" h  U! L3 c9 x# T( k' Y5 B
将基地址重设为0x8000000,这个地址怎么来的?可以看stm32f103zet6的datasheet。
0 s2 h1 ]$ _& ?: Q
微信图片_20230221210540.png

( ~2 G$ ?. C1 u0 m% F
从0x8000000开始就是flash的空间。

4 c1 V2 C- V5 Q  `1 M0 v& }* k% a重新设置加载地址之后再将其按照代码解析就变成了hex文件的样子。
( Z1 q& k8 h) V8 c1 z; B& j
微信图片_20230221210536.png

' C' ^; `. Q2 u- C/ ]
但此时存在很多标红的地址,这是因为这些地址在IDA中并没有设置,因此IDA将其解析为非法地址,标红了,我们现在要做的就是手动添加一些段。
# S7 S" r3 s/ v6 ~3 s. X, q* m( ?

; q6 }+ w5 s% c2 h6 X# F
三、添加SRAM

; ~# ^  @: e" e# v, m
段
7 d% a. @/ O' ~在axf文件中,IDA解析出了如下几个段:

0 j4 M/ s7 y5 _, m5 G8 y
微信图片_20230221210532.png

2 ?# y& b/ Y: ~, O: t
单片机内存被总分为flash(rom)和sram(ram),flash里面的数据掉电可保存,sram中的数据掉电就丢失,sram的执行速度要快于flash,flash容量大于sram上方的最低内存地址,最高地址,都是在flash和sram中。

# o& q/ Y  t. ~! k5 q我们正常下载程序都是下载存储进flash里面,这也是为什么断电可保存的原因。
5 d  U% I* Z" t& M
单片机的程序存储分为code(代码存储区)、RO-data(只读数据存储区)、RW-data(读写数据存储区) 和 ZI-data(零初始化数据区)$ K' w9 W* w+ _9 n2 U
Flash 存储 code和RO-data
& \# y2 v* L4 W9 D' vSram 存储 RW-data 和ZI-data
1 F% l7 v/ J$ ~( q* Q" h+ x: a* o
在datasheet中一样可以找到sram的起始地址,为0x20000000,结束地址为0x2000ffff,我们自己添加段不必像IDA自动分析的那样详细,一个sram的段即可。

2 ^$ p6 c7 o5 s; L. ^. |. }, ?  y也可以在IDA启动时设置好固件加载地址和sram的开始以及结束地址。
4 z3 n' f5 C4 G$ Z3 E0 R2 O添加了sram段之后,红色的非法地址消失了,如下:
# o. q7 Q. E# Q  ^7 @
微信图片_20230221210525.png

3 l+ z8 {. r$ q& Z
  K& n5 K! `" l% }' T$ w6 k
四、恢复符号表
! w6 Q: r% V* d% Q
下一步,我们需要恢复符号表,由于bin文件不存在调试信息,所以IDA生成的伪代码几乎没眼看,为了方便我们的逆向,我们需要恢复函数名以及导入一些结构体。在这里就需要用到axf文件。
在前面我们看到axf文件几乎和源代码是一样的效果,里面有丰富的调试信息,当然,我们不使用这个课设的axf文件来恢复bin,这样就太刻意了。
由于我这个课设使用的是库函数来开发的,所以就不能使用stm32cube生成的hal库来恢复。具体该怎么恢复?我们采用bindiff来恢复符号表。
如果有闲工夫或者是对stm32的开发非常上手,就可以自己写一个demo,尽可能多的使用到各种库函数,然后编译出一个axf文件。我这里的话,由于好久没有用stm32了,开发起来有些生疏,所以就不自己手写了,我选择捡现成的项目。我学习stm32的时候买的是xx原子家的开发板,他们提供的例程也是非常丰富的,有五十多个,由易到难,涵盖了stm32开发的方方面面,因此我就直接拿这些例程中的一部分,编译出axf文件。比如下面这个内存分配的例程:5 ]  s6 C8 B+ l: I4 i/ w

1 {3 T3 O& P' {( W3 X( ]
微信图片_20230221210522.png
. e% R* Y$ K1 o4 @) M
又比如这个pwm的例程:

! U3 P+ h5 y6 }* _& y# I" a
微信图片_20230221210519.png / @0 }/ }/ _* @* T. d* L
# n* R$ b+ W5 C( ?, _
可以多选几个例程,能涵盖更多的库函数,将这些axf文件用IDA打开,然后生成idb文件。然后在我们的目标bin文件中,使用bindiff加载idb文件。

+ o  P3 H0 w, }+ A( C. E
微信图片_20230221210515.png
1 u9 D" v! {0 b# L& V! W7 @
选择一个idb文件,然后会出现这样一个比较界面:

+ _3 _6 |" @7 \% V8 p7 b
微信图片_20230221210511.png

" E, o. j) O$ }5 _* L
选取similarity大的函数导入到bin文件中。
6 |& ]. W- K2 c
微信图片_20230221210508.png
- L4 I9 p4 I" K+ H2 r, |
导入之后实际上就能恢复大部分的函数名了。

9 D9 M2 u9 G: O$ j$ K1 ]' u( C, t
微信图片_20230221210505.png

( I% k, d6 ?# `4 K
但实际上,由于我这个课设的开发中使用了一些xx原子封装好的初始化函数(相当于xx原子对原本的库函数做了二次开发),例如uart_init,delay_init这两个函数均是二次封装后的函数,所以恢复出来的函数会比一般情况下多一点。
+ ]; {7 P) K, h
多使用几个idb文件进行恢复后,已经可以看了。
  ?3 J# J+ L5 s+ ~
微信图片_20230221210502.png

* ^; ?7 q. b: }0 H3 Q
8 x1 I0 Z+ {- p. C

5 A5 z" S8 m, z" ~( _
五、导入SVD文件,恢复外设结构

& ?( o, v; v  H9 b  ~. I4 H- u1 _
但这还不够,我们还需要导入SVD文件,啥是SVD文件捏?
+ r  N+ u. B( Z& ]  p( X8 [  O" m
# B3 ^2 M7 [6 Q( m& O
微信图片_20230221210459.png
" X2 t6 J% s: q0 e- w: V8 \
' a; H4 [7 l* }# B$ o% ]6 ?" K. c
在IDA7.5以后,就自带SVD文件加载插件了,如下图:
4 i$ Z9 O( O6 t0 t
微信图片_20230221210453.png
7 w# F% _: {  d. V! T! P5 i/ x
# q3 v  J) l+ p( q* ~+ e3 R8 r
打开之后如下:

- j" t' A' Y6 f: v
微信图片_20230221210449.png

; X- y4 Y8 f" ]; |% d4 l
我们可以自行下载相应的SVD文件,或者加载GitHub上的仓库,我这里选择自行下载然后在本地加载。下载链接是这个:cmsis-svd$ i' F- t$ J8 ]% t: K2 y& C
选中想要加载的svd文件之后,IDA就会自动恢复bin文件中的外设结构,体现在伪代码中就是这样:

: w7 a! V& {% y  r# r
微信图片_20230221210446.png
6 u. t" Y  o: n" ^) b, Z9 F; l2 b' W& x" a$ Z3 Z
这样:

' h. V; O5 i" s; R$ d* d$ u9 I
微信图片_20230221210443.png
4 K& e- n% l7 _; t, p$ W
当然,也不是所有的外设都能恢复,和axf文件中的肯定是有差别的,但对我们的逆向已经起到了很大的作用了。
到目前位置我们已经做了如下几步:加载bin文件->设置固件加载基地址->添加sram段->恢复符号表->恢复外设信息。程序还原工作已经接近尾声,还剩下最后一步,恢复结构体。
; }. f7 U; i8 U/ n$ I$ l2 f2 g$ ?
2 ^: n# t" d7 x- w! i  x
六、恢复结构体
4 Q; m, v! _" c1 @
打开某个例程的axf文件,可以查看下其中使用的结构体。

$ p$ E1 ?9 B9 S8 U: A; |
微信图片_20230221210439.png

6 E2 h. N$ @" g( q
这些结构体中,有TIM_TimeBaseInitTypeDef,NVIC_InitTypeDef,GPIO_InitTypeDef,USART_InitTypeDef,GPIO_TypeDef等是我们经常使用的,用来进行定时器初始化,中断初始化,IO口初始化等等。
4 r$ f& R$ V2 H/ x$ [* b6 d; Z
我们生成c header file。

( g/ q: t7 ^+ k. |/ G) ^
微信图片_20230221210433.png
4 i6 f5 ?3 G( P+ }; W
然后在bin文件中解析生成的头文件:

6 D" e* P1 _! f# E
微信图片_20230221210436.png % D/ u  d& Z# {7 I3 ]

* w; E' l& Q+ ?% ?2 u1 [3 o0 r
然后在local type中就能看到不少结构体了。
$ G; j; i. n5 F  z  e+ N3 p) m# M
微信图片_20230221210426.png
. D3 i! `$ `) k: M# R! I+ \4 o3 B' J- Z( C6 }# a
光导入了结构体还不够,我们还得手动将IDA的伪代码中的变量重新设置类型,怎么确定某个变量的类型是什么呢?需要结合库函数的变量类型来设置。
% }* ^0 m8 m" k) Y2 x- Y' u
/ N' ~6 t! S& K8 f, Q
以GPIO_Init这个函数为例:

7 z' w+ f7 a$ d: {7 d) d
微信图片_20230221210422.png
- s, S8 V' _4 g( I7 ?: t$ g* _5 z7 c9 n

) L( B& R* Y7 `* E% ?) }6 k
我们打开固件库使用手册,找到GPIO_Init的原型。

! V* Q' @3 Z) g
微信图片_20230221210419.png
; P' N% Y/ g& [. h- ]9 h7 L
( z5 n% l, a6 O0 U, D4 _$ R
可以看到原型为:
  1. void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
复制代码

( |: f- D! D: D% a
第二个参数为GPIO_InitTypeDef类型的,因此我们将伪代码中的v10的类型修改为GPIO_InitTypeDef,剩下的结构体我们也一样可以使用这样的方法来恢复,只要有固件库函数,我们就能够恢复对应的结构体,效果如下:
( ?/ w& {" s- B9 ?# W  v# Y
微信图片_20230221210415.png
7 n2 Y# V2 k" b) D5 j
效果还不错。

$ q; t- z' J: w

0 D& u, K2 O# B2 F+ }
七、总结

  S% u* |- e/ r; y
此次逆向是基于我的一个小课设,功能不多,而且由于我的课设用到了xx原子的例程二次开发的一些库,而我用来恢复符号表时使用的axf文件也是xx原子的例程编译出来的,所以恢复出来的符号表的比例会偏高。如果能确定使用芯片型号,以及使用的是固件库还是hal库,那么按照此方法大致上能在一定程度上恢复固件,使其可读性大大增加。
9 x. E# J/ T3 @# \/ p3 ?
此次实验主要是起一个抛砖引玉之用,如果能给师傅们一些帮助自然是最好,如果师傅们有其他方法也请不吝赐教。

* o6 D# e; m& I/ @7 b8 E4 q
完整版请查看:附件
/ O. b: P2 Y# H& `! X! M

6 k8 K$ o# b/ q% t. f7 W% t# b0 x& v
赞 收藏 评论0 发布时间:2023-2-21 21:06

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版