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

STM32固件逆向

[复制链接]
STMCU小助手 发布时间:2022-12-1 18:00
一、概括3 I- m* I: t$ w
用的很久之前做的一个电风扇的课设来做实验。
4 h4 J/ n9 A3 |6 p* x2 S0 Q- `& Z6 }- J. c/ G+ Y: T8 x
微信图片_20221129184848.png 0 w' V0 M0 L6 M; e; ?
5 l' B: Q$ v0 x2 f2 W

, F* }% x' O" E2 E" s  M用到了外部中断,定时器中断,pwm。MCU是STM32F103ZET6。3 Q$ O$ W7 q- J2 m  g
  b1 n7 j- I. ^: `% E# _+ P
微信图片_20221129184844.png
0 ^- J1 F: K) q3 p0 y: }+ |" w" B- O6 s, o. b. |2 A9 P6 N
$ P+ P+ [" \1 [6 |# s* z% ?
在keil的设置中可以看到固件的起始地址,默认情况下为0x8000000。另外,keil生成的是hex文件,hex文件是带有基地址信息的bin文件,如果我们要生成bin文件,需要进行如下设置:8 t" y* @$ }( f/ s9 m

% X/ c, {" T' h" m5 D& Z1 f4 R
# e3 }" C6 M  f+ \& [ 微信图片_20221129184839.png * O/ S9 b' Q- ~, X9 j# @
6 x. H. P$ A" Z" H6 x  X
另外,烧录到mcu中的固件是不带符号表等调试信息的,即hex和bin文件都不带调试信息,因为mcu的内存有限,但keil会单独生成一个带有调试信息的axf文件,我们可以以这个axf文件为基准,对照着恢复bin文件或者hex文件,下面我们来观察一下这三个文件的区别:- U5 j/ m* Z. B. i. e' D
首先是axf文件
; n6 h  m1 g9 G, q7 ^8 L7 h, m, ?
1 Z0 a9 u( G6 F+ }  |1 p2 e9 C 微信图片_20221129184836.png 0 W% W, x; w- M, M% c' q. c
微信图片_20221129184833.png : z; E: p0 U7 v. [8 O* F: q% `( M# b
% ~$ u' q$ ~1 ~6 A9 b4 R1 ~
几乎和源代码一摸一样,除了一些外设名用地址代替了。
/ m; I9 Q+ c$ w/ v1 p; t
' o2 j& A( w, c5 h$ Z' I5 T再看到hex文件:
/ f6 j% o/ T" f2 ?6 Z2 X
# x9 w+ Y, w) U9 N. k 微信图片_20221129184829.png ) I# m+ F, F3 o# w) Y5 K

% g. N, m3 M5 T首先需要将架构修改为arm小端序,再修改一下架构版本,stm32f103zet6是cortex-m3内核的,armv7架构。
  w! _6 d4 Q2 X  l+ j6 R9 W# s9 d/ Z" `& k5 v# j% @, p# Q
$ x; Q8 W' o* l; O
微信图片_20221129184826.png
9 V/ B# i$ i- P2 N0 t$ h! G8 k6 n

; O7 t1 s$ O3 q3 g修改完后加载hex文件。; G( u% l8 ^( u/ _/ F
% Q$ d2 \) S  ]- f8 Q# d9 \8 H

' [( e" J+ Z- U 微信图片_20221129184824.png 7 ]4 q1 [* v( D8 B

3 K7 D& h' l$ d6 }! r& P6 G/ Z5 e& H
IDA会自动识别出基地址,能识别出不少函数了,但伪代码是真的难看。# ?4 L% k8 \/ O0 t0 K# B* e! o
$ w& v8 }) c# b' ^; J
微信图片_20221129184821.png / x8 O+ B; W% l# B, b6 i
1 p' c8 K  B% Y; O: z5 M

3 X5 F+ r& m1 m% x0 K+ A6 H基本上是看不出个什么东西来。
2 U, |7 |4 x. D" j2 P) t5 m) @/ z1 v  d; f% L
最后是bin文件。+ L( _9 H/ `$ e& U8 V, I3 Q

& q$ Z4 I8 u" R# z( {
& ~8 @) I$ s! h) p+ Z- A: k 微信图片_20221129184818.png 1 ~7 c$ ~% p6 N8 f' }3 _* Y

" l7 p$ I! ^) ]/ k* zbin文件去除了地址信息的hex文件,需要手动设置基地址。4 t) Z5 v5 h2 ]2 ]+ y

# c+ F7 n* k2 t  m, H3 t3 ]' h' Y1 V6 j! P3 `
3 P: L0 ~7 a1 z
二、手动设置固件基地址
0 F& w9 f2 \( N* n9 P7 g) m1 _

# E) ]! g" Z8 _" A" ~3 e( {& m我们的分析将以bin文件为目标。, N1 B8 P% R2 m! y4 p

: V2 I  T9 w) p2 Q. s/ V将基地址重设为0x8000000,这个地址怎么来的?可以看stm32f103zet6的datasheet。
. O7 A, y2 c7 ~. h' M$ s9 \$ P' y  H( Q' r; q* ~
微信图片_20221129184815.png 6 \4 p- u; O! ]) G
7 L. @5 g$ f+ n( O- ~! w- f% C4 q
# p5 B% U" J1 `, F) @
从0x8000000开始就是flash的空间。- |# {+ w+ i8 _% j6 U( H7 ~

! y, r% r: }* }$ Z2 p# i& h9 ]重新设置加载地址之后再将其按照代码解析就变成了hex文件的样子。' o9 s& {; j' Z$ c$ }! w; k9 |

, s% w6 |: G0 x2 t( n
. h) c1 S6 n* h0 M) s$ ]6 `8 C 微信图片_20221129184812.png ( R3 U2 ]8 t! Y$ e, B* {+ a
" a7 f) g( ^* a; Z
+ I4 u) g* G6 @
但此时存在很多标红的地址,这是因为这些地址在IDA中并没有设置,因此IDA将其解析为非法地址,标红了,我们现在要做的就是手动添加一些段。' O/ f0 Q  |& r

2 g; E  ^4 z# t, g5 D
. L9 O, e) |) ~2 ?/ {三、添加SRAM
5 A; ?3 V$ x8 s# ?* v
段在axf文件中,IDA解析出了如下几个段:
$ U! A" y  [7 O& I& x5 V* J5 Q5 w+ V/ d) K, m: [8 S1 ]" [
微信图片_20221129184805.png
5 Y( E( d) b; M' t" A+ _
; z2 C7 M) `9 L% a: |4 Q单片机内存被总分为flash(rom)和sram(ram),flash里面的数据掉电可保存,sram中的数据掉电就丢失,sram的执行速度要快于flash,flash容量大于sram上方的最低内存地址,最高地址,都是在flash和sram中。9 |, @: ~# a( b) [# j
7 G" H: x% F% E% L, a/ C
我们正常下载程序都是下载存储进flash里面,这也是为什么断电可保存的原因。$ }7 n/ E( T6 ^* B& a4 ]
5 R6 R0 o7 n" H% H
单片机的程序存储分为code(代码存储区)、RO-data(只读数据存储区)、RW-data(读写数据存储区) 和 ZI-data(零初始化数据区)* a- u8 L- k. ]; B# P- m
Flash 存储 code和RO-data
6 i: n/ \$ z. F: s7 ESram 存储 RW-data 和ZI-data5 q$ ^! F' `" e! z' r9 H
- |0 G2 j; I9 Q/ N5 Q2 v, O
, H: _5 p( {2 S; ?, A/ C/ m' R
在datasheet中一样可以找到sram的起始地址,为0x20000000,结束地址为0x2000ffff,我们自己添加段不必像IDA自动分析的那样详细,一个sram的段即可。8 I+ s$ T* H* a% _

( [, O/ e9 t% l. v% s1 y也可以在IDA启动时设置好固件加载地址和sram的开始以及结束地址。
+ E0 t9 O$ F0 R8 g. A6 o; t添加了sram段之后,红色的非法地址消失了,如下:, X  p+ ?7 N6 \# M% c( j* M+ U
# F! Y4 R6 D8 k3 i5 ?5 R( R! z
微信图片_20221129184756.png
: T8 }! s- W; M! @7 v' e# |
" S: o) C/ ?/ d9 T$ ~( k" \) F' k& l; ^9 |6 |' t
四、恢复符号表- q7 R6 D( B- O; E% \
下一步,我们需要恢复符号表,由于bin文件不存在调试信息,所以IDA生成的伪代码几乎没眼看,为了方便我们的逆向,我们需要恢复函数名以及导入一些结构体。在这里就需要用到axf文件。/ [7 h" [% Z# A+ {6 u4 T' c4 x  t7 h) V# p

6 N$ |; K) x1 @8 D在前面我们看到axf文件几乎和源代码是一样的效果,里面有丰富的调试信息,当然,我们不使用这个课设的axf文件来恢复bin,这样就太刻意了。
' v; E  i% P8 E  X
- X+ n3 `. N' p. T0 q- N# b由于我这个课设使用的是库函数来开发的,所以就不能使用stm32cube生成的hal库来恢复。具体该怎么恢复?我们采用bindiff来恢复符号表。' T) `  u. C) x* g% d( U

( ?6 g5 d& p9 \4 @5 ^如果有闲工夫或者是对stm32的开发非常上手,就可以自己写一个demo,尽可能多的使用到各种库函数,然后编译出一个axf文件。我这里的话,由于好久没有用stm32了,开发起来有些生疏,所以就不自己手写了,我选择捡现成的项目。我学习stm32的时候买的是xx原子家的开发板,他们提供的例程也是非常丰富的,有五十多个,由易到难,涵盖了stm32开发的方方面面,因此我就直接拿这些例程中的一部分,编译出axf文件。比如下面这个内存分配的例程:8 K% C. j0 q/ G

9 z0 N: Z% M$ Q" W7 a
& _/ J- m( y. j. N& q9 c3 I; |2 c1 _
又比如这个pwm的例程:) p9 X# Z% k, C) y/ w# V

# r: |" p$ r: w1 D! A 微信图片_20221129184753.png , F" \* K2 d# A' v5 r/ a

: o* x# k' `6 y可以多选几个例程,能涵盖更多的库函数,将这些axf文件用IDA打开,然后生成idb文件。然后在我们的目标bin文件中,使用bindiff加载idb文件。: I! K- e) W5 }+ ?  d8 n* m
+ G0 w3 n8 M+ M" u) ?$ q
微信图片_20221129184750.png
- V* l3 P. `& Z$ j- _  T1 E4 _$ I8 u  z

( m# {4 Y' v% j  ^5 b选择一个idb文件,然后会出现这样一个比较界面:
( J; P4 i) ~  {2 @
0 f  G6 C7 d' D' |6 L2 f) ~- H3 Y 微信图片_20221129184747.png
) m, c" `, |0 K# D
/ U! {3 }! o% f1 h2 S+ X$ ?) T$ ]- E" k
选取similarity大的函数导入到bin文件中。
7 U. p! }7 ]( b
1 W! M4 O6 L. X$ D 微信图片_20221129184743.png
0 Q. j# d0 b* U* z5 {; W& _( a: e" s' O. [$ y, H/ g
* z$ O; h: M& N" a4 p
导入之后实际上就能恢复大部分的函数名了。
( d8 e3 D; I, T! X3 h, T
2 z( O( P0 j# w* R+ e
, g) V" t/ y; j1 h/ C, A) a* c 微信图片_20221129184739.png 2 v+ Q1 j: o8 X' W" u* t9 o

, J. F" t& c" k# Q5 V& U
2 O- a; u" x6 y: e但实际上,由于我这个课设的开发中使用了一些xx原子封装好的初始化函数(相当于xx原子对原本的库函数做了二次开发),例如uart_init,delay_init这两个函数均是二次封装后的函数,所以恢复出来的函数会比一般情况下多一点。
* n+ g- d& T% d% o3 P- T6 S& j5 k  Y) S* I4 y
多使用几个idb文件进行恢复后,已经可以看了。
! h2 _& h9 y8 Z  y- h; G1 z/ O8 H* K6 g% ]

5 Q7 `! n' W( Q9 }8 o 微信图片_20221129184735.png ( P4 _, u# x0 r# g$ y: S  }

3 o5 c: K, z8 R5 C2 l5 {
( J1 E) d7 U5 [1 R  C) F* {五、导入SVD文件,恢复外设结构* c, `2 t' B0 \
! C. a/ I6 A+ j8 j
但这还不够,我们还需要导入SVD文件,啥是SVD文件捏?  k6 T; Z, @) J5 D, w) N: B

" K0 r, B. J) j) m+ P! h$ m 微信图片_20221129184732.png * c; f* v' c$ ~/ f4 D! k$ B* g9 B

9 N: s1 H+ \9 N( K+ Q- C) b3 {* F$ m& ]  O7 g: S% F7 S$ N
在IDA7.5以后,就自带SVD文件加载插件了,如下图:
/ O, ]: }, s2 ?3 F" w: p; z) c7 ]1 `& l* x+ x5 G7 r
0 P  }4 c5 z' r1 b. ^; t7 v3 {
微信图片_20221129184728.png + ]3 V3 M- v" P. s% {

% N/ ~* W- F/ B0 V# u  J2 _打开之后如下:6 C/ y' R/ @1 [& J. `" D  f
# v& g. k& O6 k* k( p
微信图片_20221129184723.png 9 d( {8 v% r: V$ [4 a1 e# D! D
9 U9 N+ L% P0 g9 Y

# w6 ?1 f( [- M0 ^4 C0 q: g我们可以自行下载相应的SVD文件,或者加载GitHub上的仓库,我这里选择自行下载然后在本地加载。下载链接是这个:cmsis-svd! D- H: M- {6 u0 G, ?
选中想要加载的svd文件之后,IDA就会自动恢复bin文件中的外设结构,体现在伪代码中就是这样:2 O* J$ w/ j1 j* n/ u" `4 V
$ R; p9 o" v: D7 ~0 |
微信图片_20221129184720.png
, J4 d3 n$ e3 f) w
% d5 M0 v8 Q2 \9 C6 K$ I! {- Z* z8 w7 T8 q9 {
这样:
, L% x# A0 U; x
# m8 L: m; b  n3 A+ f2 g1 z/ ?$ ^4 Z
微信图片_20221129184710.png # n: d! ?; p9 J
* D; _5 U. @6 m: f1 I, y3 x; p$ K2 H

3 C' l* G4 P9 j) e当然,也不是所有的外设都能恢复,和axf文件中的肯定是有差别的,但对我们的逆向已经起到了很大的作用了。* b2 G$ U( x! O3 a7 P7 s# k. I5 t
# E$ q0 V  F0 R" D. P0 e) J
到目前位置我们已经做了如下几步:加载bin文件->设置固件加载基地址->添加sram段->恢复符号表->恢复外设信息。程序还原工作已经接近尾声,还剩下最后一步,恢复结构体。
! B$ B) {; d$ M+ W. b8 ?$ k1 z9 S6 `* p. ?
六、恢复结构体. S% z1 e( c5 P( w
打开某个例程的axf文件,可以查看下其中使用的结构体。
$ m( i8 u# Y* r
! ~, f0 t5 ]2 H# m$ g 微信图片_20221129184705.png   }5 J1 v6 D* W7 h

+ w3 F. d; y4 K, q2 W这些结构体中,有TIM_TimeBaseInitTypeDef,NVIC_InitTypeDef,GPIO_InitTypeDef,USART_InitTypeDef,GPIO_TypeDef等是我们经常使用的,用来进行定时器初始化,中断初始化,IO口初始化等等。
0 F# l# x/ R/ ^9 e$ h
$ g6 S7 d, `3 n3 M我们生成c header file。3 t( p8 F8 C0 u- ~2 _1 f3 h; Z
7 N7 c; f7 A( W* r+ }1 f
微信图片_20221129184702.png
6 J6 A8 C# k  G" S! D- V1 w  t; ^8 D) K
然后在bin文件中解析生成的头文件:6 b) J2 Y% p# v' c' A# r
4 ^% k1 x/ q/ u7 m; R' h6 {) K' ~
微信图片_20221129184659.png
. P9 ?. e; g3 l  C0 \. n$ R  A6 H- \8 `* d
然后在local type中就能看到不少结构体了。3 a* Z7 h2 {& \
; K# H, O- O1 m4 q
微信图片_20221129184654.png / q, V1 U1 [' h3 c, [
6 X2 y5 S- ~7 x
光导入了结构体还不够,我们还得手动将IDA的伪代码中的变量重新设置类型,怎么确定某个变量的类型是什么呢?需要结合库函数的变量类型来设置。
# N, i( t9 m$ O& D, X
% ~& g) ^8 J# s. I1 s& c  G( x) ~# Z; ]$ t以GPIO_Init这个函数为例:7 E" n: z6 J6 I: x+ i5 F. Z+ L
3 G4 E, z" f+ j6 X% @
微信图片_20221129184650.png 5 M7 m- v. @8 }: c+ P* y
. [2 H6 q1 n  a" R
我们打开固件库使用手册,找到GPIO_Init的原型。% @& p; w- N* b+ \: v

6 |# Z6 y) Z! m0 G
1 \( _& n  x) `+ ^0 r& L8 B1 R 微信图片_20221129184646.png + N$ |' D6 l  x4 Z9 [  ?1 C  g/ r

0 w: o1 F3 \& h+ j4 P8 B可以看到原型为:# o1 j3 _6 I7 }7 H7 F5 q
4 D. Y& ^3 N8 O& z6 A- r8 W' g# k
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)" @9 I8 w9 j- O( d8 q( `5 `
; \" i  V' Y5 _5 j! Z
第二个参数为GPIO_InitTypeDef类型的,因此我们将伪代码中的v10的类型修改为GPIO_InitTypeDef,剩下的结构体我们也一样可以使用这样的方法来恢复,只要有固件库函数,我们就能够恢复对应的结构体,效果如下:$ C2 Y/ q( |- ?# ^9 G
, ~+ \5 f! e  ?- r0 U8 `- W: M
; F$ {' U" y  F5 Z$ ~" S
8 `. i' e' I2 B4 p! o. d7 N0 @6 h
( y" c& G2 D- q' g

5 t; }% k- _$ r+ X, U效果还不错。6 s  t- P" @" c

1 ^; Z" `! o: D# i  ?
6 \- d( e1 p8 D; y$ C0 b+ G, K七、总结
. h$ X* R1 C+ i此次逆向是基于我的一个小课设,功能不多,而且由于我的课设用到了xx原子的例程二次开发的一些库,而我用来恢复符号表时使用的axf文件也是xx原子的例程编译出来的,所以恢复出来的符号表的比例会偏高。如果能确定使用芯片型号,以及使用的是固件库还是hal库,那么按照此方法大致上能在一定程度上恢复固件,使其可读性大大增加。
. W+ I8 ?7 E! S( \& O( b# t$ w, o9 W
转载自:Lpwn
5 T+ A$ ~% s! B/ _0 S  s7 B
2 Q5 H8 w& S- ]! Q! d4 U
, ]4 B! I6 a" v8 N
微信图片_20221129184800.png
微信图片_20221129184404.png
收藏 评论0 发布时间:2022-12-1 18:00

举报

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