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

STM32固件逆向

[复制链接]
STMCU小助手 发布时间:2022-12-1 18:00
一、概括/ B. W$ |: s; x+ q3 D0 K2 A7 _
用的很久之前做的一个电风扇的课设来做实验。: ^% \) K7 f# W# Q4 `

* }6 I  U4 \* I! h: z: T 微信图片_20221129184848.png 2 W7 g" t  o+ ^8 c" r) }
) 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
微信图片_20221129184844.png
% h7 \- U  _( @! X6 A7 M
. E! I, I3 _: I8 g
( x0 T' t4 Z/ D在keil的设置中可以看到固件的起始地址,默认情况下为0x8000000。另外,keil生成的是hex文件,hex文件是带有基地址信息的bin文件,如果我们要生成bin文件,需要进行如下设置:
. H, e+ B$ r6 b! s7 j6 C, _1 I9 l. u9 e4 ^7 }: @9 x
( V. f1 E1 o! Q2 G/ Z
微信图片_20221129184839.png
6 g8 `+ n  v6 E& W$ A, _) O, z2 h6 F- _$ C4 X* }- n9 ~9 h
另外,烧录到mcu中的固件是不带符号表等调试信息的,即hex和bin文件都不带调试信息,因为mcu的内存有限,但keil会单独生成一个带有调试信息的axf文件,我们可以以这个axf文件为基准,对照着恢复bin文件或者hex文件,下面我们来观察一下这三个文件的区别:
2 z/ S$ E& k' t& ^+ \# |首先是axf文件9 M4 x( ^( }! @4 q! K1 e

6 h) x; E8 j& [4 w 微信图片_20221129184836.png
* ^$ G  L* L  }2 ^8 b 微信图片_20221129184833.png $ ]( }& T7 Z0 p, M8 `( v6 \. s
1 Y# C# J: X( y. t* b8 Q
几乎和源代码一摸一样,除了一些外设名用地址代替了。
4 \2 m7 Z# E1 ]5 h5 a, w8 L$ ^) D: b- o, n
再看到hex文件:5 h; ?' ?7 j" |" q2 @

4 e; }4 E) O  C" ]' M: K; ?  x 微信图片_20221129184829.png
( }7 [' b$ w; D8 x% Z4 m2 s7 l$ C+ Q# D6 ~6 D8 P- U
首先需要将架构修改为arm小端序,再修改一下架构版本,stm32f103zet6是cortex-m3内核的,armv7架构。
2 T# H+ l$ F9 x+ K
( G2 x1 w# T; b) a! m* y! N) F8 n
微信图片_20221129184826.png
, C8 Y) g# M" j0 c. h
# ?, F' H( k; d+ F1 a. _
' u1 \7 ~3 d; U7 G0 v8 |修改完后加载hex文件。6 K  C) w! u6 T  R! y
2 @. D- k! ^) u( o

+ P  T1 P/ g9 V8 Q 微信图片_20221129184824.png
2 E. `5 a) [( G/ x! i
( {" H$ j2 _( j6 J; Y
! I( V& L4 g% D2 E& ~IDA会自动识别出基地址,能识别出不少函数了,但伪代码是真的难看。
& K9 k2 \1 f* p) y& k
6 E8 d- ]" g0 _- m8 X' I( m% O 微信图片_20221129184821.png . s! p& l% ^, i" v, |. H' u7 X+ h
1 j' V* \8 J1 R7 V  |. N. B

& B- l# d$ F9 p+ k基本上是看不出个什么东西来。
; @$ S. F+ t- N( \9 Y6 s; r% L6 m. p# C6 q8 I! E; `; Z/ |
最后是bin文件。
7 a2 d1 {  v5 b. {4 x# w7 m5 y! O7 O
( l1 g' a! s% ^* }' [, }  v
微信图片_20221129184818.png
5 y9 X4 b2 G* b: G, @: X1 c, N  H6 S( G  H2 Y
bin文件去除了地址信息的hex文件,需要手动设置基地址。
& u$ I  z* p: T: f# N/ a* n, |! W1 }  w

" V' e/ l: {, X/ N/ Z( {- V, 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

% u# w: e3 u2 d7 H; O 微信图片_20221129184815.png
+ ~) t9 z; w7 P: Z2 S$ v( e6 j0 L  e( Z" J# u7 Q6 h

- W, ^0 a: }( d, l$ q1 ]从0x8000000开始就是flash的空间。3 J9 e0 S" G3 c/ w% H3 z- D# W

+ F& W% p2 k- H  C2 a重新设置加载地址之后再将其按照代码解析就变成了hex文件的样子。
* c$ E+ e' J3 d( x
: V; Z/ U4 p' y; w, k3 o/ z( c6 J% r4 C6 l4 L5 g# K) L
微信图片_20221129184812.png
6 w$ e% ~: m; `, o+ K/ ^3 u
5 w0 A- q; u2 w3 p8 F/ F$ |. V$ m: 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

. s/ F6 v5 @: x5 ~, U段在axf文件中,IDA解析出了如下几个段:2 e  A2 K5 J# e8 \7 b8 M0 E! ~5 V
2 C9 ~. Z  I7 ~1 P$ @5 {& s3 S2 y
微信图片_20221129184805.png ! I- ]- j  W9 Y$ i% Z

3 Y$ V& H' b2 _: \5 A7 ~单片机内存被总分为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里面,这也是为什么断电可保存的原因。
9 W  W! U# r7 C' }; R9 ^! e: E; E7 Q: M  _- O5 ?; y- j
单片机的程序存储分为code(代码存储区)、RO-data(只读数据存储区)、RW-data(读写数据存储区) 和 ZI-data(零初始化数据区)
% S- E, `% `; Z/ J) A2 F) x# |# @Flash 存储 code和RO-data( Z- @4 b% G- Z' L
Sram 存储 RW-data 和ZI-data
1 p, t% P# v% y6 t) o1 _3 S6 |2 ]+ `0 k

- t1 X8 }! V* H9 d* e) h" x1 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段之后,红色的非法地址消失了,如下:
2 h" [. u- F' ^% t+ Q; w
* @! r1 B' ]- W8 v9 m 微信图片_20221129184756.png
; ^% r" ]- G. ]0 z* J9 c4 S! p  F7 T2 h7 Y- i

4 A2 R1 l3 v9 T+ P4 P四、恢复符号表
% l  Z  c' C  a$ Y# b下一步,我们需要恢复符号表,由于bin文件不存在调试信息,所以IDA生成的伪代码几乎没眼看,为了方便我们的逆向,我们需要恢复函数名以及导入一些结构体。在这里就需要用到axf文件。+ u" e# d& b. t

  j& J5 M7 F% d8 M+ J4 Q在前面我们看到axf文件几乎和源代码是一样的效果,里面有丰富的调试信息,当然,我们不使用这个课设的axf文件来恢复bin,这样就太刻意了。9 U* s1 D6 B% G3 |6 L* {8 T3 X$ K

! k' I2 ~) m& a: n- Y5 n由于我这个课设使用的是库函数来开发的,所以就不能使用stm32cube生成的hal库来恢复。具体该怎么恢复?我们采用bindiff来恢复符号表。
' {6 x4 _% [( j% a
/ l5 z' i# i& |1 x6 f" }如果有闲工夫或者是对stm32的开发非常上手,就可以自己写一个demo,尽可能多的使用到各种库函数,然后编译出一个axf文件。我这里的话,由于好久没有用stm32了,开发起来有些生疏,所以就不自己手写了,我选择捡现成的项目。我学习stm32的时候买的是xx原子家的开发板,他们提供的例程也是非常丰富的,有五十多个,由易到难,涵盖了stm32开发的方方面面,因此我就直接拿这些例程中的一部分,编译出axf文件。比如下面这个内存分配的例程:% U0 N: H2 A% v. J
% K2 F* F- E* B0 ?

8 _9 ?- W2 X& z
& y0 P! v5 K; a8 g3 I又比如这个pwm的例程:9 z- ~- D) L0 A" N+ i5 K
& c4 V- F* n5 `' k9 G% H1 n1 T, d
微信图片_20221129184753.png
4 V4 u" }2 O8 u# f
$ F& s/ }( C4 I/ i; ~% w可以多选几个例程,能涵盖更多的库函数,将这些axf文件用IDA打开,然后生成idb文件。然后在我们的目标bin文件中,使用bindiff加载idb文件。# p' a) V* Y) @

9 v" ]* _& ?+ x. ]- h 微信图片_20221129184750.png
1 {  X2 t# t9 ]# N/ L" D+ @
+ Q; u1 T9 n9 b9 [- u8 \" P1 u. ^8 @9 w- K# K& _/ @
选择一个idb文件,然后会出现这样一个比较界面:
, _% B# ?& G* O% F% v
9 Z% w6 x4 t7 T: S 微信图片_20221129184747.png
+ X0 y% l* r: R! W  p6 h/ K
0 e/ y. }8 R' Z! J5 M
) ^) D; b5 ?7 T( Q4 P' Q4 W选取similarity大的函数导入到bin文件中。
3 ~  z  q. i  H6 R' v2 F* V3 J7 K1 d! Q6 O7 R; A
微信图片_20221129184743.png ' B! n* P2 h  u; |- }  U
6 b/ _2 L1 K! U+ S3 q2 ~
+ z, f/ C+ L$ b8 E* O, E
导入之后实际上就能恢复大部分的函数名了。
# C  o. Y: l# |" o8 V$ Q. W1 x6 b6 }- V

$ |1 W; Q4 k2 n7 f7 b: n+ z 微信图片_20221129184739.png
4 ]& ]( S! W- E1 C+ p7 o) `9 _: h: a) D! X/ y# m+ k
' T8 O, M; n7 W* A) [# W5 p
但实际上,由于我这个课设的开发中使用了一些xx原子封装好的初始化函数(相当于xx原子对原本的库函数做了二次开发),例如uart_init,delay_init这两个函数均是二次封装后的函数,所以恢复出来的函数会比一般情况下多一点。
, O( P# z1 P* C$ Q
2 S) X( M% U8 n. ]+ a5 F多使用几个idb文件进行恢复后,已经可以看了。# P: G: S, B$ J8 M9 U/ z% C

' |0 n9 \8 ^8 q2 H7 m8 Q! I+ m/ o8 t- E* E. \
微信图片_20221129184735.png   @2 k; d* [2 U+ r& [3 e- v
' |" i" P# J4 D

0 a. U# R/ ]5 O% r7 ~五、导入SVD文件,恢复外设结构7 Z/ h* B6 Z  m1 l7 n

: W: i3 E7 @4 f3 {3 F& X但这还不够,我们还需要导入SVD文件,啥是SVD文件捏?
& R( M/ V5 k7 u( z5 k
+ n. N8 B1 ?% g' V7 Z* f+ V& P 微信图片_20221129184732.png & D6 p" Y, w* Z6 s2 N
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

  C% Y# k) S8 V* h: g. i) {; G& N6 V7 q, x9 k+ x
微信图片_20221129184728.png
" h1 w0 a" _9 V% N: ~  _
- W* ?* R4 x7 R打开之后如下:
8 T4 e2 \2 |3 T' R  L, `6 e; B8 s& L% D
微信图片_20221129184723.png
$ Q5 |7 t% D0 x( C# K5 y( w
" e6 n2 k  b$ H1 n' }" G% t, s- w( R. K7 d4 l. e( s
我们可以自行下载相应的SVD文件,或者加载GitHub上的仓库,我这里选择自行下载然后在本地加载。下载链接是这个:cmsis-svd  B" I2 J2 v: Z) _
选中想要加载的svd文件之后,IDA就会自动恢复bin文件中的外设结构,体现在伪代码中就是这样:
, t. w8 n6 i& u
$ c) M/ C) q  e. R( ~4 W7 O3 Q 微信图片_20221129184720.png
; b5 q3 t. a+ R+ \( ~5 M, q7 O, c) F# W$ m+ c. k/ O5 ?
* N) ]8 U6 }; k4 L4 n& c
这样:
' i# G, c5 `9 V7 U: Q# r* p
7 D: ?! R) A. ]1 F. G, m4 P+ R3 C7 C/ B$ S
微信图片_20221129184710.png + e1 q$ d7 f* T# Y, f/ q! W
' e' w5 n: }5 M' {7 \2 L  X
7 ]6 E7 z. V0 X' G: ?
当然,也不是所有的外设都能恢复,和axf文件中的肯定是有差别的,但对我们的逆向已经起到了很大的作用了。  ?+ t+ S0 v8 T9 {# Y

! i8 q6 _1 S# W/ \# i到目前位置我们已经做了如下几步:加载bin文件->设置固件加载基地址->添加sram段->恢复符号表->恢复外设信息。程序还原工作已经接近尾声,还剩下最后一步,恢复结构体。
! L; n5 D2 k) P# Q2 E$ ]; b; f
/ @) S2 N( M7 A六、恢复结构体* w& H! R4 L, o5 m. d
打开某个例程的axf文件,可以查看下其中使用的结构体。# o  ^; b9 `" ]( e
9 E. g8 [  k' y& B- O# k
微信图片_20221129184705.png
7 ~6 y$ a1 ?# c, c7 r# R7 S% }
' h3 p& K3 w. g# @这些结构体中,有TIM_TimeBaseInitTypeDef,NVIC_InitTypeDef,GPIO_InitTypeDef,USART_InitTypeDef,GPIO_TypeDef等是我们经常使用的,用来进行定时器初始化,中断初始化,IO口初始化等等。
# M4 u1 G! O4 t5 K# ]/ I) i
% j; A) m* U3 K. k% S7 E2 U- p0 P- F我们生成c header file。
) m/ f5 P# j. O6 ]1 ?
# h8 _3 D& S8 \6 \6 M% @ 微信图片_20221129184702.png
( U( y" p7 f( g$ k0 t$ W" |1 v: q3 L4 _5 E2 J8 n8 i1 O
然后在bin文件中解析生成的头文件:
/ x& P( S3 |" |6 }0 n1 v  D
$ f* N0 ]5 L: M- e' S 微信图片_20221129184659.png
0 W' L$ {  t! l& C% y* T
! E4 d3 u9 G* h, O6 L然后在local type中就能看到不少结构体了。2 b$ Q9 w0 h2 L2 _5 V

! r6 r' w! L: F- s5 X7 U0 \ 微信图片_20221129184654.png , R# z% `7 H3 R0 S1 ]: F+ T+ w# v
. l' k! z0 z* J: D/ l5 x' A
光导入了结构体还不够,我们还得手动将IDA的伪代码中的变量重新设置类型,怎么确定某个变量的类型是什么呢?需要结合库函数的变量类型来设置。
# r% V8 |6 P' D2 q8 g
6 S$ q1 }: [7 M" m7 D4 \以GPIO_Init这个函数为例:6 @0 f- t; q3 N

' h3 v6 ~; S( \, A1 U 微信图片_20221129184650.png
/ T# z6 [5 w) r) U7 `0 o! y9 [9 k# ]9 A, ^, ~
我们打开固件库使用手册,找到GPIO_Init的原型。
; d$ i( Y# e( d# x/ T8 w4 R9 o0 e, `; G. o

: D1 x4 W& e9 R! D. z: i 微信图片_20221129184646.png
$ O. X7 @4 F# h: C5 R5 S% F5 A( C3 q3 R. v2 D
可以看到原型为:
) L8 o" w  Z3 X: 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

0 x; V% V+ B1 Y; B第二个参数为GPIO_InitTypeDef类型的,因此我们将伪代码中的v10的类型修改为GPIO_InitTypeDef,剩下的结构体我们也一样可以使用这样的方法来恢复,只要有固件库函数,我们就能够恢复对应的结构体,效果如下:$ k0 g' n# S6 d' X" Y

5 n$ O$ i- M! a  P, C* z' p4 i, A. v# K; e

4 q7 J+ N$ \8 l$ x1 X" b; l" G4 l) j8 s# ^8 r/ M

" ]( i4 P# }, ^4 N. ]8 _效果还不错。
5 Z8 b$ _0 H6 ^" K5 v9 E7 s. |3 }0 b

9 k/ D- ~1 ?" U. i七、总结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 h' w3 c" A7 i& A4 ?9 B) M: n, o' z! K& q- j
7 u; O7 |2 m- Y( a
微信图片_20221129184800.png
微信图片_20221129184404.png
收藏 评论0 发布时间:2022-12-1 18:00

举报

0个回答
关于意法半导体
我们是谁
投资者关系
意法半导体可持续发展举措
创新和工艺
招聘信息
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
关注我们
st-img 微信公众号
st-img 手机版