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

STM32固件逆向

[复制链接]
STMCU小助手 发布时间:2022-12-1 18:00
一、概括8 P2 Z1 t$ u6 o% w$ S2 O
用的很久之前做的一个电风扇的课设来做实验。
1 j; ?! h- X: V3 S% ?, G, q- y9 w- s
微信图片_20221129184848.png 0 W, r3 Z+ f' V/ T# t& s% f* q
4 g7 y' \) \9 C- A

0 g8 P3 g- G* u( B' U9 Y用到了外部中断,定时器中断,pwm。MCU是STM32F103ZET6。
' @' N0 v  h8 t5 ~: m9 N- O  }7 G4 V
微信图片_20221129184844.png 7 n- x" J6 n3 d
1 \+ U% A; d2 Z; K& x. a, l% {
1 `$ z, z9 k# D8 ~  t
在keil的设置中可以看到固件的起始地址,默认情况下为0x8000000。另外,keil生成的是hex文件,hex文件是带有基地址信息的bin文件,如果我们要生成bin文件,需要进行如下设置:
( t! s5 g- y0 m. G1 o3 L0 l! m% `
% b: T8 w9 Y1 z# O( f! t  e3 I- `* r  P0 B: ?. f" c
微信图片_20221129184839.png . S$ {. B/ _) P( `8 S, b/ S  u
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- |

6 _& ]% N1 O. A, H 微信图片_20221129184836.png * @1 T$ \0 o# P, n+ R
微信图片_20221129184833.png
; w7 W, `) L. ~% ~8 V2 q* L% f/ K1 k, T; Y+ C
几乎和源代码一摸一样,除了一些外设名用地址代替了。
( o  p- `2 f; E: M
6 t2 J* s. Y& C0 R再看到hex文件:) D7 p& W/ T: Y$ t; f8 W5 T' R, s
( T( L; [. V- J( z) T
微信图片_20221129184829.png
6 y; Y, ^+ {0 j
& w* T% M# \' T: s4 F首先需要将架构修改为arm小端序,再修改一下架构版本,stm32f103zet6是cortex-m3内核的,armv7架构。
7 X0 o! b+ y* `/ d  J* M
4 }# U  h( ^4 e6 G% n7 _( i" q1 J- J2 Z
微信图片_20221129184826.png
, J" ?/ o7 l: A2 P. f
/ {- x# T, d* g" `3 t0 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
微信图片_20221129184824.png 0 V& F3 q/ M# v, F4 d
* ]$ T8 v" g' L4 J% Y9 H3 |

9 L5 X# H. m5 M; n4 z# jIDA会自动识别出基地址,能识别出不少函数了,但伪代码是真的难看。1 J+ T, v  x% }0 E# D* e& C* b
0 l/ n& |2 s2 ]& K7 h
微信图片_20221129184821.png ) H: X" l, X8 Z" i. x$ S/ F7 j
9 x5 C  w8 t6 V0 ?

7 W8 C7 p' t% h7 r) t基本上是看不出个什么东西来。
/ v' O" \5 `/ A" p% [+ e( 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
微信图片_20221129184818.png
: T% h2 I- Y1 E) M. W6 T( `( ~1 T/ L* X/ {
bin文件去除了地址信息的hex文件,需要手动设置基地址。9 y2 ~0 k' O- T3 z  e
3 |; r7 A2 E: j7 }- y' D1 w

1 w1 {3 r; E' |6 Z2 V& J# f- P, s/ ~+ D5 Z- x3 r
二、手动设置固件基地址
0 B$ A$ I0 ^  F: W

, _# p/ O" W$ ^! T) g我们的分析将以bin文件为目标。
! [/ z, n+ R- ~  G3 r- v% e* s6 k
将基地址重设为0x8000000,这个地址怎么来的?可以看stm32f103zet6的datasheet。
( {# W/ b* j: _: g! q( X9 d# U$ w( e1 y) F: k, j7 S* a' ]
微信图片_20221129184815.png
9 \8 ~, I# W2 i) ~+ Q$ X$ ^- f: x  _8 G" b2 a* @
; N! o, P: J4 {) ]  b& ~
从0x8000000开始就是flash的空间。
6 S$ v7 g1 K2 P; X; K- I2 `' o4 F  d& k) F' g: r5 _
重新设置加载地址之后再将其按照代码解析就变成了hex文件的样子。: F  O& O, L& ]: b# z! q/ l9 }

) x! X' h& Y% c! S  m( ]
/ X0 [1 M( I" X6 D1 g+ s) }. I8 B 微信图片_20221129184812.png
6 z* O& u" C  i$ E8 R" f8 {3 x4 C& Y3 I4 j' s

4 P# y- u; n3 j但此时存在很多标红的地址,这是因为这些地址在IDA中并没有设置,因此IDA将其解析为非法地址,标红了,我们现在要做的就是手动添加一些段。
" l" [3 X4 W* c. A$ I- z# g
1 F# Y+ Q# B3 u; g9 S5 A
( ^2 w" q: V/ _8 k4 m, g" e5 n三、添加SRAM

! U- |& Q# p! L% o1 J9 a; M段在axf文件中,IDA解析出了如下几个段:
0 {! M) k) b3 t+ f! T0 P
( ]1 w! f  B0 n, X" [5 j9 s6 _ 微信图片_20221129184805.png & H5 M3 Q7 W+ @2 }

  g; t+ r+ X+ O! [8 f/ B" p单片机内存被总分为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(零初始化数据区)
4 p0 v5 Y. g7 r+ A8 M! K& nFlash 存储 code和RO-data
" d7 B+ V9 J" jSram 存储 RW-data 和ZI-data
, P) j  o9 j9 i  f  N
+ _6 X; N* Z, O; M
) z( R/ q) _9 O5 J2 ~$ i0 c在datasheet中一样可以找到sram的起始地址,为0x20000000,结束地址为0x2000ffff,我们自己添加段不必像IDA自动分析的那样详细,一个sram的段即可。) M% t  l3 Q/ [

9 L& Y) D" b# w) V5 q  N9 @' m* C也可以在IDA启动时设置好固件加载地址和sram的开始以及结束地址。! Z  k$ x+ s; M% e
添加了sram段之后,红色的非法地址消失了,如下:
7 o; X* z" ^$ o, g
- ~" C: m3 C4 n( S, E" C% C& u 微信图片_20221129184756.png
6 z& D" H* P; d! o$ l7 e5 b. n. x: Y; I+ }& B3 K8 `
7 w! ~/ u% }( g  ~* w  f
四、恢复符号表
" h- i) {# f) C2 y下一步,我们需要恢复符号表,由于bin文件不存在调试信息,所以IDA生成的伪代码几乎没眼看,为了方便我们的逆向,我们需要恢复函数名以及导入一些结构体。在这里就需要用到axf文件。
" T5 U5 X. z& U  d- s# V/ H  O  M$ E: 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$ \

. o  H% S; i6 c' i如果有闲工夫或者是对stm32的开发非常上手,就可以自己写一个demo,尽可能多的使用到各种库函数,然后编译出一个axf文件。我这里的话,由于好久没有用stm32了,开发起来有些生疏,所以就不自己手写了,我选择捡现成的项目。我学习stm32的时候买的是xx原子家的开发板,他们提供的例程也是非常丰富的,有五十多个,由易到难,涵盖了stm32开发的方方面面,因此我就直接拿这些例程中的一部分,编译出axf文件。比如下面这个内存分配的例程:
9 {7 t# M6 Z/ R: ~+ [( 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; _

9 M. E, V' {6 U% B/ U 微信图片_20221129184753.png
4 q. v+ r5 u, L7 Y: S5 \
  z% Z( K( b4 R9 i6 c: c/ |可以多选几个例程,能涵盖更多的库函数,将这些axf文件用IDA打开,然后生成idb文件。然后在我们的目标bin文件中,使用bindiff加载idb文件。9 W6 g: E% c" u9 a; ?
1 a$ f: @& K2 O+ \9 e. Z3 g* T
微信图片_20221129184750.png 3 U# j* ~' a5 L7 G1 P5 s0 P7 ?% _
5 N2 R/ u0 Q- v- \3 O# m; ]
% S6 j" v  @8 {, L/ T
选择一个idb文件,然后会出现这样一个比较界面:
& T3 h6 c2 V' K" u8 k% v0 t6 \$ ^* s4 _
微信图片_20221129184747.png , f# z( y7 @: x7 e1 g, p9 ?) _5 p4 \3 |' Z

* r7 N! a$ I, o2 F+ m5 k7 M% |5 q/ }; Q) j' e: n
选取similarity大的函数导入到bin文件中。
/ D6 @& s: t7 |9 g/ U- {* m% y  i3 D' K& z* r( V( \
微信图片_20221129184743.png 2 e1 ]* e* s& d
* {7 z+ Q/ L' D5 W7 c9 T9 m

3 w( |: a2 t9 I; j5 e; s! [& H: G导入之后实际上就能恢复大部分的函数名了。
5 G" ~2 `: e+ h1 c" a5 V; Y  J* W' p9 j7 u

/ T9 k; C* p$ c! E: H 微信图片_20221129184739.png   A, v2 I3 k, T$ G
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

2 y$ \# ]/ Q5 z) i多使用几个idb文件进行恢复后,已经可以看了。6 }2 ~7 L* ?5 ~$ `" I' Q* ]. V

7 j7 k: F! s% ~; H# d  X( L- x* f/ y' x4 l
微信图片_20221129184735.png ) e" S6 {% c! w

3 m+ m4 r/ E* Y) d2 \# 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
微信图片_20221129184732.png
/ N5 A, |: h: X( @3 E
! d) @- @. y2 {  E  y' s% {
* l' S7 W) j* @1 u# v1 p6 {5 E在IDA7.5以后,就自带SVD文件加载插件了,如下图:
# r# d- b( o+ j) i) ^9 g8 C- l3 z5 _) Q
& Z1 I" }4 @3 z1 ~
微信图片_20221129184728.png 2 D9 B# d: H! M7 {# B( V5 \5 P

0 T2 ~+ G" u( }7 T7 M; U- N( Y1 ]打开之后如下:
" n  \& Z, Q# ]' x- V, w' l5 o! Z' c) n1 f& \
微信图片_20221129184723.png % O7 K0 W- X! G" s

! q( S) ]( f# ]2 ]* U8 [& ]# `" 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

+ v1 T1 F; r3 K; M6 w4 E5 P" W* J 微信图片_20221129184720.png ) w: l3 \$ g' u0 x; \% W( h2 K7 q
: |* 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
微信图片_20221129184710.png ( t8 c/ g6 N  l. i7 g  ]
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文件,可以查看下其中使用的结构体。
1 {( A% A3 a6 B- m! I7 H. U+ D
4 A) h8 {: {! B  b1 O 微信图片_20221129184705.png
& Z% P! `' e2 ~0 ]; g4 o
  t, I5 j7 Y" s4 R$ b# ~# u这些结构体中,有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

9 |4 ^3 Q+ k% [7 W' g; g 微信图片_20221129184702.png
+ I0 D- x% }! R$ N$ Z9 Y: B' ]$ s' ]; i( N% N# p: G0 O! |
然后在bin文件中解析生成的头文件:
6 l9 h3 \, R+ ?' r; Q0 z  Z3 B2 `" [2 I1 b2 {) `
微信图片_20221129184659.png ! z4 \5 L/ N- w5 r) q8 I+ O

" j$ j# s4 y+ O/ U; K然后在local type中就能看到不少结构体了。& z$ R" @. T! r! ^
( i3 ^" ]7 j( q  k! n! H" y
微信图片_20221129184654.png
$ r. x. q5 V# E0 w9 q0 w
* h- |8 }: n; P* t* B" ~光导入了结构体还不够,我们还得手动将IDA的伪代码中的变量重新设置类型,怎么确定某个变量的类型是什么呢?需要结合库函数的变量类型来设置。1 _9 G; N9 v* s, p# Z0 V7 D* E+ N' F

+ v" S, {! r6 K7 U以GPIO_Init这个函数为例:. s' Z" }- |% s7 t/ v
& i1 Z9 s$ X) F; A6 ~3 `
微信图片_20221129184650.png
2 b, z6 Q5 U' h& p; l! P1 l! J) ~3 k3 f3 D4 C9 l
我们打开固件库使用手册,找到GPIO_Init的原型。
+ l) W" v# I" v% f
0 ^+ @5 {7 Q: i. c) C& B8 |' t/ e( v" C8 s, S; b; b- ^
微信图片_20221129184646.png 5 }) A! u' X6 {3 q% ?& Z$ Y

5 h# ?& y6 L6 F3 n4 |% X1 d可以看到原型为:
: y; H, _, h" K9 n% \: N! 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

: Q4 a0 t# T7 G; Q$ t. O. k" D1 C9 |: W8 r; ], n
7 D: l# A9 A' B: i4 |

- I, X9 C# f3 F0 u/ S效果还不错。% g4 ^% R9 A3 y9 u8 Q1 a

- g1 m! z6 o6 ]/ l* p; R' Z9 o- J
七、总结
* q% ^( ?0 `; W6 ~此次逆向是基于我的一个小课设,功能不多,而且由于我的课设用到了xx原子的例程二次开发的一些库,而我用来恢复符号表时使用的axf文件也是xx原子的例程编译出来的,所以恢复出来的符号表的比例会偏高。如果能确定使用芯片型号,以及使用的是固件库还是hal库,那么按照此方法大致上能在一定程度上恢复固件,使其可读性大大增加。
0 _+ _$ F% |6 P& F( ]! l! E: z3 y* F" a9 y: V: [$ j, ?4 q
转载自:Lpwn, ~4 w* R& g3 `$ ]

" S$ H' B! d3 T; \1 Z. X1 g% k2 |4 P  {
微信图片_20221129184800.png
微信图片_20221129184404.png
收藏 评论0 发布时间:2022-12-1 18:00

举报

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