本文为看雪论坛精华文章
看雪论坛作者ID:KenLi
0x0 简介
0x01 确定加载基地址
ROM:000020EC 94 21 FF F8 stwu r1, back_chain(r1) //开辟栈空间
ROM:000020F0 7C 08 02 A6 mflr r0 //
https://github.com/ilovepp/ppc_rebase运行可以得到一个基地址,但验证后发现不正确。
#coding=utf-8
import os
import sys
import re
import struct
'''
get powerpc big endin base addr by switch case jmp_table
ROM:0000FAF4 28 03 00 07 cmplwi r3, 7
ROM:0000FAF8 54 63 10 3A slwi r3, r3, 2
ROM:0000FAFC 3D 83 00 07 addis r12, r3, 7
ROM:0000FB00 41 81 01 D0 bgt loc_FCD0
ROM:0000FB04 81 6C FB 10 lwz r11, -0x4F0(r12)
ROM:0000FB08 7D 69 03 A6 mtctr r11
ROM:0000FB0C 4E 80 04 20 bctr
ROM:0000FB0C # ---------------------------------------------------------------------------
ROM:0000FB10 00 06 FB 30 .long unk_6FB30
ROM:0000FB14 00 06 FB 54 .long unk_6FB54
ROM:0000FB18 00 06 FB 70 .long unk_6FB70
ROM:0000FB1C 00 06 FB A8 .long unk_6FBA8
ROM:0000FB20 00 06 FB C4 .long unk_6FBC4
ROM:0000FB24 00 06 FC 18 .long unk_6FC18
ROM:0000FB28 00 06 FC 34 .long unk_6FC34
ROM:0000FB2C 00 06 FC B8 .long unk_6FCB8
ROM:0000FB30 # ---------------------------------------------------------------------------
ROM:0000FB30 80 7F 01 68 lwz r3, 0x168(r31)
ROM:0000FB34 48 01 D3 85 bl sub_2CEB8
ROM:0000FB38 38 83 00 00 addi r4, r3, 0
ROM:0000FB3C 38 7F 00 00 addi r3, r31, 0
ida pro crtl+B "7D ?? 03 A6 4E 80 04 20" 匹配查找到类似代码
方法1:
bctr 根据ctr寄存器值跳转
mtctr r11 表示将r11的值加载到ctr寄存器
r11 = 0x70000+r3*4-0x4F0,可以计算得到 当r3为0时,r11为0x6FB10 则ctr寄存器值也为0x6FB10
则第一个跳转地址表实际所在的地址应该为0x6FB10, 0x6FB10 = base_addr + file_offset(0xFB10)
可以计算base_addr = 0x6FB10 - 0xFB10
方法2:
最后一个跳转地址后面应该是第一个case语句跳转地址,这里的文件偏移为0xFB30
在跳转地址表中找到最小的一个地址,这里为0x6FB30
实际这两个地址应该相等,则 base_addr = 0x6FB30 - 0xFB30
脚本实现的方法2,但是在某些固件中跳转地址表中的跳转地址不是绝对地址而是相对地址,脚本就无法通过方法二计算
需要手动根据方法1计算
'''
def get_ppc_base_by_switch_table(image_data, start_addr, max_gap=1<<16):
'''
通过跳转表首地址获取跳转地址表、第一个case语句地址
跳转地址表中的地址应该是紧凑的,有一个地址范围差max_gap,通过该条件可以获取到所有跳转地址
与最后一个跳转地址相邻的是第一个case语句的地址,如果基地址正确则该地址应该和跳转地址表中最小的地址相等
这里设置的基地址为0,则这两个地址之间的差值即为基地址
'''
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):
'''
#ida 7D ?? 03 A6 4E 80 04 20
7D ?? 03 A6 mtctr rS
4E 80 04 20 bctr
通过switch语句字节码匹配查找固件中switch case跳转表首地址
'''
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) #7D ?? 03 A6 4E 80 04 20 len = 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()
0x02 IDA Pro反编译函数
#coding=utf-8
import csv
import ida_funcs
import ida_kernwin
'''
ida pro 7.5 python3
'''
def get_funcs_addr(csv_path):
starts = []
with open(csv_path, 'r') as file:
reader = csv.DictReader(file)
for row in reader:
start = int(row['Location'], 16)
starts.append(start)
return starts
def add_ida_funcs(starts):
for index, start in enumerate(starts):
ida_funcs.add_func(start)
def main():
file_path=ida_kernwin.ask_file(1, "*", "ghidra export functions csv file path")
starts = get_funcs_addr(file_path)
add_ida_funcs(starts)
print("[+] done")
if __name__ == "__main__":
main()
#coding=utf-8
import re
import ida_kernwin
import ida_funcs
import ida_ida
'''
ida pro 7.5 python3
'''
def find_func_prologue(file_path, pattern):
f = open(file_path, "rb")
image_data = f.read()
f.close()
bytes_data = bytearray(image_data)
re_pattern = re.compile(pattern)
addrs = []
for match_obj in re_pattern.finditer(bytes_data):
addrs.append(match_obj.start())
return addrs
def auto_make_function(prolog_addrs):
for addr in prolog_addrs:
ida_funcs.add_func(ida_ida.inf_get_min_ea() + addr)
def main():
ppc_prologue = b"\x94.{2}\xF8\x7C\x08\x02\xA6"
file_path=ida_kernwin.ask_file(1, "*", "firmware path")
addrs = find_func_prologue(file_path, ppc_prologue)
print("[+] find %d func prologue"%len(addrs))
auto_make_function(addrs)
print("[+] done")
if __name__ == "__main__":
main()
0x03 通过sig优化库函数识别
0x04 TODO
0x05 参考
https://cq674350529.github.io/2021/03/04/Zyxel%E8%AE%BE%E5%A4%87eCos%E5%9B%BA%E4%BB%B6%E5%8A%A0%E8%BD%BD%E5%9C%B0%E5%9D%80%E5%88%86%E6%9E%90/
http://gb.oversea.cnki.net/KCMS/detail/detail.aspx?filename=1018812112.nh&dbcode=CDFD&dbname=CDFDREF
https://bbs.pediy.com/thread-191928.htm
http://blog.nsfocus.net/function-recognition-reverse-engineering-iot-equipment/
看雪ID:KenLi
https://bbs.pediy.com/user-home-627167.htm
# 往期推荐
球分享
球点赞
球在看
点击“阅读原文”,了解更多!