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

STM32固件逆向

[复制链接]
STMCU小助手 发布时间:2022-12-1 18:00
一、概括# x+ X& E; U* W9 t
用的很久之前做的一个电风扇的课设来做实验。
' y0 Q! x% }8 O. b( D& C* a7 L& r* r
微信图片_20221129184848.png
+ O1 c2 B0 w. P6 }/ N# Z& X. }0 Y" c8 n

8 C1 Y* g5 g( L: @1 u6 E: ]用到了外部中断,定时器中断,pwm。MCU是STM32F103ZET6。" e7 i7 s, [& }6 i/ n
' q+ [# J- H1 }' u
微信图片_20221129184844.png 4 k8 F/ b& U0 W, n: t: L5 I
1 r) W6 h: f/ r  x

) V! z9 k0 e9 z8 h) R) b在keil的设置中可以看到固件的起始地址,默认情况下为0x8000000。另外,keil生成的是hex文件,hex文件是带有基地址信息的bin文件,如果我们要生成bin文件,需要进行如下设置:$ s" s" x0 E6 ?0 L

  n8 v$ e2 O) |9 r9 {" A/ W4 m% J- a- x* w- p, C. W) j2 r9 o2 C" A
微信图片_20221129184839.png 0 l$ S1 q7 s9 M' D  a, ^" J2 Y2 h

, q& }- ?) J# ?6 i另外,烧录到mcu中的固件是不带符号表等调试信息的,即hex和bin文件都不带调试信息,因为mcu的内存有限,但keil会单独生成一个带有调试信息的axf文件,我们可以以这个axf文件为基准,对照着恢复bin文件或者hex文件,下面我们来观察一下这三个文件的区别:; H' t! h: y/ q- A. m' R5 l( O7 B
首先是axf文件
( O1 o3 q. G# @/ x5 |0 @% R# I4 F3 Z7 Z
微信图片_20221129184836.png 8 a7 G2 n5 M& f" w1 m( y* }
微信图片_20221129184833.png " h1 @2 V  c$ V2 ~! _5 b
" u: n& Z# [' ]) U9 {2 d
几乎和源代码一摸一样,除了一些外设名用地址代替了。
% v+ ?. h' O& c+ h% X; J$ Y) f# r6 V2 D3 B/ y, G5 l5 M0 o  [" U
再看到hex文件:
% C/ n. O: G, \: A
* S" P5 U! i: k) s  z& T9 J 微信图片_20221129184829.png
/ {2 R; p# P- R  E
/ ~- q& h9 r8 M6 f  q% D首先需要将架构修改为arm小端序,再修改一下架构版本,stm32f103zet6是cortex-m3内核的,armv7架构。4 c7 j- x" ^9 p: P
  n- Q5 L& ^9 Y: S) J6 `" C* z3 K* j

8 X& p% ?% D' N4 U. A$ _ 微信图片_20221129184826.png # l' A5 b  q/ H" Y) B) r

% E/ V$ J4 r/ e5 z* \7 D
6 r& ]) C( D5 c- e修改完后加载hex文件。4 b) n' I3 J: K$ ^6 ~+ ]2 A

3 o, A7 |0 i3 S3 c
$ w! d1 i* v6 z2 Q  N 微信图片_20221129184824.png
# Z& f: d% O9 p; c6 p  p, g; O
  L, ]7 `, \$ d( Y
& z6 t8 |5 E( H8 d' W% B+ qIDA会自动识别出基地址,能识别出不少函数了,但伪代码是真的难看。
) e# S! D9 t+ o) ?1 N+ A- P2 r
+ G5 I" X' Q6 Q; x: j 微信图片_20221129184821.png
: D' H" |3 W, M6 w/ s
- z1 U( g+ R" T  i1 t, \
3 ?3 M. r( h2 f' e  R7 Z, v基本上是看不出个什么东西来。
3 f  z0 ^* I- y- c  d! p0 `
' `4 b) p* d+ k! u, E: P7 K6 Z7 t最后是bin文件。+ g4 E8 k2 ?! y) s, k& [9 a- F
& i( D3 J- R4 x$ t2 ~7 r: `$ T
8 m# Y9 N+ M7 B6 F/ l
微信图片_20221129184818.png 3 L( }9 o5 O" f8 O1 P) j1 w! X) C: ]
9 q$ a; ]' Z4 `" j
bin文件去除了地址信息的hex文件,需要手动设置基地址。
  P4 T* [7 o/ m* R. O! ^1 x& a! S1 {8 S3 {
! H3 c2 G6 q2 s1 I. O, p; e1 J; `  S7 D

4 H) v$ h4 K& ]9 v1 c$ o+ d二、手动设置固件基地址
, v- p, ^: r- [; {( s
/ M; K6 ~; Q( }- H
我们的分析将以bin文件为目标。
) A: d: Y4 V! z3 x6 g5 Q
$ c# z  I! h6 ~* k将基地址重设为0x8000000,这个地址怎么来的?可以看stm32f103zet6的datasheet。
; w+ ]- W& j8 P, G0 h2 T4 ?5 p, A* P# B4 _# S8 _# W% s
微信图片_20221129184815.png
7 K4 \+ |3 I; V) a. j4 c  j
0 z$ E7 p. G  a; w% z
# q9 p9 i, |3 K" M) i从0x8000000开始就是flash的空间。
& d% w, Y7 k- p( B8 P1 o8 S! Y7 S" L- ?  ]
重新设置加载地址之后再将其按照代码解析就变成了hex文件的样子。
+ u& A4 D" m/ o* @  S$ l( z  l/ @+ M% N( y: C2 K5 a

! b% D8 B5 P% c; u8 t8 T! Z' e 微信图片_20221129184812.png
3 m$ b8 K7 A: n8 c/ d
& u! w# P, {/ d% J
( I9 U, e, O! M% `5 J但此时存在很多标红的地址,这是因为这些地址在IDA中并没有设置,因此IDA将其解析为非法地址,标红了,我们现在要做的就是手动添加一些段。+ p2 z6 c7 }0 n) t, j: L' J9 l
" B" k' ?) ?' L( T1 O
7 x* s: n1 ~+ h% F! n6 l; u/ W
三、添加SRAM

" c# x; l7 h6 B段在axf文件中,IDA解析出了如下几个段:
0 H. b) c& S. t6 v% T1 E  ^8 X8 }5 J7 k# s- ^: n! c+ F% }
微信图片_20221129184805.png 0 b3 {* N3 P" ]  x; E5 T

2 C3 Y' J% n* Q6 q( h) C单片机内存被总分为flash(rom)和sram(ram),flash里面的数据掉电可保存,sram中的数据掉电就丢失,sram的执行速度要快于flash,flash容量大于sram上方的最低内存地址,最高地址,都是在flash和sram中。
/ o* W6 A* ^/ k! [3 D% l9 v7 ]3 f% o. J) [
我们正常下载程序都是下载存储进flash里面,这也是为什么断电可保存的原因。" N6 z0 `  o6 F  P: g( a2 d
4 a( x# N- Q/ ?- u) g" j0 P  t
单片机的程序存储分为code(代码存储区)、RO-data(只读数据存储区)、RW-data(读写数据存储区) 和 ZI-data(零初始化数据区)
! M) k- @% b+ O: i- T/ U' H! a, {( oFlash 存储 code和RO-data% ]+ v! X  R8 x0 s5 [6 T+ V
Sram 存储 RW-data 和ZI-data
2 R7 e$ P3 X: d5 Z+ `* k# R, p
+ m5 D6 p0 `7 E* u; ^8 O; X" o  `5 Z3 _' ~" Y
在datasheet中一样可以找到sram的起始地址,为0x20000000,结束地址为0x2000ffff,我们自己添加段不必像IDA自动分析的那样详细,一个sram的段即可。3 N1 A2 y" ~* @; z  p
: f: |# o$ H' n4 C+ ^) a7 r  y
也可以在IDA启动时设置好固件加载地址和sram的开始以及结束地址。
, o" B: [6 q/ r1 S3 f& W添加了sram段之后,红色的非法地址消失了,如下:3 g3 K5 m8 l9 L% ]5 f. n9 R

2 W4 h7 s1 ^* f, Q 微信图片_20221129184756.png $ B# r  E) \" P( A, d9 |
) m% j9 C6 K* \% N. u( y6 W

4 g- n" ^  o4 v7 R/ j$ H( `四、恢复符号表! t- y. _0 B1 r# q
下一步,我们需要恢复符号表,由于bin文件不存在调试信息,所以IDA生成的伪代码几乎没眼看,为了方便我们的逆向,我们需要恢复函数名以及导入一些结构体。在这里就需要用到axf文件。  J0 n0 g" m3 C& k4 \2 g9 ]) J
. V7 `; e7 r* k# U% X! D$ a
在前面我们看到axf文件几乎和源代码是一样的效果,里面有丰富的调试信息,当然,我们不使用这个课设的axf文件来恢复bin,这样就太刻意了。: h6 B0 u! w7 F8 u
% \' s! a3 H6 _: T/ n! E
由于我这个课设使用的是库函数来开发的,所以就不能使用stm32cube生成的hal库来恢复。具体该怎么恢复?我们采用bindiff来恢复符号表。3 F. q& f7 S& y, `" D. C

) I+ o% i9 d. A7 |6 i9 T% {如果有闲工夫或者是对stm32的开发非常上手,就可以自己写一个demo,尽可能多的使用到各种库函数,然后编译出一个axf文件。我这里的话,由于好久没有用stm32了,开发起来有些生疏,所以就不自己手写了,我选择捡现成的项目。我学习stm32的时候买的是xx原子家的开发板,他们提供的例程也是非常丰富的,有五十多个,由易到难,涵盖了stm32开发的方方面面,因此我就直接拿这些例程中的一部分,编译出axf文件。比如下面这个内存分配的例程:
5 e0 d$ i6 q) G3 O
) B2 F$ P# R, z" C7 H% `. F* b  k( k0 T" Q" g+ m# ]
' @1 q5 d) g1 }
又比如这个pwm的例程:. ?; L/ G" M9 b% X
# |7 p* I: R4 I/ [  X4 t& V. K9 @$ m; E
微信图片_20221129184753.png
0 z( h- `0 ?/ s$ \, k% i: S" f9 b$ Z- y
可以多选几个例程,能涵盖更多的库函数,将这些axf文件用IDA打开,然后生成idb文件。然后在我们的目标bin文件中,使用bindiff加载idb文件。( E/ J+ P. o1 x# o; k# ~2 [

& d0 h/ J3 G- ^5 ~/ n' U 微信图片_20221129184750.png
$ k6 @8 }" P6 d) U7 `, e  S3 l( d' n4 q# d1 E
* r4 H7 r6 g$ n3 u) P
选择一个idb文件,然后会出现这样一个比较界面:" T$ J* ^1 ^+ ]" ^  W3 a

! C' z; Z, M, U 微信图片_20221129184747.png . X/ m* o; P# j5 y4 ~5 k3 ]. v
* p2 M- N! Z9 N) u* P8 g- v

; k* E4 E' `8 n% Z9 T. e3 W选取similarity大的函数导入到bin文件中。
6 ?+ D, N$ c' A. g' w: L
' s5 m% a9 ^' z+ k/ ~ 微信图片_20221129184743.png
7 l# R# h; S1 T" {3 f* y+ y0 H8 u" B  h$ {4 K- a
% n! C: b$ J3 K7 R$ |- |
导入之后实际上就能恢复大部分的函数名了。
& l' X. w5 l& p* _
; F& y$ }0 \+ X6 f
- W& l* d4 t3 A' y5 c: ?9 B7 \ 微信图片_20221129184739.png 7 O3 w$ H+ J6 X

* I) F+ T  L7 \. Z2 n! E
5 G" W1 `; r9 \但实际上,由于我这个课设的开发中使用了一些xx原子封装好的初始化函数(相当于xx原子对原本的库函数做了二次开发),例如uart_init,delay_init这两个函数均是二次封装后的函数,所以恢复出来的函数会比一般情况下多一点。8 ^- |& o! ]5 @) |+ N4 c

( B8 M) L! T6 m$ b" r5 b+ G多使用几个idb文件进行恢复后,已经可以看了。
, l" A/ z1 |) a6 ]2 a/ L) B. a4 J) X! h, p6 M

9 n1 k% n9 l! d3 Q8 }& Y 微信图片_20221129184735.png
0 }! S4 f* o4 N' T4 b9 Q
6 F! {7 [& g) x+ n5 W
  v7 Y1 q/ W- t9 w五、导入SVD文件,恢复外设结构
  e8 N4 c' m" W5 H

6 Y* s/ x: v# X; _5 V; l. `1 _8 m$ w但这还不够,我们还需要导入SVD文件,啥是SVD文件捏?
. S& P  L/ T" F7 I  Q; i
* o; c/ i$ n3 K& e# H 微信图片_20221129184732.png
. P, V: X: r/ @3 B6 g
; z* c* e, @; J: C& V8 }% z1 A$ ?8 H5 o& x, T: v
在IDA7.5以后,就自带SVD文件加载插件了,如下图:- e6 \& E" O0 T3 m
( F1 }8 W1 H4 g# d2 {( H4 H

$ Q, z$ Z! D  Y2 c  N5 ^+ v 微信图片_20221129184728.png 2 U3 j; r0 ]8 N
% a2 j0 X. o: [- F  j5 a) J
打开之后如下:
1 p+ m6 t1 z+ H) O" j, h' X' |7 j% p4 @
微信图片_20221129184723.png
& U0 S8 p, U! ]1 a: e
4 _5 ~  E+ T$ J! k; B6 `
" A# I3 p7 H& G# R3 m" @. l; @我们可以自行下载相应的SVD文件,或者加载GitHub上的仓库,我这里选择自行下载然后在本地加载。下载链接是这个:cmsis-svd7 h' b6 p% N; D0 W7 S0 ^
选中想要加载的svd文件之后,IDA就会自动恢复bin文件中的外设结构,体现在伪代码中就是这样:# J( X. K6 }( r, s: `% h

3 @' g5 T3 N9 Q6 q' ^ 微信图片_20221129184720.png , n$ b% Y/ \6 @# n4 M( e

4 v+ X) \* Q* @9 [9 `$ ?8 a9 C$ `. D' g; x( Z) l: @$ U9 `
这样:
; m0 v1 n  g; V% c9 X$ R
' j8 \' K" Y* x5 J5 M, m( |# \/ J1 D% X5 H% h' [6 J
微信图片_20221129184710.png ; V# @3 B/ W5 n/ {
7 ^8 Y% ^8 y3 f' l( d& |/ }0 ?
. e$ S( z+ P6 P  F
当然,也不是所有的外设都能恢复,和axf文件中的肯定是有差别的,但对我们的逆向已经起到了很大的作用了。
3 p: p) n, H+ S7 H1 S, x( }7 @; [. i) o
到目前位置我们已经做了如下几步:加载bin文件->设置固件加载基地址->添加sram段->恢复符号表->恢复外设信息。程序还原工作已经接近尾声,还剩下最后一步,恢复结构体。6 d/ h5 X8 a! H3 ^6 P

: d% h. x+ u+ S- f, m( i六、恢复结构体: I4 s5 Q6 m5 z- w( N/ ]
打开某个例程的axf文件,可以查看下其中使用的结构体。& N: S# d% Q0 X: X( O

2 D5 |2 O( @6 m  _" }5 j2 B 微信图片_20221129184705.png * Z$ W1 N' V# J" E: Y
8 p, j, t8 |) M6 ^- o  ]$ Z" J
这些结构体中,有TIM_TimeBaseInitTypeDef,NVIC_InitTypeDef,GPIO_InitTypeDef,USART_InitTypeDef,GPIO_TypeDef等是我们经常使用的,用来进行定时器初始化,中断初始化,IO口初始化等等。
& @. q) x0 F8 H' {+ p- p, q( M
/ R: s+ A/ Q8 K) E我们生成c header file。& D) W* u8 ]- _* v# w* S, b: J

$ L+ q1 C& s$ d$ t 微信图片_20221129184702.png ( M8 \6 I0 B3 ~/ l1 D
& J+ k' p! p- C. ]7 Y% D7 t
然后在bin文件中解析生成的头文件:
# _5 H' B* z9 Q/ ?2 q, C9 Y4 ]6 }" o; `
微信图片_20221129184659.png
9 e0 G- {7 O( I- v* k8 `8 S* a( H- j" o
% s6 X8 n# I' ~* D# M然后在local type中就能看到不少结构体了。
. ~# m1 n7 X: L. B$ s+ g' ^- r
. T0 k& I  P+ n' E  r9 p 微信图片_20221129184654.png
+ s6 a9 H( m  e6 C4 D- F
+ N! @+ ]3 u1 i8 S. z" I& `/ d光导入了结构体还不够,我们还得手动将IDA的伪代码中的变量重新设置类型,怎么确定某个变量的类型是什么呢?需要结合库函数的变量类型来设置。. j( _' p, \8 O# n: Z, A6 }9 w& N
: ?) s1 ]3 Z- Z$ F3 R. M
以GPIO_Init这个函数为例:$ L  i8 Z  _4 V+ E3 S, P" n
- w& j0 j. }6 _$ m% b; L8 v# {
微信图片_20221129184650.png
0 ?$ k/ ]& D2 g
* t4 b, X) e- o( W# O: i4 F我们打开固件库使用手册,找到GPIO_Init的原型。
! ?# w9 y6 m* A( K- K/ s2 A! ]; T: H
+ L1 A7 z% o2 y
微信图片_20221129184646.png
$ a6 F! l1 w" n' U+ @7 J# E4 u- W2 u5 b
可以看到原型为:1 H% h( ]2 [( U- Q* L, ?# i& g# Q

' U$ Q. x8 ]. N" Xvoid GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct)
+ j* Y; }! {9 P5 y+ H2 e
) B2 [, i% o$ g! x5 y- P第二个参数为GPIO_InitTypeDef类型的,因此我们将伪代码中的v10的类型修改为GPIO_InitTypeDef,剩下的结构体我们也一样可以使用这样的方法来恢复,只要有固件库函数,我们就能够恢复对应的结构体,效果如下:5 _1 v+ o5 B& Z- Q* M/ z: K3 {

% P$ |. a) L! b9 @, c
0 v; T3 y1 R* C' O
- R! j) i2 [2 t' V. C
* O% b2 p1 _1 e8 ?" A* x& d' b7 f1 q( P: {2 X! i" b9 i) C+ K
效果还不错。8 a/ R8 J+ N4 L1 r

, @/ e2 W  Q: \6 @8 u- J  X9 e( g/ z" b3 _5 d
七、总结# |( u3 X0 i  x, r
此次逆向是基于我的一个小课设,功能不多,而且由于我的课设用到了xx原子的例程二次开发的一些库,而我用来恢复符号表时使用的axf文件也是xx原子的例程编译出来的,所以恢复出来的符号表的比例会偏高。如果能确定使用芯片型号,以及使用的是固件库还是hal库,那么按照此方法大致上能在一定程度上恢复固件,使其可读性大大增加。" R/ o7 E5 |) a* {7 k2 N4 e% _

9 ?* t) m! M# y" y2 h; f转载自:Lpwn# v1 K/ c/ g+ ~* ^

8 p- E) c) S( h
  M( w# t* e/ D! O+ p
微信图片_20221129184800.png
微信图片_20221129184404.png
收藏 评论0 发布时间:2022-12-1 18:00

举报

0个回答

所属标签

相似技术帖

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版