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. 两种参数识别方法

  1. 基于汇编指令和模拟执行

    • 追踪寄存器/内存地址的使用
    • 使用Unicorn模拟执行到函数调用位置
    • 获取寄存器/内存的值作为参数值
  2. 基于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)

七、脚本使用流程

  1. 使用argument_tracker.py获取固件中每个函数的msg id使用情况
  2. 将结果导出到文件
  3. 使用search_msg_handler.py导入结果并搜索消息ID对应的回调函数
  4. 为两者建立交叉引用

八、总结

本教程详细介绍了DA14531芯片固件逆向分析的完整流程,包括:

  • 环境搭建和固件加载
  • 符号加载和函数识别
  • 操作系统任务识别
  • 函数参数追踪技术
  • 消息处理函数分析和交叉引用建立

通过这套方法,可以系统性地分析DA14531芯片的固件,理解其内部工作机制。

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 符号文件,格式示例: 编写IDApython脚本加载符号: 四、操作系统任务识别 1. 任务创建API分析 DA145x使用Riviera Waves实时系统 ke_task_create 函数定义: 2. 任务描述符结构 3. 消息处理结构 4. 自动化任务识别脚本 查找 ke_task_create 交叉引用 使用参数追踪脚本获取任务描述符地址 定义任务描述符结构 五、函数调用参数识别 1. 两种参数识别方法 基于汇编指令和模拟执行 追踪寄存器/内存地址的使用 使用Unicorn模拟执行到函数调用位置 获取寄存器/内存的值作为参数值 基于IDA伪代码 定位参数并提取字符串 追踪参数的每个组成部分 求出每个部分的值 2. 参数追踪核心代码 六、消息处理函数分析 1. 消息分配函数 2. 消息处理函数搜索 3. 消息处理结构体 4. 交叉引用建立 七、脚本使用流程 使用 argument_tracker.py 获取固件中每个函数的msg id使用情况 将结果导出到文件 使用 search_msg_handler.py 导入结果并搜索消息ID对应的回调函数 为两者建立交叉引用 八、总结 本教程详细介绍了DA14531芯片固件逆向分析的完整流程,包括: 环境搭建和固件加载 符号加载和函数识别 操作系统任务识别 函数参数追踪技术 消息处理函数分析和交叉引用建立 通过这套方法,可以系统性地分析DA14531芯片的固件,理解其内部工作机制。