利用Frida 分析OLLVM字符串加密算法还原
字数 991 2025-08-19 12:41:34

利用Frida分析OLLVM字符串加密算法还原

1. 背景介绍

本文档详细记录了如何利用Frida工具分析并还原OLLVM字符串加密算法的过程。OLLVM(Obfuscator-LLVM)是一个开源的代码混淆工具,常用于Android Native代码的保护,其中字符串加密是其重要功能之一。

2. 目标分析

2.1 Java层分析

目标应用包含一个HelloJni类,关键代码如下:

public class HelloJni extends AppCompatActivity {
    TextView tv;
    public native String sign1(String str);
    public native String stringFromJNI();

    @Override
    protected void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView(R.layout.activity_hello_jni);
        this.tv = (TextView) findViewById(R.id.hello_textview);
        this.tv.setText(stringFromJNI());
        
        ((Button) findViewById(R.id.button_sign1)).setOnClickListener(
            new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    HelloJni.this.tv.setText(
                        HelloJni.this.sign1(RandomStringUtils.randomAscii(16))
                    );
                }
            }
        );
    }
    
    static {
        System.loadLibrary("hello-jni");
    }
}

关键点:

  • sign1是native方法,接收一个随机16字节ASCII字符串
  • 点击按钮时会调用sign1方法并将结果显示在TextView上

2.2 Native层分析

2.2.1 JNI_OnLoad分析

jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    // ... 省略变量声明 ...
    
    v8 = sub_E76C;  // sign1函数的地址
    v7 = *(_OWORD *)&sign1;  // 注册sign1函数
    
    v5 = (*v6)->FindClass(v6, &xmmword_37050);
    if (!v5 || (((*v4)->RegisterNatives)(v4, v5, &v7, 1LL) & 0x80000000) != 0) {
        return -1;
    }
    return v2;
}

关键点:

  • 使用RegisterNatives注册native方法
  • sub_E76Csign1函数的实现地址
  • 由于OLLVM字符串加密,直接分析困难

2.2.2 sign1函数分析

jstring __fastcall sign1(JNIEnv *env, __int64 a2, void *a3) {
    const char *v5 = (*env)->GetStringUTFChars(env, a3, 0LL);  // 获取输入字符串
    
    // ... 省略中间处理代码 ...
    
    sub_103F0(v10, v11, &value);  // 关键加密函数
    
    // 将value的每个字节格式化为16进制字符串
    sprintf(s, &byte_37040, (unsigned __int8)value);
    sprintf(&s[2], &byte_37040, BYTE1(value));
    // ... 省略类似的sprintf调用 ...
    
    jstring v12 = (*env)->NewStringUTF(env, s);  // 返回结果
    return v12;
}

关键点:

  • 获取输入字符串后调用sub_103F0进行加密
  • 将加密结果格式化为16进制字符串返回

3. Frida辅助分析

3.1 定位sign1函数地址

使用Frida脚本findSign1.js

function hook_sign1() {
    var module_libart = Process.findModuleByName("libart.so");
    var addr_RegisterNatives = null;
    
    // 枚举符号找到RegisterNatives
    var symbols = module_libart.enumerateSymbols();
    for (var i = 0; i < symbols.length; i++) {
        if (symbols[i].name.indexOf("RegisterNatives") > 0) {
            addr_RegisterNatives = symbols[i].address;
        }
    }
    
    // Hook RegisterNatives
    Interceptor.attach(addr_RegisterNatives, {
        onEnter: function(args) {
            var java_class = Java.vm.tryGetEnv().getClassName(args[1]);
            var methods = args[2];
            var method_count = parseInt(args[3]);
            
            for (var i = 0; i < method_count; i++) {
                // 打印方法名和签名
                console.log(methods.add(i*Process.pointerSize*3).readPointer().readCString());
                console.log(methods.add(i*Process.pointerSize*3+Process.pointerSize).readPointer().readCString());
                
                // 获取函数地址
                var fnPtr = methods.add(i*Process.pointerSize*3+Process.pointerSize*2).readPointer();
                var module_so = Process.findModuleByAddress(fnPtr);
                console.log(module_so.name + " addr: " + fnPtr.sub(module_so.base));
            }
        }
    });
}

运行命令:

frida -U -f 包名 -l findSign1.js

结果:

  • 找到sign1函数地址为0xE76C

3.2 固定输入值

为了便于分析,固定Java层输入:

function hook_java() {
    Java.perform(function() {
        var hellojni = Java.use("com.example.hellojni.HelloJni");
        hellojni.sign1.implementation = function(args) {
            return this.sign1("0123456789abcdef");  // 固定输入
        }
    });
}

3.3 分析加密过程

3.3.1 Hook关键函数

function hook_native() {
    var base_hello_jni = Module.findBaseAddress("libhello-jni.so");
    
    // Hook sub_FD90 (结果处理函数)
    Interceptor.attach(base_hello_jni.add(0xFD90), {
        onEnter: function(args) {
            this.arg0 = args[0];
            this.arg1 = args[1];
        },
        onLeave: function(retval) {
            console.log("0xFD90:\r\n", hexdump(this.arg0), "\r\n", hexdump(this.arg1));
        }
    });
    
    // Hook sub_F008 (疑似MD5加密函数)
    Interceptor.attach(base_hello_jni.add(0xF008), {
        onEnter: function(args) {
            this.arg0 = args[0];
            this.arg1 = args[1];
        },
        onLeave: function(retval) {
            console.log("0xF008:\r\n", hexdump(this.arg0), "\r\n", hexdump(this.arg1));
            console.log("0xF008:", ptr(this.arg1).readCString());
        }
    });
}

3.3.2 固定随机数

function hook_libc() {
    var lrand48 = Module.findExportByName("libc.so", "lrand48");
    Interceptor.attach(lrand48, {
        onLeave: function(retval) {
            retval.replace(0xAAAAAAAA);  // 固定随机数返回值
        }
    });
}

4. 算法还原

通过Frida分析发现:

  1. 输入字符串为固定值"0123456789abcdef"
  2. 在加密前会拼接"salt2+"前缀,形成"salt2+0123456789abcdef"
  3. 对该字符串进行MD5加密
  4. 将MD5结果格式化为16进制字符串返回

验证:

md5("salt2+0123456789abcdef") = 5a6ecb4b69e035e521bf582135281509

与应用程序输出一致,确认算法还原成功。

5. 总结

完整流程:

  1. Java层调用native方法sign1,传入随机16字节字符串
  2. Native层拼接固定"salt2+"前缀
  3. 对拼接后的字符串进行MD5加密
  4. 将MD5结果格式化为16进制字符串返回

关键技术点:

  • 使用Frida的RegisterNatives Hook定位native函数地址
  • 通过固定输入简化分析过程
  • 识别并Hook关键加密函数
  • 分析加密前的字符串处理过程
  • 验证加密算法

通过这种方法,可以有效分析OLLVM字符串加密保护的Native代码逻辑。

利用Frida分析OLLVM字符串加密算法还原 1. 背景介绍 本文档详细记录了如何利用Frida工具分析并还原OLLVM字符串加密算法的过程。OLLVM(Obfuscator-LLVM)是一个开源的代码混淆工具,常用于Android Native代码的保护,其中字符串加密是其重要功能之一。 2. 目标分析 2.1 Java层分析 目标应用包含一个 HelloJni 类,关键代码如下: 关键点: sign1 是native方法,接收一个随机16字节ASCII字符串 点击按钮时会调用 sign1 方法并将结果显示在TextView上 2.2 Native层分析 2.2.1 JNI_ OnLoad分析 关键点: 使用 RegisterNatives 注册native方法 sub_E76C 是 sign1 函数的实现地址 由于OLLVM字符串加密,直接分析困难 2.2.2 sign1函数分析 关键点: 获取输入字符串后调用 sub_103F0 进行加密 将加密结果格式化为16进制字符串返回 3. Frida辅助分析 3.1 定位sign1函数地址 使用Frida脚本 findSign1.js : 运行命令: 结果: 找到sign1函数地址为 0xE76C 3.2 固定输入值 为了便于分析,固定Java层输入: 3.3 分析加密过程 3.3.1 Hook关键函数 3.3.2 固定随机数 4. 算法还原 通过Frida分析发现: 输入字符串为固定值"0123456789abcdef" 在加密前会拼接"salt2+"前缀,形成"salt2+0123456789abcdef" 对该字符串进行MD5加密 将MD5结果格式化为16进制字符串返回 验证: 与应用程序输出一致,确认算法还原成功。 5. 总结 完整流程: Java层调用native方法 sign1 ,传入随机16字节字符串 Native层拼接固定"salt2+"前缀 对拼接后的字符串进行MD5加密 将MD5结果格式化为16进制字符串返回 关键技术点: 使用Frida的 RegisterNatives Hook定位native函数地址 通过固定输入简化分析过程 识别并Hook关键加密函数 分析加密前的字符串处理过程 验证加密算法 通过这种方法,可以有效分析OLLVM字符串加密保护的Native代码逻辑。