首页
社区
课程
招聘
《安卓逆向这档事》第二十四课、Unidbg之补完环境我就睡(上)
发表于: 2025-9-8 08:51 3239

《安卓逆向这档事》第二十四课、Unidbg之补完环境我就睡(上)

2025-9-8 08:51
3239

1.了解unidbg_trace,精准控制追踪,提升逆向分析效率
2.配置 mcp 服务,利用AI辅助逆向,加速关键算法分析
3.unidbg 之补 jni,模拟各类Java层调用

1.教程Demo
2.IDEA
3.IDA
4.Cursor

1. 基础用法
在调用目标函数前,只需简单地调用 emulator.traceCode() 即可开启对后续所有指令的追踪。

2. 约束追踪范围
通常我们只关心目标 SO 的执行流,而非 libc 等系统库的内部执行。traceCode 的重载方法 traceCode(long begin, long end) 允许我们精确设定追踪的内存地址范围。

3. 精确控制追踪时机

4. 预估追踪耗时
等待一个未知的 Trace 过程是痛苦的。我们可以通过一个轻量级的 CodeHook 快速统计指令总数,从而预估总耗时。这基本不含反汇编和寄存器打印的开销,所以速度极快。

PS::在较好的测试条件下,unidbg 每分钟约可追踪 40 万行执行流。你可以根据统计出的总行数来判断是泡杯咖啡等几分钟,还是需要去睡一觉等几个小时。
5. 限定特定函数
结合断点,可以实现对单个函数内部的精确追踪。

6.函数调用追踪 (debugger.traceFunctionCall) 详解
traceFunctionCall 是 unidbg 0.9.6 版本后新增的强大功能,用于追踪函数粒度的调用关系,对理清高层逻辑和对抗 OLLVM 等混淆有奇效。
1. 基础用法
它属于 Debugger 的一部分,使用时需要先 attach() 到模拟器。

2. 构建函数调用树
通过获取栈回溯深度,我们可以格式化输出,形成一个清晰的函数调用树,这对于分析复杂的调用逻辑极其有用。
首先修改一下源码,路径:unidbg-api\src\main\java\com\github\unidbg\unwind
添加以下方法:

调用实例:

完整代码:

MCP概念:
MCP(Model Context Protocol,模型上下文协议)是什么?
MCP是一种标准化协议,旨在为人工智能模型(如大语言模型)与外部工具、数据源之间的交互提供统一接口。(为 AI 装上了手脚)
MCP是AI智能体与外部工具的"USB接口",定义了AI模型与外部工具(如API、数据库、文档编辑器等)的交互标准,开发者无需为每个工具单独开发适配代码。
MCP协议旨在实现大型语言模型(LLM)与外部数据源和工具之间的无缝集成,通过提供标准化的接口,使AI应用程序能够安全、可控地与本地或远程资源进行交互。
image.png

项目地址
要求:
1.IDA Pro 8.3 以上,最好是 9
2.python3.11 或更高版本,使用idapyswitch切换到Python 版本
3.支持 Mcp 的客户端,这里以 Cursor 为例

环境配置:

Ps: 如果你现有的 python 版本低于十一,可以单独下载一个版本然后安装到 ida 的目录下
image.png
接着打开 idapyswitch 切换 3.11 的版本
image.png
然后 cmd 窗口打开新安装的 python.exe 再运行 pip 命令

接下来,再运行命令:

这步如果报错,可以打开 11 版本 python 目录下的 Scripts 文件夹,找到 ida-pro-mcp.exe
Cmd 窗口运行:

最后运行:

image.png
接下来打开 cursor,发现 ida_pro_mcp 已经亮绿灯了,说明 mcp 服务正常了。如果没有你就复制 ida-pro-mcp --config 输出的那串 json 到 mcp.json
image.png
image.png
最后在 IDA 中将 server 端开启即可。
image.png

定义:
“Unidbg 补环境”是指在使用 Unidbg 模拟执行 Android 原生 (Native) 代码时,为了使程序能够正常运行并获得与真实设备一致的结果,对 Unidbg 模拟器环境中的缺失或不完善部分进行补充和调整的过程

JNI (Java Native Interface) 补环境是 Unidbg 应用中的核心环节。当 Unidbg 模拟执行的原生库(. So 文件)尝试通过 JNI 调用 Java 层的代码时,Unidbg 必须能够提供这些 Java 方法的模拟实现。如果 Unidbg 缺少某个方法的实现,或者默认实现不符合预期,程序就会抛出 UnsupportedOperationException 异常,导致模拟中断。因此,JNI 补环境的目的就是拦截这些 JNI 调用,并提供一个符合目标 SO 文件逻辑预期的返回值或行为,从而“欺骗”SO 文件,使其认为自己运行在真实的 Android 环境中。(本质就是缺啥补啥)

这是最主要的 Java 层补环境方式。通过创建一个继承自com.github.unidbg.linux.android.dvm.AbstractJni 的自定义类,并重写其关键方法,可以模拟任意 JNI 调用。
具体步骤:

方法实现示例

public DvmObject<?> newObjectV(BaseVM vm, DvmClass dvmClass, String signature, VaList vaList) {
switch (signature) {
case "java/util/HashMap->

}
```
- 模拟字段访问 (Get/Set<Type>Field)

处理复杂的数据结构(如结构体、数组)是 JNI 补环境的难点和重点。
1. 数组 byte[]String[]int[]

2.结构体 (Struct)

3.Map 等集合对象

在本次课程中,我们系统性地学习了 Unidbg 逆向分析中的两项核心技术和 mcp 的配置与辅助分析:

本节完整代码:

百度云
阿里云
哔哩哔哩
教程开源地址
PS:解压密码都是52pj,阿里云由于不能分享压缩包,所以下载exe文件,双击自解压

白龙unidbg教程

Trace 类型 描述
函数 Trace 包括无差别的函数 Trace、导出函数 Trace、库函数 Trace、系统调用 Trace 等,用于分析算法执行流、应对 OLLVM 混淆与动态跳转。
基本块 Trace 也叫 Block Trace,用于控制流分析与反混淆。
汇编 Trace 包括指令级 Trace、特定指令 Trace 等,尤其适用于分析自定义算法、花指令;它也是最重要的 Trace 类型,基于它可实现其他类型 Trace。
维度 表现
易用性 必须掌握 Unidbg 的使用,但一旦会用,就能得到目标函数完整执行的 Trace。开启指令追踪十分简单,只需 emulator.traceCode(begin, end) 一句码。
对比:Frida Stalker、IDA Instruction trace 的启动更复杂。
效率 速度中等:测试环境下约可达 40w 行执行流/分钟(2400w/小时)。
简单样本约 10 分钟即可完成 Trace,中等样本则需 2 小时,复杂、混淆严重的样本可能长达 12 小时甚至更多。
IDA trace 相比快很多,但比 Stalker 等动态重编译方慢。
实际速度会因范围过滤等处理而折减至理论值的 1/4 或更低。
稳定性 稳定性好:基于 Unicorn,引发崩溃或异常 Trace 的情况较少。
对比:IDA Trace 容易中断或导致应用崩溃,Stalker 对某些指令支持有 Bug。
兼容性 支持 ARM32ARM64
比多数指令追踪方案(如 Stalker 不完全支持 ARM32)更全面。
相比 IDA Trace 对 X86、MIPS 等架构的支持仍有不足。
展示效果 信息维度丰富:包括时间、绝对地址、模块、相对偏移、机器码、汇编代码、执行前后寄存器变化等。
信息处理也很出色,细节优化多。
// 在调用目标函数前开启指令追踪
emulator.traceCode();
boolean result = security.callStaticJniMethodBoolean(emulator, "check", "123456");
// 在调用目标函数前开启指令追踪
emulator.traceCode();
boolean result = security.callStaticJniMethodBoolean(emulator, "check", "123456");
// 获取目标模块对象
Module module = dm.getModule();
// 仅追踪目标模块地址范围内的指令
emulator.traceCode(module.base, module.base + module.size);
// 注意:第二个参数是结束地址,不是长度!这是一个常见误区。
// 获取目标模块对象
Module module = dm.getModule();
// 仅追踪目标模块地址范围内的指令
emulator.traceCode(module.base, module.base + module.size);
// 注意:第二个参数是结束地址,不是长度!这是一个常见误区。
memory.addModuleListener(new ModuleListener() {
    @Override
    public void onLoaded(Emulator<?> emulator, Module module) {
        // 当我们关心的模块被加载时,立即开启trace
        if("lib52pojie.so".equals(module.name)){
            emulator.traceCode(module.base, module.base + module.size);
        }
    }
});
// 这之后再加载模块
DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/zj/wuaipojie/util/lib52pojie.so"), true);
memory.addModuleListener(new ModuleListener() {
    @Override
    public void onLoaded(Emulator<?> emulator, Module module) {
        // 当我们关心的模块被加载时,立即开启trace
        if("lib52pojie.so".equals(module.name)){
            emulator.traceCode(module.base, module.base + module.size);
        }
    }
});
// 这之后再加载模块
DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/zj/wuaipojie/util/lib52pojie.so"), true);
private long instructionCount = 0; // 用于指令计数
emulator.getBackend().hook_add_new(new CodeHook() { 
    @Override 
    public void hook(Backend backend, long address, int size, Object user) { 
        instructionCount++; 
    
    @Override 
    public void onAttach(UnHook unHook) {} 
    @Override 
    public void detach() {} 
}, module.base, module.base + module.size, null);
System.out.println("总共执行ARM指令数: " + test.instructionCount);
private long instructionCount = 0; // 用于指令计数
emulator.getBackend().hook_add_new(new CodeHook() { 
    @Override 
    public void hook(Backend backend, long address, int size, Object user) { 
        instructionCount++; 
    
    @Override 
    public void onAttach(UnHook unHook) {} 
    @Override 
    public void detach() {} 
}, module.base, module.base + module.size, null);
System.out.println("总共执行ARM指令数: " + test.instructionCount);
long callAddr = module.base + 0xE53C; // 假设这是BL指令的地址
final TraceHook[] traceHook = new TraceHook[1];
// 在调用处开启追踪
emulator.attach().addBreakPoint(callAddr, new BreakPointCallback() {
    @Override
    public boolean onHit(Emulator<?> emulator, long address) {
        traceHook[0] = emulator.traceCode(module.base, module.base + module.size);
        return true;
    }
});
// 在调用返回后关闭追踪
emulator.attach().addBreakPoint(callAddr + 4, new BreakPointCallback() {
    @Override
    public boolean onHit(Emulator<?> emulator, long address) {
        if (traceHook[0] != null) {
            traceHook[0].stopTrace();
        }
        return true;
    }
});
long callAddr = module.base + 0xE53C; // 假设这是BL指令的地址
final TraceHook[] traceHook = new TraceHook[1];
// 在调用处开启追踪
emulator.attach().addBreakPoint(callAddr, new BreakPointCallback() {
    @Override
    public boolean onHit(Emulator<?> emulator, long address) {
        traceHook[0] = emulator.traceCode(module.base, module.base + module.size);
        return true;
    }
});
// 在调用返回后关闭追踪
emulator.attach().addBreakPoint(callAddr + 4, new BreakPointCallback() {
    @Override
    public boolean onHit(Emulator<?> emulator, long address) {
        if (traceHook[0] != null) {
            traceHook[0].stopTrace();
        }
        return true;
    }
});
// 获取调试器实例
Debugger debugger = emulator.attach();
// 追踪目标模块内的所有函数调用
debugger.traceFunctionCall(module, new FunctionCallListener() {
    @Override
    public void onCall(Emulator<?> emulator, long callerAddress, long functionAddress) {
        // 函数调用前(相当于Frida onEnter)
        System.out.println("onCall: " + UnidbgPointer.pointer(emulator, callerAddress) + " -> " + UnidbgPointer.pointer(emulator, functionAddress));
    }
    @Override
    public void postCall(Emulator<?> emulator, long callerAddress, long functionAddress, Number[] args) {
        // 函数调用后(相当于Frida onLeave)
        // 注意:这里的args是寄存器R0-R7(x0-x7)的值,不完全等同于函数参数
    }
});
// 获取调试器实例
Debugger debugger = emulator.attach();
// 追踪目标模块内的所有函数调用
debugger.traceFunctionCall(module, new FunctionCallListener() {
    @Override
    public void onCall(Emulator<?> emulator, long callerAddress, long functionAddress) {
        // 函数调用前(相当于Frida onEnter)
        System.out.println("onCall: " + UnidbgPointer.pointer(emulator, callerAddress) + " -> " + UnidbgPointer.pointer(emulator, functionAddress));
    }
    @Override
    public void postCall(Emulator<?> emulator, long callerAddress, long functionAddress, Number[] args) {
        // 函数调用后(相当于Frida onLeave)
        // 注意:这里的args是寄存器R0-R7(x0-x7)的值,不完全等同于函数参数
    }
});
/** 
 * 将给定的内存地址格式化为包含详细信息、可读的字符串。 
 * 这个方法会尝试解析地址,并提供尽可能多的上下文信息,如模块名、函数名(符号)、偏移量等。 
 
 * @param address 要格式化的绝对内存地址。 
 * @return 一个包含地址详细信息的格式化字符串。 
 */ 
public String formatAddressDetails(long address) { 
    // 1. 尝试根据地址查找其所属的模块(例如,一个 .so 文件)。 
    Module module = emulator.getMemory().findModuleByAddress(address); 
   
    // 2. 如果地址位于一个已加载的模块内: 
    if (module != null) { 
        // 2.1. 在模块中查找离该地址最近的符号(即函数或全局变量的名称)。 
        //      `true` 参数表示也查找非导出的内部符号。 
        Symbol symbol = module.findClosestSymbolByAddress(address, true); 
   
        // 2.2. 如果找到了一个符号: 
        if (symbol != null) { 
            // 2.2.1. 创建一个 demangler 实例,用于将 C++ "mangled"(混淆)的符号名还原为可读的函数签名。 
            GccDemangler demangler = DemanglerFactory.createDemangler(); 
            // 2.2.2. 对符号名进行 demangle 操作。 
            String demangledName = demangler.demangle(symbol.getName()); 
            // 2.2.3. 计算当前地址相对于符号起始地址的偏移量。 
            long offset = address - symbol.getAddress(); 
            // 2.2.4. 返回最详细的格式化字符串,例如:"[libnative.so] JNI_OnLoad + 0x10 (at 0x...)" 
            return String.format("[%s] %s + 0x%x (at 0x%x)", module.name, demangledName, offset, address); 
        } else
            // 2.3. 如果在模块内但没有找到具体的符号,则将其视为一个未命名的子程序(subroutine)。 
            //      计算地址相对于模块基地址的偏移量。 
            long offset = address - module.base; 
            // 2.3.1. 返回一个通用的子程序格式,例如:"[libnative.so] sub_c80 (at 0x...)" 
            return String.format("[%s] sub_%x (at 0x%x)", module.name, offset, address); 
        
    
   
    // 3. 如果地址不属于任何模块,则尝试查找它是否位于一个已知的内存区域中(例如,栈或堆)。 
    MemRegion region = emulator.getSvcMemory().findRegion(address); 
    if (region != null) { 
        // 3.1. 如果找到了,返回区域名称,例如:"[stack] (at 0x...)" 
        return String.format("[%s] (at 0x%x)", region.getName(), address); 
    
   
    // 4. 如果以上所有尝试都失败了,返回一个表示“未知”的通用格式。 
    return String.format("[unknown] (at 0x%x)", address); 
}
/** 
 * 将给定的内存地址格式化为包含详细信息、可读的字符串。 
 * 这个方法会尝试解析地址,并提供尽可能多的上下文信息,如模块名、函数名(符号)、偏移量等。 
 
 * @param address 要格式化的绝对内存地址。 
 * @return 一个包含地址详细信息的格式化字符串。 
 */ 
public String formatAddressDetails(long address) { 
    // 1. 尝试根据地址查找其所属的模块(例如,一个 .so 文件)。 
    Module module = emulator.getMemory().findModuleByAddress(address); 
   
    // 2. 如果地址位于一个已加载的模块内: 
    if (module != null) { 
        // 2.1. 在模块中查找离该地址最近的符号(即函数或全局变量的名称)。 
        //      `true` 参数表示也查找非导出的内部符号。 
        Symbol symbol = module.findClosestSymbolByAddress(address, true); 
   
        // 2.2. 如果找到了一个符号: 
        if (symbol != null) { 
            // 2.2.1. 创建一个 demangler 实例,用于将 C++ "mangled"(混淆)的符号名还原为可读的函数签名。 
            GccDemangler demangler = DemanglerFactory.createDemangler(); 
            // 2.2.2. 对符号名进行 demangle 操作。 
            String demangledName = demangler.demangle(symbol.getName()); 
            // 2.2.3. 计算当前地址相对于符号起始地址的偏移量。 
            long offset = address - symbol.getAddress(); 
            // 2.2.4. 返回最详细的格式化字符串,例如:"[libnative.so] JNI_OnLoad + 0x10 (at 0x...)" 
            return String.format("[%s] %s + 0x%x (at 0x%x)", module.name, demangledName, offset, address); 
        } else
            // 2.3. 如果在模块内但没有找到具体的符号,则将其视为一个未命名的子程序(subroutine)。 
            //      计算地址相对于模块基地址的偏移量。 
            long offset = address - module.base; 
            // 2.3.1. 返回一个通用的子程序格式,例如:"[libnative.so] sub_c80 (at 0x...)" 
            return String.format("[%s] sub_%x (at 0x%x)", module.name, offset, address); 
        
    
   
    // 3. 如果地址不属于任何模块,则尝试查找它是否位于一个已知的内存区域中(例如,栈或堆)。 
    MemRegion region = emulator.getSvcMemory().findRegion(address); 
    if (region != null) { 
        // 3.1. 如果找到了,返回区域名称,例如:"[stack] (at 0x...)" 
        return String.format("[%s] (at 0x%x)", region.getName(), address); 
    
   
    // 4. 如果以上所有尝试都失败了,返回一个表示“未知”的通用格式。 
    return String.format("[unknown] (at 0x%x)", address); 
}
Debugger debugger = emulator.attach(); 
System.out.println("函数调用关系追踪器已附加,结果将输出到日志文件。"); 
debugger.traceFunctionCall(null, new FunctionCallListener() { 
    private int depth = 0
    private String getPrefix(int currentDepth) { 
        if (currentDepth <= 0) { 
            return ""
        
        StringBuilder sb = new StringBuilder(); 
        for (int i = 0; i < currentDepth - 1; i++) { 
            sb.append("│  "); 
        
        sb.append("├─ "); 
        return sb.toString(); 
    
    @Override 
    public void onCall(Emulator<?> emulator, long callerAddress, long functionAddress) { 
        String prefix = getPrefix(depth + 1); 
        String details = emulator.getUnwinder().formatAddressDetails(functionAddress); 
        traceStream.printf("%sCALL -> %s%n", prefix, details); 
        depth++; 
    
    @Override 
    public void postCall(Emulator<?> emulator, long callerAddress, long functionAddress, Number[] args) { 
        depth--; 
        String prefix = getPrefix(depth + 1); 
        String details = emulator.getUnwinder().formatAddressDetails(functionAddress); 
        Backend backend = emulator.getBackend(); 
        Number retVal = emulator.is64Bit() ? backend.reg_read(Arm64Const.UC_ARM64_REG_X0) : backend.reg_read(ArmConst.UC_ARM_REG_R0); 
        long retValLong = retVal.longValue(); 
        // 尝试将返回值作为指针解析 
        String retValFormatted = String.format("0x%x", retValLong); 
        UnidbgPointer pointer = UnidbgPointer.pointer(emulator, retValLong); 
        if (pointer != null) { 
            String cstring = safeReadCString(pointer); 
            // 如果是一个可打印的字符串,则附加到日志中 
            if (isPrintable(cstring)) { 
                retValFormatted += String.format(" -> \"%s\"", cstring); 
            
        
        traceStream.printf("%sRET  <- %s, ret=%s%n", prefix, details, retValFormatted); 
    
});
Debugger debugger = emulator.attach(); 
System.out.println("函数调用关系追踪器已附加,结果将输出到日志文件。"); 
debugger.traceFunctionCall(null, new FunctionCallListener() { 
    private int depth = 0
    private String getPrefix(int currentDepth) { 
        if (currentDepth <= 0) { 
            return ""
        
        StringBuilder sb = new StringBuilder(); 
        for (int i = 0; i < currentDepth - 1; i++) { 
            sb.append("│  "); 
        
        sb.append("├─ "); 
        return sb.toString(); 
    
    @Override 
    public void onCall(Emulator<?> emulator, long callerAddress, long functionAddress) { 
        String prefix = getPrefix(depth + 1); 
        String details = emulator.getUnwinder().formatAddressDetails(functionAddress); 
        traceStream.printf("%sCALL -> %s%n", prefix, details); 
        depth++; 
    
    @Override 
    public void postCall(Emulator<?> emulator, long callerAddress, long functionAddress, Number[] args) { 
        depth--; 
        String prefix = getPrefix(depth + 1); 
        String details = emulator.getUnwinder().formatAddressDetails(functionAddress); 
        Backend backend = emulator.getBackend(); 
        Number retVal = emulator.is64Bit() ? backend.reg_read(Arm64Const.UC_ARM64_REG_X0) : backend.reg_read(ArmConst.UC_ARM_REG_R0); 
        long retValLong = retVal.longValue(); 
        // 尝试将返回值作为指针解析 
        String retValFormatted = String.format("0x%x", retValLong); 
        UnidbgPointer pointer = UnidbgPointer.pointer(emulator, retValLong); 
        if (pointer != null) { 
            String cstring = safeReadCString(pointer); 
            // 如果是一个可打印的字符串,则附加到日志中 
            if (isPrintable(cstring)) { 
                retValFormatted += String.format(" -> \"%s\"", cstring); 
            
        
        traceStream.printf("%sRET  <- %s, ret=%s%n", prefix, details, retValFormatted); 
    
});
package com.example.ndkdemo; 
   
import com.github.unidbg.*; 
import com.github.unidbg.arm.backend.Backend; 
import com.github.unidbg.arm.backend.CodeHook; 
import com.github.unidbg.arm.backend.UnHook; 
import com.github.unidbg.arm.backend.Unicorn2Factory; 
import com.github.unidbg.arm.context.Arm64RegisterContext; 
import com.github.unidbg.debugger.BreakPointCallback; 
import com.github.unidbg.debugger.Debugger; 
import com.github.unidbg.debugger.FunctionCallListener; 
import com.github.unidbg.linux.android.AndroidEmulatorBuilder; 
import com.github.unidbg.linux.android.AndroidResolver; 
import com.github.unidbg.linux.android.dvm.DalvikModule; 
import com.github.unidbg.linux.android.dvm.DvmClass; 
import com.github.unidbg.linux.android.dvm.VM; 
import com.github.unidbg.memory.Memory; 
import com.github.unidbg.pointer.UnidbgPointer; 
import unicorn.Arm64Const; 
import unicorn.ArmConst; 
   
import java.io.*; 
   
public class MainActivity { 
    private final AndroidEmulator emulator; 
    private final VM vm; 
    private final Module module; 
    private final DvmClass security; 
    private final boolean logging; 
    private long instructionCount = 0; // 用于指令计数 
   
    MainActivity(boolean logging) { 
        this.logging = logging; 
   
        // 1. 创建模拟器实例 
        emulator = AndroidEmulatorBuilder.for64Bit() 
                .setProcessName("com.example.ndkdemo"
                .addBackendFactory(new Unicorn2Factory(true)) 
                .build(); 
   
        // 2. 设置内存和系统库解析 
        final Memory memory = emulator.getMemory(); 
        memory.setLibraryResolver(new AndroidResolver(23)); 
   
        // 3. 创建Dalvik虚拟机并加载SO文件 
        vm = emulator.createDalvikVM(); 
        vm.setVerbose(logging); 
        DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/zj/wuaipojie/util/libndkdemo.so"), false); 
        dm.callJNI_OnLoad(emulator); 
        module = dm.getModule(); 
   
        // 4. 获取DVM类 
        security = vm.resolveClass("com/example/ndkdemo/MainActivity"); 
   
        // 5. 设置指令计数钩子 (可选) 
        emulator.getBackend().hook_add_new(new CodeHook() { 
            @Override 
            public void hook(Backend backend, long address, int size, Object user) { 
                instructionCount++; 
            
            @Override 
            public void onAttach(UnHook unHook) {} 
            @Override 
            public void detach() {} 
        }, module.base, module.base + module.size, null); 
   
   
        // 6.设置函数追踪与x0寄存器检查 
        attachTraceAndInspectX0(module.base + 0x15F8); 
    
   
    /** 
     * 【模块化功能】: 附加一个函数调用追踪器 (traceFunctionCall)。 
     * 此功能会监听所有的函数调用,并以树状结构打印出调用关系。 
     * @param traceStream 日志输出流 
     */ 
    public void attachFunctionCallTracer(final PrintStream traceStream) { 
        Debugger debugger = emulator.attach(); 
        System.out.println("函数调用关系追踪器已附加,结果将输出到日志文件。"); 
   
        debugger.traceFunctionCall(null, new FunctionCallListener() { 
            private int depth = 0
   
            private String getPrefix(int currentDepth) { 
                if (currentDepth <= 0) { 
                    return ""
                
                StringBuilder sb = new StringBuilder(); 
                for (int i = 0; i < currentDepth - 1; i++) { 
                    sb.append("│  "); 
                
                sb.append("├─ "); 
                return sb.toString(); 
            
   
            @Override 
            public void onCall(Emulator<?> emulator, long callerAddress, long functionAddress) { 
                String prefix = getPrefix(depth + 1); 
                String details = emulator.getUnwinder().formatAddressDetails(functionAddress); 
                traceStream.printf("%sCALL -> %s%n", prefix, details); 
                depth++; 
            
   
            @Override 
            public void postCall(Emulator<?> emulator, long callerAddress, long functionAddress, Number[] args) { 
                depth--; 
                String prefix = getPrefix(depth + 1); 
                String details = emulator.getUnwinder().formatAddressDetails(functionAddress); 
   
                Backend backend = emulator.getBackend(); 
                Number retVal = emulator.is64Bit() ? backend.reg_read(Arm64Const.UC_ARM64_REG_X0) : backend.reg_read(ArmConst.UC_ARM_REG_R0); 
                long retValLong = retVal.longValue(); 
   
                // 尝试将返回值作为指针解析 
                String retValFormatted = String.format("0x%x", retValLong); 
                UnidbgPointer pointer = UnidbgPointer.pointer(emulator, retValLong); 
                if (pointer != null) { 
                    String cstring = safeReadCString(pointer); 
                    // 如果是一个可打印的字符串,则附加到日志中 
                    if (isPrintable(cstring)) { 
                        retValFormatted += String.format(" -> \"%s\"", cstring); 
                    
                
   
                traceStream.printf("%sRET  <- %s, ret=%s%n", prefix, details, retValFormatted); 
            
        }); 
    
   
    /** 
     * 在指定地址设置断点,实现对一个函数调用的追踪,并在函数返回后检查 x0 寄存器的内容。 
     
     * @param callAddress 函数调用指令(例如 BL, B)的绝对地址 
     */ 
    private void attachTraceAndInspectX0(long callAddress) { 
        final TraceHook[] traceHook = new TraceHook[1]; 
   
        emulator.attach().addBreakPoint(callAddress, (emu, address) -> { 
            traceHook[0] = emu.traceCode(module.base, module.base + module.size); 
            return true
        }); 
   
        long returnAddress = callAddress + 4
        emulator.attach().addBreakPoint(returnAddress, new BreakPointCallback() { 
            @Override 
            public boolean onHit(Emulator<?> emu, long address) { 
                if (traceHook[0] != null) { 
                    traceHook[0].stopTrace(); 
                    System.out.println(); 
                
   
                Arm64RegisterContext ctx = emu.getContext(); 
                long x0 = ctx.getXLong(0); 
                System.out.printf("[+] 检查地址: 0x%x, x0寄存器值: 0x%x\n", address, x0); 
   
                UnidbgPointer pointer = UnidbgPointer.pointer(emu, x0); 
                if (pointer == null) { 
                    System.out.println("[-] x0的值不是一个有效的指针或指向未映射的内存"); 
                    return true
                
   
                String cstring = safeReadCString(pointer); 
                if (cstring != null && isPrintable(cstring)) { 
                    System.out.println("[+] x0指向的字符串: " + cstring); 
                } else
                    System.out.println("[-] x0指向的内容不是一个可打印的字符串"); 
                
   
                int dumpSize = 256
                byte[] data = pointer.getByteArray(0, dumpSize); 
                System.out.println("[+] x0指向内存的HexDump (前" + dumpSize + "字节):"); 
                System.out.println(prettyHexDump(data, x0)); 
                System.out.println("--- x0寄存器检查结束 ---\n"); 
   
                return true
            
        }); 
    
   
    private static String safeReadCString(UnidbgPointer p) { 
        try
            return p.getString(0); 
        } catch (Exception e) { 
            return null
        
    
   
    private static boolean isPrintable(String s) { 
        if (s == null || s.isEmpty()) { 
            return false
        
        int printableChars = 0
        for (int i = 0; i < s.length(); i++) { 
            char c = s.charAt(i); 
            if ((c >= 32 && c <= 126) || Character.isWhitespace(c)) { 
                printableChars++; 
            
        
        return s.length() >= 2 && (double) printableChars / s.length() > 0.8
    
   
    private static String prettyHexDump(byte[] data, long baseAddr) { 
        StringBuilder sb = new StringBuilder(); 
        for (int i = 0; i < data.length; i += 16) { 
            sb.append(String.format("%016x: ", baseAddr + i)); 
            StringBuilder hexPart = new StringBuilder(); 
            StringBuilder asciiPart = new StringBuilder(); 
            for (int j = 0; j < 16; j++) { 
                if (i + j < data.length) { 
                    byte b = data[i + j]; 
                    hexPart.append(String.format("%02x ", b)); 
                    char c = (b >= 32 && b <= 126) ? (char) b : '.'
                    asciiPart.append(c); 
                } else
                    hexPart.append("   "); 
                
                if (j == 7) { 
                    hexPart.append(" "); 
                
            
            sb.append(hexPart).append(" |").append(asciiPart).append("|\n"); 
        
        return sb.toString(); 
    
   
    private void crack() { 
        emulator.traceCode(); 
        boolean result = security.callStaticJniMethodBoolean(emulator, "check", "1234567"); 
        System.out.println("函数调用结束,返回结果: " + result); 
    
   
    void destroy() { 
        try
            emulator.close(); 
            if (logging) { 
                System.out.println("模拟器已成功关闭"); 
            
        } catch (IOException e) { 
            e.printStackTrace(); 
        
    
   
    public static void main(String[] args) { 
        MainActivity test = new MainActivity(false); 
        String traceFile = "unidbg-android/src/test/resources/traceFunctions.txt"
        try (PrintStream traceStream = new PrintStream(new FileOutputStream(traceFile), true)) { 
            // 在调用 crack 之前附加追踪器,持久化日志 
            test.attachFunctionCallTracer(traceStream); 
            // 执行会触发函数调用的模拟代码 
            test.crack(); 
        } catch (IOException e) { 
            e.printStackTrace(); 
        } finally
            // 确保模拟器资源被释放 
            test.destroy(); 
            System.out.println("总共执行ARM指令数: " + test.instructionCount); 
        
    
}
package com.example.ndkdemo; 
   
import com.github.unidbg.*; 
import com.github.unidbg.arm.backend.Backend; 
import com.github.unidbg.arm.backend.CodeHook; 
import com.github.unidbg.arm.backend.UnHook; 
import com.github.unidbg.arm.backend.Unicorn2Factory; 
import com.github.unidbg.arm.context.Arm64RegisterContext; 
import com.github.unidbg.debugger.BreakPointCallback; 
import com.github.unidbg.debugger.Debugger; 
import com.github.unidbg.debugger.FunctionCallListener; 
import com.github.unidbg.linux.android.AndroidEmulatorBuilder; 
import com.github.unidbg.linux.android.AndroidResolver; 
import com.github.unidbg.linux.android.dvm.DalvikModule; 
import com.github.unidbg.linux.android.dvm.DvmClass; 
import com.github.unidbg.linux.android.dvm.VM; 
import com.github.unidbg.memory.Memory; 
import com.github.unidbg.pointer.UnidbgPointer; 
import unicorn.Arm64Const; 
import unicorn.ArmConst; 
   
import java.io.*; 
   
public class MainActivity { 
    private final AndroidEmulator emulator; 
    private final VM vm; 
    private final Module module; 
    private final DvmClass security; 
    private final boolean logging; 
    private long instructionCount = 0; // 用于指令计数 
   
    MainActivity(boolean logging) { 
        this.logging = logging; 
   
        // 1. 创建模拟器实例 
        emulator = AndroidEmulatorBuilder.for64Bit() 
                .setProcessName("com.example.ndkdemo"
                .addBackendFactory(new Unicorn2Factory(true)) 
                .build(); 
   
        // 2. 设置内存和系统库解析 
        final Memory memory = emulator.getMemory(); 
        memory.setLibraryResolver(new AndroidResolver(23)); 
   
        // 3. 创建Dalvik虚拟机并加载SO文件 
        vm = emulator.createDalvikVM(); 
        vm.setVerbose(logging); 
        DalvikModule dm = vm.loadLibrary(new File("unidbg-android/src/test/java/com/zj/wuaipojie/util/libndkdemo.so"), false); 
        dm.callJNI_OnLoad(emulator); 
        module = dm.getModule(); 
   
        // 4. 获取DVM类 
        security = vm.resolveClass("com/example/ndkdemo/MainActivity"); 
   
        // 5. 设置指令计数钩子 (可选) 
        emulator.getBackend().hook_add_new(new CodeHook() { 
            @Override 
            public void hook(Backend backend, long address, int size, Object user) { 
                instructionCount++; 
            
            @Override 
            public void onAttach(UnHook unHook) {} 
            @Override 
            public void detach() {} 
        }, module.base, module.base + module.size, null); 
   
   
        // 6.设置函数追踪与x0寄存器检查 
        attachTraceAndInspectX0(module.base + 0x15F8); 
    
   
    /** 
     * 【模块化功能】: 附加一个函数调用追踪器 (traceFunctionCall)。 
     * 此功能会监听所有的函数调用,并以树状结构打印出调用关系。 
     * @param traceStream 日志输出流 
     */ 
    public void attachFunctionCallTracer(final PrintStream traceStream) { 
        Debugger debugger = emulator.attach(); 
        System.out.println("函数调用关系追踪器已附加,结果将输出到日志文件。"); 
   
        debugger.traceFunctionCall(null, new FunctionCallListener() { 
            private int depth = 0
   
            private String getPrefix(int currentDepth) { 
                if (currentDepth <= 0) { 
                    return ""
                
                StringBuilder sb = new StringBuilder(); 
                for (int i = 0; i < currentDepth - 1; i++) { 
                    sb.append("│  "); 
                
                sb.append("├─ "); 
                return sb.toString(); 
            
   
            @Override 
            public void onCall(Emulator<?> emulator, long callerAddress, long functionAddress) { 
                String prefix = getPrefix(depth + 1); 
                String details = emulator.getUnwinder().formatAddressDetails(functionAddress); 
                traceStream.printf("%sCALL -> %s%n", prefix, details); 
                depth++; 
            
   
            @Override 
            public void postCall(Emulator<?> emulator, long callerAddress, long functionAddress, Number[] args) { 
                depth--; 
                String prefix = getPrefix(depth + 1); 
                String details = emulator.getUnwinder().formatAddressDetails(functionAddress); 
   
                Backend backend = emulator.getBackend(); 
                Number retVal = emulator.is64Bit() ? backend.reg_read(Arm64Const.UC_ARM64_REG_X0) : backend.reg_read(ArmConst.UC_ARM_REG_R0); 
                long retValLong = retVal.longValue(); 
   
                // 尝试将返回值作为指针解析 
                String retValFormatted = String.format("0x%x", retValLong); 
                UnidbgPointer pointer = UnidbgPointer.pointer(emulator, retValLong); 
                if (pointer != null) { 
                    String cstring = safeReadCString(pointer); 
                    // 如果是一个可打印的字符串,则附加到日志中 
                    if (isPrintable(cstring)) { 
                        retValFormatted += String.format(" -> \"%s\"", cstring); 
                    
                
   
                traceStream.printf("%sRET  <- %s, ret=%s%n", prefix, details, retValFormatted); 
            
        }); 
    
   
    /** 
     * 在指定地址设置断点,实现对一个函数调用的追踪,并在函数返回后检查 x0 寄存器的内容。 
     
     * @param callAddress 函数调用指令(例如 BL, B)的绝对地址 
     */ 
    private void attachTraceAndInspectX0(long callAddress) { 
        final TraceHook[] traceHook = new TraceHook[1]; 
   
        emulator.attach().addBreakPoint(callAddress, (emu, address) -> { 
            traceHook[0] = emu.traceCode(module.base, module.base + module.size); 
            return true
        }); 
   
        long returnAddress = callAddress + 4
        emulator.attach().addBreakPoint(returnAddress, new BreakPointCallback() { 
            @Override 
            public boolean onHit(Emulator<?> emu, long address) { 
                if (traceHook[0] != null) { 
                    traceHook[0].stopTrace(); 
                    System.out.println(); 
                
   
                Arm64RegisterContext ctx = emu.getContext(); 
                long x0 = ctx.getXLong(0); 
                System.out.printf("[+] 检查地址: 0x%x, x0寄存器值: 0x%x\n", address, x0); 
   
                UnidbgPointer pointer = UnidbgPointer.pointer(emu, x0); 
                if (pointer == null) { 
                    System.out.println("[-] x0的值不是一个有效的指针或指向未映射的内存"); 
                    return true
                
   
                String cstring = safeReadCString(pointer); 
                if (cstring != null && isPrintable(cstring)) { 
                    System.out.println("[+] x0指向的字符串: " + cstring); 
                } else
                    System.out.println("[-] x0指向的内容不是一个可打印的字符串"); 
                
   
                int dumpSize = 256
                byte[] data = pointer.getByteArray(0, dumpSize); 
                System.out.println("[+] x0指向内存的HexDump (前" + dumpSize + "字节):"); 
                System.out.println(prettyHexDump(data, x0)); 
                System.out.println("--- x0寄存器检查结束 ---\n"); 
   
                return true
            
        }); 
    
   
    private static String safeReadCString(UnidbgPointer p) { 
        try
            return p.getString(0); 
        } catch (Exception e) { 
            return null
        
    
   
    private static boolean isPrintable(String s) { 
        if (s == null || s.isEmpty()) { 
            return false
        
        int printableChars = 0

[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!

收藏
免费 70
支持
分享
最新回复 (37)
雪    币: 579
活跃值: (575)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
666 膜拜大佬
2025-9-8 09:55
0
雪    币: 1181
活跃值: (1276)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
学习新知识
2025-9-8 09:56
0
雪    币: 268
活跃值: (1323)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
666
2025-9-8 09:57
0
雪    币: 18
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5
学习学习
2025-9-8 10:06
0
雪    币: 3781
活跃值: (4075)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
6
2025-9-8 10:12
0
雪    币: 6
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7
6666
2025-9-8 10:34
0
雪    币: 1401
活跃值: (6858)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
8
1
2025-9-8 11:14
0
雪    币: 387
活跃值: (1536)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
9
2025-9-8 17:02
0
雪    币: 200
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
10
666
2025-9-8 18:14
0
雪    币: 1762
活跃值: (1255)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
11
学习
2025-9-9 10:25
0
雪    币: 7
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
12
学习
2025-9-9 11:37
0
雪    币: 76
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
13
66666666
2025-9-10 09:04
0
雪    币: 3368
活跃值: (6470)
能力值: ( LV11,RANK:185 )
在线值:
发帖
回帖
粉丝
14
我想知道,你睡了吗
2025-9-10 16:17
0
雪    币: 5622
活跃值: (4238)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
15
Thehepta 我想知道,你睡了吗[em_065]
月亮不睡,我不睡
2025-9-10 18:19
0
雪    币: 1159
活跃值: (1261)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
16
感谢分享,干货很多
2025-9-11 09:28
0
雪    币: 226
活跃值: (330)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
ha0
17
感谢分享,干货很多
2025-9-11 16:28
0
雪    币: 13
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
18
ok
2025-9-12 12:49
0
雪    币: 2
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
19
感谢分享
2025-9-12 17:41
0
雪    币: 204
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
20
ok
2025-9-14 18:54
0
雪    币: 227
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
21
123
2025-9-15 11:08
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
22
666
2025-9-17 23:28
0
雪    币: 527
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
23
666
2025-9-17 23:56
0
雪    币: 156
活跃值: (860)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
24
666
2025-9-18 09:22
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
25
111
2025-9-22 14:41
0
游客
登录 | 注册 方可回帖
返回