非Linux固件加载地址分析
一、引言
在分析固件的时候,有时候会遇到binwalk无法解包的情况,查看信息发现它是非Linux文件系统,那么有可能固件是实时操作系统,关于实时操作系统,常见的有Vxworks、eCos、FreeRTOS等。这里以Zyxel的RGS200-12P_1.00(ABEP.0)C0固件为例,使用binwalk分析结果如下:
$ binwalk RGS200-12P.bin
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 eCos kernel exception handler, architecture: MIPSEL, exception vector table base address: 0x80000200
128 0x80 eCos kernel exception handler, architecture: MIPSEL, exception vector table base address: 0x80000200
5444512 0x5313A0 Unix path: /home/remus/svn/ivs/IVSPL5-ZyXEL_New/src_0809/src/build/../build/obj/ecos/install/include/cyg/libc/stdlib/atox.inl
5444581 0x5313E5 eCos RTOS string reference: "ecos/install/include/cyg/libc/stdlib/atox.inl"
5511365 0x5418C5 eCos RTOS string reference: "ecos/install/include/cyg/libc/time/time.inl"
......
二、固件分析
1、反编译
由上面的分析结果可知固件为eCos实时操作系统,并且架构为MIPSEL,那么直接使用Ghidra反编译工具分析看看吧。
选择架构MIPS小端,点击OK后选择不进行自动分析。
通过参考链接[3] 我们可以知道实际基址为0x80040000,我们点击内存修改按钮修改基址,然后再进行自动分析,分析的过程比较长。
分析完后,选择菜单Windows-> Defined Strings,然后选择一些字符串进行查看
这里选择了“%-3d”字符串,然后找到对应的引用函数,点击进入
找到引用字符串的函数
多找几处类似的,这里截取部分代码查看:
80046804 57 80 04 3c lui a0,0x8057
80046808 21 28 00 02 move a1,s0
8004680c 1b e7 01 0c jal FUN_80079c6c
80046810 50 cb 84 24 _addiu a0=>s_%-3d_8056cb50,a0,-0x34b0 = "%-3d "
......
80111a90 5a 80 05 3c lui a1,0x805a
80111a94 21 20 60 02 move a0,s3
80111a98 b0 28 a5 24 addiu a1=>s_Error:_%s!_805a28b0,a1,0x28b0 = "Error: %s!\n"
汇编代码分析,以指令57 80 04 3c为例,因为是小端排序,实际的指令可以理解为3c 04 80 57,其中3c 04为操作码(lui a0),80 57为操作数(0x8057),其他的指令理解方法类似。
地址 | 指令 | 指令解释 |
0x80046804 | 57 80 04 3c | lui a0,0x8057: a0=0x80570000 |
0x80046810 | 50 cb 84 24 | addiu a0,a0,-0x34b0: a0=0x80570000 - 0x34b0=0x8056cb5 ("%-3d ") |
0x80111a90 | 5a 80 05 3c | lui a1,0x805a: a1=0x805a0000 |
0x80111a98 | b0 28 a5 24 | addiu a1,a1,0x28b0 a1=0x80580000 + 0x28b0=0x805a28b0 ("Error: %s!\n") |
由上,可以得出:
函数中表达式里面的字符串地址 = 实际字符串地址
2、加法的规则
至于50 cb 84 24的操作数cb 50为啥变成负数-0x34b0,我们可以写一小段汇编测试一下:
lui $a0,0xa123
addiu $a0,$a0,0x0001
nop
lui $a0,0xa123
addiu $a0,$a0,0x7FFF
nop
lui $a0,0xa123
addiu $a0,$a0,0x8000
nop
lui $a0,0xa123
addiu $a0,$a0,0x8001
nop
lui $a0,0xa123
addiu $a0,$a0,0xFFFF
使用gcc编译,并用objdump进行反汇编
$ mipsel-linux-gnu-gcc -c test.s -o test.o
$ mipsel-linux-gnu-objdump -d test.o
test.o: file format elf32-tradlittlemips
Disassembly of section .text:
00000000 <.text>:
0: 3c04a123 lui a0,0xa123
4: 24840001 addiu a0,a0,1
8: 00000000 nop
c: 3c04a123 lui a0,0xa123
10: 24847fff addiu a0,a0,32767
14: 00000000 nop
18: 3c04a123 lui a0,0xa123
1c: 24848000 addiu a0,a0,-32768
20: 00000000 nop
24: 3c04a123 lui a0,0xa123
28: 24848001 addiu a0,a0,-32767
2c: 00000000 nop
30: 3c04a123 lui a0,0xa123
34: 2484ffff addiu a0,a0,-1
可以清楚的看出操作数80 00以后就是负数,那么操作数80 00等价于-32768=-0x8000,操作数80 01等价于-32767=-0x7FFF,操作数FF FF等价于-0x1,计算方式为:
当操作数80 00 以后的值,实际数值=操作数-0x10000
3、结论
当基址正确时,实际字符串地址等价于基址 + 相对字符串地址:
函数中表达式里面的字符串地址 = 实际字符串地址
= 基址 + 相对字符串地址
那么基址为0时,实际的基址计算方法如下:
基址 = 函数中表达式里面的字符串地址 - 相对字符串地址
有了上面的分析,很清楚的找到对应关系,如下图所示:
找到函数中表达式里面的地址(记作sif_addr)、相对字符串地址(记作sid_addr),当sif_addr有多次引用的时候,此地址大概率为实际的字符串地址,根据内存分布,相对字符串地址+基址一般不会改变末尾3位,即234,最终遍历全部的条件,排序出匹配次数较高的sif_addr,用sid_addr - sid_addr = 实际的基址。
三、自动化代码
(1)匹配大小端,找到mips汇编的常见代码,如jr $ra:如果08 00 e0 03那就是MIPS小端,反过来的就是MIPS大端。
(2)匹配字符串,找\x00开头和\x00结尾,中间均为可见字符就是字符串:
(3)匹配lui $a0, 0x????; addiu $a0, $a0, 0x????等表达式,取出其中的地址
(4)找所有可能的基址,使用函数中表达式里面的字符串地址 - 相对字符串地址,统计排序。
根据以上分析过程编写代码,执行代码分析基址,效果如下,可能的基址为以下地址(列出前10,根据采样量的情况都有可能为实际的基址),示例程序的真实的基址为0x80040000,刚好在前10列表中:
$ python findimgbase.py -f RGS200-12P.bin
[*] check endian
mips little endian
[*] find string in Data
5055
[*] find string in Func
lui $a0, 0x????; addiu $a0, $a0, 0x???? 7050
lui $a1, 0x????; addiu $a1, $a1, 0x???? 7481
lui $a2, 0x????; addiu $a2, $a2, 0x???? 2835
lui $a3, 0x????; addiu $a3, $a3, 0x???? 7772
lui $v0, 0x????; addiu $v0, $v0, 0x???? 3481
lui $v1, 0x????; addiu $v1, $v1, 0x???? 1738
lui $t1, 0x????; addiu $t1, $t1, 0x???? 8844
lui $t2, 0x????; addiu $t2, $t2, 0x???? 892
lui $v0, 0x????; addiu $a0, $v0, 0x???? 466
[*] image base address: 2043
(01) 0x80040000 70
(02) 0x80064000 15
(03) 0x80007000 13
(04) 0x800e1000 13
(05) 0x800a5000 13
(06) 0x8007b000 13
(07) 0x80087000 12
(08) 0x8005e000 12
(09) 0x80032000 12
(10) 0x80076000 12
示例脚本见gitee:
https://gitee.com/zhiwanyuzhou/IoTools/blob/master/imagebase/findimgbase.py
四、参考链接
[1] https://zhuanlan.zhihu.com/p/483751376