基于Frida的OLLVM混淆代码动态分析技术研究
字数 1841 2025-12-23 12:14:29

基于Frida的OLLVM混淆代码动态分析技术教学文档

前言

在移动应用安全领域,代码混淆技术是保护商业应用知识产权、抵御逆向工程的重要手段。OLLVM(Obfuscator-LLVM)作为强大的开源混淆工具,通过字符串加密、控制流程平坦化、指令替换等技术显著增加逆向分析难度。传统静态分析方法难以应对复杂混淆,需要Frida等动态插桩框架进行辅助分析。

一、Frida辅助分析OLLVM字符串加密

1.1 字符串加密原理

字符串加密在编译时对明文字符串进行加密存储,避免以明文形式存在于二进制文件中,增加静态分析难度。

1.2 实例分析

1.2.1 Java层与Native层交互

  • Java层调用native函数sign1,模块在libhello-jni.so
  • Native层导出表搜索无目标函数,推测为动态注册
  • 分析JNI_OnLoad函数:
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    JNIEnv *env;
    // 获取JNI环境
    if ((*vm)->GetEnv(vm, &env, 65540LL)) return -1;
    
    // 动态注册Native方法
    jclass clazz = (*env)->FindClass(env, "com/example/hellojni/HelloJni");
    JNINativeMethod methods[] = {{"sign1", "()Ljava/lang/String;", (void*)sub_E76C}};
    (*env)->RegisterNatives(env, clazz, methods, 1);
    return 65540;
}

1.2.2 字符串解密机制

  • JNINativeMethod结构体中字符串被加密(显示为"ycmd;"等乱码)
  • 交叉引用发现.datadiv_decode解密函数被.init_array段调用
  • 程序运行时自动执行解密操作

1.3 Frida动态分析技术

1.3.1 Hook Dump方法

function print_hex_dump(addr) {
    var libhello_base = Module.findBaseAddress("libhello-jni.so");
    console.log(hexdump(libhello_base.add(addr)));
}

function hooknative() {
    print_hex_dump(0x037070); // 解密字符串地址
}

1.3.2 Hook RegisterNatives方法

function hook_libart() {
    var module_libart = Process.findModuleByName("libart.so");
    var symbols = module_libart.enumerateSymbols();
    
    for (var i = 0; i < symbols.length; i++) {
        var name = symbols[i].name;
        if (name.indexOf("RegisterNatives") > 0 && name.indexOf("CheckJNI") == -1) {
            Interceptor.attach(symbols[i].address, {
                onEnter: function(args) {
                    var java_class = Java.vm.tryGetEnv().getClassName(args[1]);
                    var method_count = parseInt(args[3]);
                    console.log("RegisterNatives - Class:", java_class, "Methods:", method_count);
                    
                    // 遍历并打印所有注册的Native方法
                    for (var i = 0; i < method_count; i++) {
                        var method_ptr = args[2].add(i * Process.pointerSize * 3);
                        console.log("Method Name:", method_ptr.readPointer().readCString());
                        console.log("Signature:", method_ptr.add(Process.pointerSize).readPointer().readCString());
                    }
                }
            });
        }
    }
}

二、Frida辅助分析OLLVM控制流程平坦化

2.1 控制流程平坦化原理

将函数原有控制流拆散,平铺到while循环和switch结构中,极大增加逆向分析复杂度。

2.1.1 原始代码示例

int main(int argc, char** argv) {
    int a = atoi(argv[1]);
    if(a == 0) return 1;
    else return 10;
    return 0;
}

2.1.2 平坦化后代码

int main(int argc, char** argv) {
    int a = atoi(argv[1]);
    int b = 0;
    while(1) {
        switch(b) {
            case 0: 
                if(a == 0) b = 1;
                else b = 2;
                break;
            case 1: return 1;
            case 2: return 10;
            default: break;
        }
    }
    return 0;
}

2.2 动态分析策略

2.2.1 关键点Hook技术

  • 从输入参数开始逆向追踪
  • 从输出结果正向追踪
  • 交替使用两种分析方式

2.2.2 Hook NewStringUTF获取返回结果

function hook_libart() {
    var module_libart = Process.findModuleByName("libart.so");
    var symbols = module_libart.enumerateSymbols();
    
    for (var i = 0; i < symbols.length; i++) {
        var name = symbols[i].name;
        if (name.indexOf("NewStringUTF") > 0) {
            Interceptor.attach(symbols[i].address, {
                onEnter: function(args) {
                    console.log("[NewStringUTF Result]:", ptr(args[1]).readCString());
                }
            });
        }
    }
}

2.2.3 固定输入参数简化分析

function hook_java() {
    Java.perform(function() {
        var HelloJni = Java.use("com.example.hellojni.HelloJni");
        HelloJni.sign2.implementation = function(arg0) {
            // 固定输入参数,排除干扰
            var result = this.sign2("adkliowpwkklsap", "0987654321adksoi");
            console.log("[Fixed Input Result]:", result);
            return result;
        };
    });
}

2.3 实际案例分析

2.3.1 算法识别过程

  1. Hook NewStringUTF得到密文:ea0973cd62f76a7336874be31cfd7540
  2. 逆向追踪发现base64编码特征
  3. 进一步分析发现MD5加密常量:0xD76AA478
  4. 确认加密流程:输入拼接 → Base64 → MD5

2.3.2 关键函数Hook脚本

function hook_native_addr(addr, idb_addr) {
    var base = Module.findBaseAddress("libhello-jni.so");
    Interceptor.attach(addr, {
        onEnter: function(args) {
            this.args = [args[0], args[1], args[2], args[3]];
            this.lr = this.context.lr;
        },
        onLeave: function(retval) {
            console.log("Function:", ptr(idb_addr));
            console.log("LR:", ptr(this.lr).sub(base));
            console.log("Args:", this.args.map(hex_dump));
            console.log("Return:", hex_dump(retval));
        }
    });
}

// 批量Hook关键函数
function hook_critical_functions() {
    var base = Module.findBaseAddress("libhello-jni.so");
    var functions = [0x15F1C, 0x162B8, 0x154D4, 0x158AC, 0x14844];
    functions.forEach(addr => hook_native_addr(base.add(addr), addr));
}

三、Frida辅助分析OLLVM指令替换

3.1 指令替换原理

用功能等价但结构更复杂的指令序列替换原始简单指令,保持逻辑不变但增加理解难度。

3.1.1 常见替换规则

  • 加法替换

    • a = b + ca = b - (-c)
    • a = b + cr = rand(); a = b + r; a = a + c; a = a - r
  • 减法替换

    • a = b - ca = b + (-c)
    • a = b - cr = rand(); a = b + r; a = a - c; a = a - r
  • 逻辑运算替换

    • AND: a = b & ca = (b ^ ~c) & b
    • OR: a = b | ca = (b & c) | (b ^ c)
    • XOR: a = a ^ ba = (~a & b) | (a & ~b)

3.2 动态分析技术

3.2.1 寄存器值提取

针对指令替换,通过Inline Hook提取关键寄存器值:

function hook_instruction_replace() {
    var base = Module.findBaseAddress("libhello-jni.so");
    var target_addr = base.add(0x12345); // 替换指令地址
    
    Interceptor.attach(target_addr, {
        onEnter: function(args) {
            // 读取关键寄存器值
            var w8 = this.context.x8.toInt32();
            var w9 = this.context.x9.toInt32();
            console.log("Instruction Replacement Analysis:");
            console.log("W8:", w8.toString(16));
            console.log("W9:", w9.toString(16));
            
            // 记录计算过程
            this.calculation_step = 0;
        },
        onLeave: function(retval) {
            // 分析替换指令的计算结果
            var final_result = this.context.x8.toInt32();
            console.log("Final Result:", final_result.toString(16));
        }
    });
}

3.2.2 内存访问监控

监控关键内存写入操作,分析指令替换的实际效果:

function monitor_memory_access() {
    var target_range = [0x30000, 0x40000]; // 目标内存范围
    var base = Module.findBaseAddress("libhello-jni.so");
    
    // 监控内存写入
    MemoryAccessMonitor.enable({
        base: base.add(target_range[0]),
        size: target_range[1] - target_range[0]
    }, {
        onAccess: function(details) {
            if (details.operation === 'write') {
                console.log("Memory Write at:", details.address.sub(base));
                console.log("Value:", details.value.toInt32());
                console.log("From:", details.from.sub(base));
            }
        }
    });
}

四、综合分析与实战技巧

4.1 动态分析工作流

  1. 环境准备:安装Frida,配置测试环境
  2. 初步识别:Hook标准库函数确定输入输出
  3. 参数固定:稳定输入排除干扰因素
  4. 关键点定位:通过交叉引用确定关键函数
  5. 逐层分析:从外到内逐步分析加密逻辑
  6. 算法识别:通过常量、特征识别标准算法
  7. 验证确认:对比计算结果验证分析正确性

4.2 高级Hook技巧

4.2.1 条件Hook

function conditional_hook(addr, condition) {
    Interceptor.attach(addr, {
        onEnter: function(args) {
            if (condition(args)) {
                this.should_log = true;
                console.log("Condition met, starting trace...");
            } else {
                this.should_log = false;
            }
        },
        onLeave: function(retval) {
            if (this.should_log) {
                console.log("Function completed, return:", retval);
            }
        }
    });
}

4.2.2 调用栈追踪

function trace_call_stack() {
    var base = Module.findBaseAddress("libhello-jni.so");
    
    Interceptor.attach(Module.findExportByName("libc.so", "malloc"), {
        onEnter: function(args) {
            var backtrace = Thread.backtrace(this.context, Backtracer.ACCURATE);
            console.log("Call stack for malloc:");
            backtrace.forEach(addr => {
                console.log("  ", addr.sub(base));
            });
        }
    });
}

五、总结

通过Frida动态分析OLLVM混淆代码,可以有效应对字符串加密、控制流程平坦化和指令替换等混淆技术。关键成功因素包括:

  1. 系统化的分析思路:从外到内,从输入输出到中间过程
  2. 精准的Hook定位:针对关键函数和内存访问点
  3. 耐心的问题排查:混淆代码需要逐层分析和验证
  4. 经验积累:熟悉常见加密算法特征和混淆模式

本教学文档提供了完整的技术方案和实战代码,可作为分析OLLVM混淆应用的标准化流程参考。

基于Frida的OLLVM混淆代码动态分析技术教学文档 前言 在移动应用安全领域,代码混淆技术是保护商业应用知识产权、抵御逆向工程的重要手段。OLLVM(Obfuscator-LLVM)作为强大的开源混淆工具,通过字符串加密、控制流程平坦化、指令替换等技术显著增加逆向分析难度。传统静态分析方法难以应对复杂混淆,需要Frida等动态插桩框架进行辅助分析。 一、Frida辅助分析OLLVM字符串加密 1.1 字符串加密原理 字符串加密在编译时对明文字符串进行加密存储,避免以明文形式存在于二进制文件中,增加静态分析难度。 1.2 实例分析 1.2.1 Java层与Native层交互 Java层调用native函数 sign1 ,模块在 libhello-jni.so Native层导出表搜索无目标函数,推测为动态注册 分析 JNI_OnLoad 函数: 1.2.2 字符串解密机制 JNINativeMethod 结构体中字符串被加密(显示为"ycmd;"等乱码) 交叉引用发现 .datadiv_decode 解密函数被 .init_array 段调用 程序运行时自动执行解密操作 1.3 Frida动态分析技术 1.3.1 Hook Dump方法 1.3.2 Hook RegisterNatives方法 二、Frida辅助分析OLLVM控制流程平坦化 2.1 控制流程平坦化原理 将函数原有控制流拆散,平铺到while循环和switch结构中,极大增加逆向分析复杂度。 2.1.1 原始代码示例 2.1.2 平坦化后代码 2.2 动态分析策略 2.2.1 关键点Hook技术 从输入参数开始逆向追踪 从输出结果正向追踪 交替使用两种分析方式 2.2.2 Hook NewStringUTF获取返回结果 2.2.3 固定输入参数简化分析 2.3 实际案例分析 2.3.1 算法识别过程 Hook NewStringUTF 得到密文: ea0973cd62f76a7336874be31cfd7540 逆向追踪发现base64编码特征 进一步分析发现MD5加密常量: 0xD76AA478 确认加密流程:输入拼接 → Base64 → MD5 2.3.2 关键函数Hook脚本 三、Frida辅助分析OLLVM指令替换 3.1 指令替换原理 用功能等价但结构更复杂的指令序列替换原始简单指令,保持逻辑不变但增加理解难度。 3.1.1 常见替换规则 加法替换 : a = b + c → a = b - (-c) a = b + c → r = rand(); a = b + r; a = a + c; a = a - r 减法替换 : a = b - c → a = b + (-c) a = b - c → r = rand(); a = b + r; a = a - c; a = a - r 逻辑运算替换 : AND: a = b & c → a = (b ^ ~c) & b OR: a = b | c → a = (b & c) | (b ^ c) XOR: a = a ^ b → a = (~a & b) | (a & ~b) 3.2 动态分析技术 3.2.1 寄存器值提取 针对指令替换,通过Inline Hook提取关键寄存器值: 3.2.2 内存访问监控 监控关键内存写入操作,分析指令替换的实际效果: 四、综合分析与实战技巧 4.1 动态分析工作流 环境准备 :安装Frida,配置测试环境 初步识别 :Hook标准库函数确定输入输出 参数固定 :稳定输入排除干扰因素 关键点定位 :通过交叉引用确定关键函数 逐层分析 :从外到内逐步分析加密逻辑 算法识别 :通过常量、特征识别标准算法 验证确认 :对比计算结果验证分析正确性 4.2 高级Hook技巧 4.2.1 条件Hook 4.2.2 调用栈追踪 五、总结 通过Frida动态分析OLLVM混淆代码,可以有效应对字符串加密、控制流程平坦化和指令替换等混淆技术。关键成功因素包括: 系统化的分析思路 :从外到内,从输入输出到中间过程 精准的Hook定位 :针对关键函数和内存访问点 耐心的问题排查 :混淆代码需要逐层分析和验证 经验积累 :熟悉常见加密算法特征和混淆模式 本教学文档提供了完整的技术方案和实战代码,可作为分析OLLVM混淆应用的标准化流程参考。