DA14531芯片固件逆向系列(1)-固件加载和逆向分析
字数 1266 2025-08-24 07:48:09
DA14531芯片固件逆向分析教学文档
一、前言与背景
DA14531是Dialog公司研制的蓝牙芯片,本教程将详细介绍如何对该芯片的固件进行逆向分析,包括固件加载、符号加载、任务识别和消息处理函数分析等关键步骤。
二、环境准备
1. SDK获取与安装
- 从Dialog官网下载DA14531 SDK
- 示例项目路径:
DA145xx_SDK\6.0.14.1114\projects\target_apps\ble_examples\ble_app_profile\Keil_5
2. 关键文件说明
- ble_app_profile_531.axf:ELF格式文件,包含符号信息和加载基地址
- ble_app_profile_531.bin:可直接刷入系统RAM的二进制文件
- ble_app_profile_531.hex:带有刷入地址的hex格式文件
3. 内存映射
- 通过芯片手册确认
.bin文件的基地址为0x7fc0000 - Boot/BLE ROM区域(
0x7F22448等地址)包含Boot ROM代码和BLE协议相关代码
三、IDA加载固件
1. 初始加载
- 使用IDA直接分析
.axf文件 - 对于ROM区域的函数调用,需要额外加载dump出的二进制文件:
File -> Load file -> Additional binary file- 设置加载地址为
0x7fc0000
2. 符号加载
-
发现
da14531_symbols.txt符号文件,格式示例:0x07f2270b T custs1_set_ccc_value 0x07f22823 T gattc_cmp_evt_handler -
编写IDApython脚本加载符号:
import idaapi
import idc
fpath = "da14531_symbols.txt"
def define_func(addr, name):
if addr & 1: # 判断是否为thumb指令
addr -= 1
idaapi.split_sreg_range(addr, idaapi.str2reg("T"), 1, idaapi.SR_user)
else:
idaapi.split_sreg_range(addr, idaapi.str2reg("T"), 0, idaapi.SR_user)
if idaapi.create_insn(addr):
idc.add_func(addr)
idaapi.set_name(addr, name, idaapi.SN_FORCE)
def define_data(addr, name):
idaapi.set_name(addr, name, idaapi.SN_FORCE)
with open(fpath, "r") as fp:
for l in fp:
try:
addr, type, name = l.strip().split(" ")
if addr.startswith(";"):
continue
addr = int(addr, 16)
if type == "T":
define_func(addr, name)
else:
define_data(addr, name)
except:
pass
四、操作系统任务识别
1. 任务创建API分析
- DA145x使用Riviera Waves实时系统
ke_task_create函数定义:
uint8_t ke_task_create(uint8_t task_type, struct ke_task_desc const * p_task_desc);
2. 任务描述符结构
struct ke_task_desc{
const struct ke_state_handler* state_handler; // 状态处理表指针
const struct ke_state_handler* default_handler; // 默认状态处理指针
ke_state_t* state; // 状态表指针
uint16_t state_max; // 最大状态数
uint16_t idx_max; // 最大实例索引
};
3. 消息处理结构
struct ke_msg_handler{
ke_msg_id_t id; // 消息ID
ke_msg_func_t func; // 处理函数指针
};
struct ke_state_handler{
const struct ke_msg_handler *msg_table; // 消息处理表
uint16_t msg_cnt; // 消息数量
};
4. 自动化任务识别脚本
- 查找
ke_task_create交叉引用 - 使用参数追踪脚本获取任务描述符地址
- 定义任务描述符结构
def dump_ke_task_create():
retsult = {}
logger = CustomLogger()
m = CodeEmulator()
at = ArgumentTracker()
ke_task_create_addr = idaapi.get_name_ea(idaapi.BADADDR, "ke_task_create")
for xref in XrefsTo(ke_task_create_addr, 0):
frm_func = idc.get_func_name(xref.frm)
ret = at.track_register(xref.frm, "r1")
if ret.has_key("target_ea"):
if m.emulate(ret['target_ea'], xref.frm):
reg = m.mu.reg_read(UC_ARM_REG_R1)
retsult[xref.frm] = reg
for k, v in retsult.items():
frm_func = idc.get_func_name(k)
task_desc_ea = v
task_desc_name = "{}_task_desc".format(frm_func.split("_init")[0])
define_ke_task_desc(task_desc_ea, task_desc_name)
handler = idaapi.get_dword(task_desc_ea + 4)
define_ke_state_handler(handler)
五、函数调用参数识别
1. 两种参数识别方法
-
基于汇编指令和模拟执行
- 追踪寄存器/内存地址的使用
- 使用Unicorn模拟执行到函数调用位置
- 获取寄存器/内存的值作为参数值
-
基于IDA伪代码
- 定位参数并提取字符串
- 追踪参数的每个组成部分
- 求出每个部分的值
2. 参数追踪核心代码
while curr_ea != idc.BADADDR:
mnem = idc.print_insn_mnem(curr_ea).lower()
dst = idc.print_operand(curr_ea, 0).lower()
src = idc.print_operand(curr_ea, 1).lower()
if dst == target and self.is_set_argument_instr(mnem):
target = src
target_value = src
target_ea = curr_ea
if target.startswith("="):
break
if dst == target == "r0" and self.is_call_instr(mnem):
previous_call = curr_ea
break
curr_ea = idc.prev_head(curr_ea-1, f_start)
六、消息处理函数分析
1. 消息分配函数
void *ke_msg_alloc(ke_msg_id_t const id, ke_task_id_t const dest_id,
ke_task_id_t const src_id, uint16_t const param_len);
2. 消息处理函数搜索
def search_msg_handler(msg_id):
ret = []
data = " ".join(re.findall(".{2}", struct.pack("H", msg_id).encode("hex")))
addr = 0x07F00000
find_addr = idc.find_binary(addr, SEARCH_DOWN, data)
while find_addr != idaapi.BADADDR:
func_addr = idaapi.get_dword(find_addr + 4)
if is_func_ea(func_addr):
print(" msg_id 0x{:X} @ 0x{:X}, handler: 0x{:X}".format(msg_id, find_addr, func_addr))
ret.append(func_addr)
# custom_msg_handler
func_addr = idaapi.get_dword(find_addr + 2)
if is_func_ea(func_addr):
print(" [custom_msg_handler] msg_id 0x{:X} @ 0x{:X}, handler: 0x{:X}".format(msg_id, find_addr, func_addr))
ret.append(func_addr)
find_addr = idc.find_binary(find_addr + 1, SEARCH_DOWN, data)
return ret
3. 消息处理结构体
// 自定义消息处理
struct custom_msg_handler {
ke_task_id_t task_id;
ke_msg_id_t id;
ke_msg_func_t func;
};
// 标准消息处理
struct ke_msg_handler{
ke_msg_id_t id;
ke_msg_func_t func;
};
4. 交叉引用建立
def add_ref(frm, to):
idaapi.add_dref(frm, to, idaapi.dr_R)
idaapi.add_dref(to, frm, idaapi.dr_R)
七、脚本使用流程
- 使用
argument_tracker.py获取固件中每个函数的msg id使用情况 - 将结果导出到文件
- 使用
search_msg_handler.py导入结果并搜索消息ID对应的回调函数 - 为两者建立交叉引用
八、总结
本教程详细介绍了DA14531芯片固件逆向分析的完整流程,包括:
- 环境搭建和固件加载
- 符号加载和函数识别
- 操作系统任务识别
- 函数参数追踪技术
- 消息处理函数分析和交叉引用建立
通过这套方法,可以系统性地分析DA14531芯片的固件,理解其内部工作机制。