frida分析ollvm字符串混淆
字数 2276 2025-08-29 08:29:58

Frida分析OLLVM字符串混淆教学文档

前言

本教程将详细介绍如何使用Frida分析经过OLLVM字符串混淆处理的Android原生库(so文件)。我们将分析三个不同版本的APK,它们具有相同的Java层逻辑,但SO库采用了不同的OLLVM字符串混淆技术。

准备工作

示例APK下载

工具准备

  • Frida
  • IDA Pro或其他反编译工具
  • Android设备或模拟器

Java层分析

三个APK的Java层逻辑一致,主要功能:

  1. 初始界面显示欢迎字符串(通过stringFromJNI函数生成)
  2. 点击sign1按钮会更新显示类似hash的字符串(通过sign1函数生成)

这两个函数都是JNI函数,因此我们需要转向SO层分析。

SO层分析

版本0分析(hellojni_2.0.0.apk)

静态注册与动态注册

  • stringFromJNI:静态注册,可在导出表中直接搜索到
  • sign1:动态注册,导出表中无法直接搜索到

stringFromJNI函数分析

  1. 函数直接将stru_37010指向的内容转换为UTF-8编码字符串返回给Java层
  2. stru_37010以64位双字对形式定义,初始值可能是返回字符串的原始数据
  3. 众多byte_开头的变量(每个占1字节)参与数据处理
  4. 通过DATA XREF注释可知这些数据在多处被引用,参与如datadiv_decode8846988481537047047等函数的复杂处理流程

字符串解密特征

  1. 导出表中可搜索到datadiv_decode函数
  2. 解密函数对字符串的每一位进行异或操作
  3. 手动还原较麻烦,可使用Frida从内存中dump解码后的字符串

解密函数加载时机

  1. 使用IDA快捷键Ctrl+S跳转到.init_array
  2. 发现三个decode函数都在.init_array中加载
  3. 因此可以直接hook 37010地址获取解密后的字符串

sign1函数分析

  1. 最终结果存储在v19中返回
  2. 进入37040同样可以看到datadiv_decode函数
  3. 同样可以dump解密后的字符串

版本0特征总结

  • 导出表中出现.datadiv_decode是OLLVM字符串混淆的特征
  • 解密发生在初始化时(.init_array段)

版本1分析(hellojni_2.0.1.apk)

主要区别

  1. stringFromJNI里的37010进入未发现datadiv_decode函数
  2. 导出表中也没有datadiv_decode
  3. 发现类似std__string___4921590060622252445的交叉引用

解密函数分析

  1. std__string___4921590060622252445实际上就是之前的datadiv_decode函数
  2. 加载时机仍在.init_array
  3. 可以直接打印字符串获取明文

版本1特征总结

  1. 无法在导出表中看到OLLVM字符串混淆的明显特征
  2. 解密函数名称变化,但功能相同
  3. 解密仍在.init_array中加载
  4. 这种情况在64位架构中更常见

版本2分析(hellojni_2.0.2.apk)

主要变化

  1. .init_array中没有解密函数的踪迹
  2. 解密时机改为运行时

stringFromJNI函数分析

  1. 3E160数组转换为Java字符串返回
  2. 解密逻辑:byte_22E80[i + 21 + -18 * (i / 0x12)] ^ byte_22E80[i + 39]
  3. byte_22E80是映射表
  4. 打印0x3E160可获取欢迎字符串

sign1函数分析

  1. 解密代码不在函数内部
  2. 需要在JNI_OnLoad中查找解密逻辑
  3. 发现映射表出现在JNI_OnLoad
  4. 解密发生在JNI_OnLoad

版本2特征总结

  1. 解密时机改为运行时(JNI_OnLoad
  2. 没有明显的.datadiv_decode特征
  3. 需要分析运行时解密逻辑

Frida Hook脚本示例

版本0和1的Hook脚本

// Hook .init_array中的解密函数
Interceptor.attach(Module.findBaseAddress('libhello-jni.so').add(0x37010), {
    onLeave: function(retval) {
        console.log("Decrypted string: " + Memory.readUtf8String(retval));
    }
});

// Hook sign1函数的解密结果
Interceptor.attach(Module.findBaseAddress('libhello-jni.so').add(0x37040), {
    onLeave: function(retval) {
        console.log("Sign1 result: " + Memory.readUtf8String(retval));
    }
});

版本2的Hook脚本

// Hook JNI_OnLoad中的解密过程
Interceptor.attach(Module.findExportByName('libhello-jni.so', 'JNI_OnLoad'), {
    onLeave: function(retval) {
        var decryptedStr = Memory.readUtf8String(Module.findBaseAddress('libhello-jni.so').add(0x3E160));
        console.log("Decrypted welcome string: " + decryptedStr);
    }
});

// Hook sign1函数的解密结果
Interceptor.attach(Module.findBaseAddress('libhello-jni.so').add(0x3E160), {
    onLeave: function(retval) {
        console.log("Sign1 result: " + Memory.readUtf8String(retval));
    }
});

总结与识别特征

OLLVM字符串混淆的识别特征

  1. 版本0

    • 导出表中可见.datadiv_decode函数
    • 解密发生在.init_array
    • 字符串以加密形式存储在数据段
  2. 版本1

    • 导出表中无.datadiv_decode函数
    • 解密函数名称变化(如std__string_前缀)
    • 解密仍在.init_array
    • 64位架构中更常见
  3. 版本2

    • .init_array中的解密函数
    • 解密逻辑在JNI_OnLoad或运行时
    • 需要分析运行时解密算法

分析方法总结

  1. 首先确定解密时机(.init_arrayJNI_OnLoad
  2. 查找解密函数或解密逻辑
  3. 使用Frida在内存中dump解密后的字符串
  4. 对于运行时解密,需要分析解密算法

应对策略

  1. 对于初始化时解密:

    • Hook .init_array中的解密函数
    • 或直接Hook加密字符串的内存地址
  2. 对于运行时解密:

    • 分析JNI_OnLoad函数
    • 跟踪字符串使用过程,找到解密点
    • Hook解密后的内存位置

通过本教程,您应该能够掌握使用Frida分析不同OLLVM字符串混淆技术的方法,并能够根据不同的混淆特征选择合适的分析策略。

Frida分析OLLVM字符串混淆教学文档 前言 本教程将详细介绍如何使用Frida分析经过OLLVM字符串混淆处理的Android原生库(so文件)。我们将分析三个不同版本的APK,它们具有相同的Java层逻辑,但SO库采用了不同的OLLVM字符串混淆技术。 准备工作 示例APK下载 : 链接: https://pan.baidu.com/s/1zTCqXvG9PHxOQAqfoJS-1g 提取码: ttmm 工具准备 : Frida IDA Pro或其他反编译工具 Android设备或模拟器 Java层分析 三个APK的Java层逻辑一致,主要功能: 初始界面显示欢迎字符串(通过 stringFromJNI 函数生成) 点击sign1按钮会更新显示类似hash的字符串(通过 sign1 函数生成) 这两个函数都是JNI函数,因此我们需要转向SO层分析。 SO层分析 版本0分析(hellojni_ 2.0.0.apk) 静态注册与动态注册 stringFromJNI :静态注册,可在导出表中直接搜索到 sign1 :动态注册,导出表中无法直接搜索到 stringFromJNI函数分析 函数直接将 stru_37010 指向的内容转换为UTF-8编码字符串返回给Java层 stru_37010 以64位双字对形式定义,初始值可能是返回字符串的原始数据 众多 byte_ 开头的变量(每个占1字节)参与数据处理 通过 DATA XREF 注释可知这些数据在多处被引用,参与如 datadiv_decode8846988481537047047 等函数的复杂处理流程 字符串解密特征 导出表中可搜索到 datadiv_decode 函数 解密函数对字符串的每一位进行异或操作 手动还原较麻烦,可使用Frida从内存中dump解码后的字符串 解密函数加载时机 使用IDA快捷键 Ctrl+S 跳转到 .init_array 段 发现三个decode函数都在 .init_array 中加载 因此可以直接hook 37010 地址获取解密后的字符串 sign1函数分析 最终结果存储在v19中返回 进入 37040 同样可以看到 datadiv_decode 函数 同样可以dump解密后的字符串 版本0特征总结 导出表中出现 .datadiv_decode 是OLLVM字符串混淆的特征 解密发生在初始化时( .init_array 段) 版本1分析(hellojni_ 2.0.1.apk) 主要区别 从 stringFromJNI 里的 37010 进入未发现 datadiv_decode 函数 导出表中也没有 datadiv_decode 发现类似 std__string___4921590060622252445 的交叉引用 解密函数分析 std__string___4921590060622252445 实际上就是之前的 datadiv_decode 函数 加载时机仍在 .init_array 中 可以直接打印字符串获取明文 版本1特征总结 无法在导出表中看到OLLVM字符串混淆的明显特征 解密函数名称变化,但功能相同 解密仍在 .init_array 中加载 这种情况在64位架构中更常见 版本2分析(hellojni_ 2.0.2.apk) 主要变化 .init_array 中没有解密函数的踪迹 解密时机改为运行时 stringFromJNI函数分析 将 3E160 数组转换为Java字符串返回 解密逻辑: byte_22E80[i + 21 + -18 * (i / 0x12)] ^ byte_22E80[i + 39] byte_22E80 是映射表 打印 0x3E160 可获取欢迎字符串 sign1函数分析 解密代码不在函数内部 需要在 JNI_OnLoad 中查找解密逻辑 发现映射表出现在 JNI_OnLoad 中 解密发生在 JNI_OnLoad 中 版本2特征总结 解密时机改为运行时( JNI_OnLoad ) 没有明显的 .datadiv_decode 特征 需要分析运行时解密逻辑 Frida Hook脚本示例 版本0和1的Hook脚本 版本2的Hook脚本 总结与识别特征 OLLVM字符串混淆的识别特征 版本0 : 导出表中可见 .datadiv_decode 函数 解密发生在 .init_array 段 字符串以加密形式存储在数据段 版本1 : 导出表中无 .datadiv_decode 函数 解密函数名称变化(如 std__string_ 前缀) 解密仍在 .init_array 中 64位架构中更常见 版本2 : 无 .init_array 中的解密函数 解密逻辑在 JNI_OnLoad 或运行时 需要分析运行时解密算法 分析方法总结 首先确定解密时机( .init_array 或 JNI_OnLoad ) 查找解密函数或解密逻辑 使用Frida在内存中dump解密后的字符串 对于运行时解密,需要分析解密算法 应对策略 对于初始化时解密: Hook .init_array 中的解密函数 或直接Hook加密字符串的内存地址 对于运行时解密: 分析 JNI_OnLoad 函数 跟踪字符串使用过程,找到解密点 Hook解密后的内存位置 通过本教程,您应该能够掌握使用Frida分析不同OLLVM字符串混淆技术的方法,并能够根据不同的混淆特征选择合适的分析策略。