固件逆向学习
2025-12-13 13:45:37 # iot

前言

由于接触到的各种设备的固件都有不同的架构,在逆向的过程中也会遇到各种各样的问题,所以打算写一篇文章记录下来。

奇奇怪怪的芯片和架构

芯片名称: Tlsr8251
架构名称: Telink
ghidra插件: https://github.com/trust1995/Ghidra_TELink_TC32
官方sdk:https://wiki.telink-semi.cn/wiki/chip-series/TLSR825x-Series/
开发文档:https://wiki.telink-semi.cn/doc/an/AN-21112301-C_Telink%20B85m%20BLE%20Single%20Connection%20SDK%20Developer%20Handbook.pdf

OM(昂瑞微)

芯片名称: OMEM6621
架构名称: arm-cortex-m4
开发文档: 需要找官方签署保密协议
官方sdk: 需要找官方签署保密协议

BK(博通)

芯片名称: bk3432

NuvoTon(新唐)

芯片名称: NANO100SE3BN
架构名称: arm-cortex-m0
官方文档: https://www.nuvoton.com/products/microcontrollers/arm-cortex-m0-mcus/nano100-102-base-series/nano100se3bn/?group=Document&tab=2

一些零零散散的小知识

需要自己新增segment

逆向omem6621芯片的固件中遇到的这个问题,一开始是通过中断入口地址来确定固件的基地址,但是ida一通分析之后只解析出来400多个函数,用bindiff恢复符号表后还有很多库函数比对不出来,那大概率就是少分析了很多函数。
比对了sdk编译出来的固件,得知了在ROM中还有两块区域需要单独抓到其他地址处,这样才能满足调用需求,所以要在ida中edit segment再move segment。

缩减固件体积

telink架构

telink旗下的产品分为两种,一种是自研架构,需要使用tc32 toolchain,还有一种是基于开源架构risc-v,采用risc-v toolchain。本模块以telink自研的架构为例。
通常来说,编译telink架构的固件可以使用官方提供的telink ide。但是我们的目的是为了缩减固件大小,ide更改编译选项已经不能满足需求,所以这里直接使用makefile编译
makefile的内容无关紧要,注意一下使用几个优化固件大小的编译选项即可
接着可以查找cstartup_825x.s文件,这个是系统的启动文件,用于堆栈的初始化配置,中断向量表以及引导程序。
在文件中可以找到下列的代码,这一段初始化代码调用了大量的tmov r8, r8指令用于延时,显然会对固件大小造成负担,这里进行优化,删除tmov r8, r8指令又或者是采用其他的延时方案都可以

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
@********************************************************************************************************
@ LOW-LEVEL INITIALIZATION
@********************************************************************************************************
.extern main



.org 0x20
.align 4
.global start_suspend
.thumb_func
.type start_suspend, %function

start_suspend:
tpush {r2-r3}

tmovs r2, #129 @0x81
tloadr r3, __suspend_data @0x80006f
tstorerb r2, [r3, #0] @*(volatile unsigned char *)0x80006f = 0x81

tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8

tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8

tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8

tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8
tmov r8, r8

tpop {r2-r3}
tjex lr

__suspend_data:
.word (0x80006f)

接下来的思路是优化代码本身,这里只是提供一个优化函数的思路,仅供参考。
在控制gpio接口时会用到gpio_set_func函数,经过比对这个函数的调用会占用大量字节,找到drive/gpio.c,查看这个函数的实现。根据不同的参数情况进行了多种if分支,如果固件代码中调用的gpio_set_func只走一种分支,又或者我们不需要调用gpio_set_mux函数也可以达到一样的效果,这里就可以将gpio_set_func中的函数拆分开,从而节约固件大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void gpio_set_func(GPIO_PinTypeDef pin, GPIO_FuncTypeDef func)

{

    unsigned char   bit = pin & 0xff;

    if(func > AS_GPIO){

        gpio_set_mux(pin, func);

    }

    if(func == AS_GPIO){

        BM_SET(reg_gpio_func(pin), bit);

        return;

    }else{

        BM_CLR(reg_gpio_func(pin), bit);

    }



}

如何寻找调试接口

要想获取一款设备的固件,通常可以从拦截固件更新包,或者是通过电路板的调试接口来下载固件,那么寻找电路板的调试接口就成为了固件逆向的重中之重。
调试接口有多种标准,例如JTAG,SWD等,不同标准要找的调试接口数量以及类型都不一样

JTAG

SWD

telink私有调试协议

telink芯片采取自己的私有调试协议进行固件的烧录以及提取,为单线调试协议,只需要将烧录器的swm接口和电路板的sws接口连接再将gnd相连即可
541eea45c234adfdf95824366702d47b.jpeg
以上图中的电路板举例 调试接口一般都是扎堆存在,所以重点关注红框圈起来的部分。
4个的部分在电路板背面可以看到有标识其他用途,所以先排除
那么就是在左侧的7个接口中找到sws和gnd调试接口,这里可以用万用表来找到
首先将万用表调成蜂鸣挡
IMG_20250812_172815.jpg
接着将黑表笔接到电路板的负极上,红表笔依次接入接口,如果发出蜂鸣声说明红表笔当前所指的接口是gnd接口
随后用遍历的方式来寻找sws接口,如果可以读取到flash芯片那么就找到了调试接口。

固件分析的经验之谈

Arm架构m核心的启动

很常见的一个知识点,arm架构中的m系列芯片,主打的是一个低成本高能效的需求。常用于小型设备中,比如家用电子设备等。
Mcu复位后需要一段启动代码来引导程序,而存储于固件头部的地址信息,称之为中断入口地址。
image.png
从上到下分别是:

1
2
3
4
1. __initial_sp 栈顶
2. Reset_Handler 复位中断
3. NMI_Handler NMI
4. HardFault_Handler 硬件错误

通过将栈顶赋值为sp寄存器,随后跳转到复位中断函数,初始化Mcu时钟,然后执行main函数。
image.png
所以通常可以用这个知识点来判断固件的基地址以及main函数的位置

ROM LIB库

在嵌入式设备中,将稳定且不常修改的功能模块预先编译为二进制代码,烧录到设备的Flash或ROM区域,称之为ROM Library。
应用程序运行时,通过地址跳转或者符号引用来调用函数和访问数据。
一些芯片的官方文档中,通常会随着sdk一同给出lib文件。

多地址启动机制

这个机制第一次看到是在采用telink芯片的设备中,可以在flash中烧录多个固件,bootloder根据标识位来决定启动哪个固件。
以telink为例,默认的启动地址是0x0开始的,此外还可以从0x20000,0x40000开始启动。
这一机制的用处,我个人认为是为了方便dfu升级,dfu传入的新固件可以存放于0x20000处,这样就不需要删除原本的固件也可以做到更新固件了。
那么还有一个问题,bootloader是如何判断该从哪个地址启动的呢。根据的是启动地址的0x8偏移处的4个标识字节
image.png
bootloader会先从0x0开始读取这个位置的值,如果符合那么就启动该位置的固件,否则继续往后。

Prev
2025-12-13 13:45:37 # iot