获取到一个设备的固件,有6M,基本没在网上找到对该固件进行分析的文章,因此决定按照固件分析的一般思路对该固件进行逆向分析,实践学习下。主要用到的工具binwalk、ida pro 7.5、ghidra。首先使用binwalk获取固件基础信息,首先看下能不能提取出文件来:
失败,猜测可能是个单文件固件或者有没有可能是个加密的固件,但明文字符串信息分布比较均匀,应该不是加密固件,再使用-A选项看看固件中的字节码信息。
很明确了,扫描出来全是PowerPC大端指令,包括函数开始prologue、结束epilogue相关的指令。
首先使用32位ida pro对firmware进行反汇编,不过需要首先设定处理器类型,选择PowerPC big-endian。
然后一路点确定,很遗憾ida pro没有自动分析,没有一个函数被识别出来,试试binwalk扫描出的函数prologue地址0x2004,跳转到这个地址按C键反汇编,可以自动识别关联的一些函数,查看字符串窗口也无法查看到对字符串的引用,猜测可能有两个原因:
ghirda总共识别出来26000多个函数,应该识别出来差不多了,但是字符串仍然无法形成引用,要不然就是不正确,必须要解决基地址问题。
搜索powerpc固件加载基地址可以获取到一些信息,ppc_rebase运行可以得到一个基地址,但验证后发现不正确。通过参考其他资料,目前主要有四种固件基地址识别方法,具体参考ARM设备固件装载基址定位的研究_朱瑞瑾,然后通过学习PowerPC相关指令集PowerPC下C逆向指南,发现PowerPC switch语句汇编实现中存在一个跳转表,通过跳转表及函数语句地址之间的关系可以计算出PowerPC固件基地址,具体参考脚本。
关键是通过7D ?? 03 A6 4E 80 04 20特征码获取switch语句地址,然后根据地址关系可以计算出固件基地址为0x60000,设置基地址后字符串可以正常引用
大概原理是首先获取固件中的字符串地址,然后通过设置不同的基地址测试该基地址下字符串的引用次数,引用次数越高说明该地址为基地址的概率越大,有一定的通用性,在readme中说对查找ARM固件效果比较好,我测试了两个PowePC固件都能正确获取到基地址,不过需要设置好参数,特别是大小端、字符串长度范围。由于是暴力搜索计算出来的所以比较费CPU,计算一次至少半小时起步,项目地址:https://github.com/sgayou/rbasefind
设置正确的基地址后,ghirda基本可以正常进行静态反汇编分析了,也有伪代码功能,但是用得不熟悉、插件貌似也很少,还是习惯ida pro,但是ida不能自动反汇编,必须手动make code,下面通过两种方法使ida反编译函数。
可以将ghirda反汇编得函数地址信息导出,然后使用脚本导入到ida中make code。
ghirda中导出函数列表方法:Window->Functions 在Functions窗口右键Export->Export to CSV保存。
ida中导入ghirda函数脚本如下:
只需要填入ghirda导出的csv文件路径即可。
大部分编译器编译生成的函数头可能会有一些固定的指令,如x86平台的mov edi, edi;push ebp,这种情况在PowerPc也存在PowerPC特征码为stwu rS,rD(n);mflr r0,我们可以利用这个特征编写ida python脚本使ida开始自动反编译固件生成函数。
其实在github发现一个类似的脚本(https://github.com/maddiestone/IDAPythonEmbeddedToolkit/blob/master/define_code_functions.py),但是这个是针对ida 7.0编写的,本来想移植过来的,折腾了下最后还是自己写一个简单点。
由于固件文件并不像PE、ELF文件有导入表,ida中也没有内置的sig文件,所有的函数都必须靠自己人工识别,工作量太大了,不过强大的ida pro可以自己创建sig文件,经过一番折腾,可以识别libc中一些字符串处理的函数,这里列以下尝试了哪些方法。
根据固件中的Copyright string: "Copyright MGC 2004 - Nucleus PLUS - MPC860 Diab C/C++ v. 1.14"字符串,安装VxWorks Tornado开发环境,提取lib
最后还是Tornado开发环境中提取的lib制作的sig有效,这里分享下Tornado.V2.2.POWERPC下载地址,我是xp环境才安装运行成功的,提取的路径如下C:\Tornado\host\diab\PPCCS
效果如下:
由于并没有实际运行环境,这里并没有进行动态分析,分析起来难度较大,后续可能尝试能否使用qemu-system模式将固件运行起来进行动态调试。
Zyxel设备eCos固件加载地址分析
ARM设备固件装载基址定位的研究_朱瑞瑾
PowerPC下C逆向指南
IOT设备逆向工程中的函数识别
原文链接:http://www.youngroe.com/2021/03/20/Cybersecurity/powerpc_binary_blob_firmware_reverse/
ROM:
000020EC
94
21
FF F8 stwu r1, back_chain(r1)
/
/
开辟栈空间
ROM:
000020F0
7C
08
02
A6 mflr r0
/
/
ROM:
000020EC
94
21
FF F8 stwu r1, back_chain(r1)
/
/
开辟栈空间
ROM:
000020F0
7C
08
02
A6 mflr r0
/
/
import
os
import
sys
import
re
import
struct
def
get_ppc_base_by_switch_table(image_data, start_addr, max_gap
=
1
<<
16
):
offset
=
start_addr
gap
=
0
jmp_table_addr
=
struct.unpack_from(
">i"
, image_data, offset)[
0
]
if
jmp_table_addr
=
=
0
:
return
-
1
jmp_table_addrs
=
[]
while
gap < max_gap:
jmp_table_addrs.append(jmp_table_addr)
offset
=
offset
+
4
addr
=
struct.unpack_from(
">i"
, image_data, offset)[
0
]
gap
=
abs
(addr
-
jmp_table_addr)
jmp_table_addr
=
addr
jmp_table_addrs.sort()
file_loc1_addr
=
offset
true_loc1_addr
=
jmp_table_addrs[
0
]
ppc_base
=
true_loc1_addr
-
file_loc1_addr
return
ppc_base
def
get_switch_code_addrs(image_data):
re_switch_opcode
=
b
"\x7d.{1}\x03\xA6\x4E\x80\x04\x20"
bytes_data
=
bytearray(image_data)
re_pattern
=
re.
compile
(re_switch_opcode)
addrs
=
[]
for
match_obj
in
re_pattern.finditer(bytes_data):
addrs.append(match_obj.start()
+
8
)
return
addrs
def
ppc_base_count(ppc_bases):
freq_dict
=
{}
for
ppc_base
in
ppc_bases:
freq_dict[ppc_base]
=
freq_dict.get(ppc_base,
0
)
+
1
return
freq_dict
def
print_success(ppc_bases):
ppc_base_freq
=
ppc_base_count(ppc_bases)
ppc_base_freq
=
sorted
(ppc_base_freq.items(), key
=
lambda
kv:(kv[
0
], kv[
1
]))
for
base
in
ppc_base_freq:
print
(
'%#x:%d'
%
(base[
0
], base[
1
]))
print
(
"The rebase address is:%#x"
%
ppc_base_freq[
0
][
0
])
def
find_ppc_rebase(firmware_path):
f
=
open
(firmware_path,
"rb"
)
image_data
=
f.read()
f.close()
addrs
=
get_switch_code_addrs(image_data)
if
len
(addrs)
=
=
0
:
print
(
"[-] error find switch table addrs"
)
return
ppc_bases
=
[]
for
addr
in
addrs:
ppc_base
=
get_ppc_base_by_switch_table(image_data, addr)
if
ppc_base <
0
:
continue
ppc_bases.append(ppc_base)
if
len
(ppc_bases) >
0
:
print
(firmware_path
+
" firmware base addr:\n"
)
print_success(ppc_bases)
else
:
print
(
"find rebase address failed, you can see the fllow addr use ida pro:"
)
for
inx, val
in
enumerate
(addrs):
if
inx >
5
:
break
print
(
"%#x"
%
(val
-
16
))
print
(
"press key C, find addi ra,rb, eg:addi r9, r11, 0x71A4 # 0x271A4"
)
print
(
"base = \"0x271A4\" - %#x"
%
addrs[
0
])
def
usage():
print
(
"ppc_rebase.py firmware_path"
)
def
main():
if
len
(sys.argv) <
2
:
usage()
else
:
firmware_path
=
sys.argv[
1
]
if
not
os.path.exists(firmware_path):
usage()
else
:
find_ppc_rebase(firmware_path)
if
__name__
=
=
"__main__"
:
main()
import
os
import
sys
import
re
import
struct
def
get_ppc_base_by_switch_table(image_data, start_addr, max_gap
=
1
<<
16
):
offset
=
start_addr
gap
=
0
jmp_table_addr
=
struct.unpack_from(
">i"
, image_data, offset)[
0
]
if
jmp_table_addr
=
=
0
:
return
-
1
jmp_table_addrs
=
[]
while
gap < max_gap:
jmp_table_addrs.append(jmp_table_addr)
offset
=
offset
+
4
addr
=
struct.unpack_from(
">i"
, image_data, offset)[
0
]
gap
=
abs
(addr
-
jmp_table_addr)
jmp_table_addr
=
addr
jmp_table_addrs.sort()
file_loc1_addr
=
offset
true_loc1_addr
=
jmp_table_addrs[
0
]
ppc_base
=
true_loc1_addr
-
file_loc1_addr
return
ppc_base
def
get_switch_code_addrs(image_data):
re_switch_opcode
=
b
"\x7d.{1}\x03\xA6\x4E\x80\x04\x20"
bytes_data
=
bytearray(image_data)
re_pattern
=
re.
compile
(re_switch_opcode)
addrs
=
[]
for
match_obj
in
re_pattern.finditer(bytes_data):
addrs.append(match_obj.start()
+
8
)
return
addrs
def
ppc_base_count(ppc_bases):
freq_dict
=
{}
for
ppc_base
in
ppc_bases:
freq_dict[ppc_base]
=
freq_dict.get(ppc_base,
0
)
+
1
return
freq_dict
def
print_success(ppc_bases):
ppc_base_freq
=
ppc_base_count(ppc_bases)
ppc_base_freq
=
sorted
(ppc_base_freq.items(), key
=
lambda
kv:(kv[
0
], kv[
1
]))
for
base
in
ppc_base_freq:
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2021-4-2 21:41
被KenLi编辑
,原因: