OLLVM 混淆 + VMP 壳照样破!绕过加壳 SDK 的核心检测逻辑

版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/

前言

逆向目标是一个第三方 SDK,核心代码在 so 层,已知 so 有加壳。

调用入口是 *******************************************,会触发 libhexymsb.so 中的 token 和 time 检查方法,目标是绕过检查并成功调用 SDK。

Java 层代码逆向分析

通过 Frida hook 一下 目标类

/**
 * Hook 指定类的所有方法(每个方法所有重载)
 * @param {string} className - Java 类的完整名
 */
function hook_all_methods(className) {
    Java.perform(function () {
        var clazz = Java.use(className);
        var methods = clazz.class.getDeclaredMethods(); // 反射获取所有声明的方法

        var hooked = new Set(); // 用于避免重复 hook 相同方法名(因为多重载)

        methods.forEach(function (m) {
            var methodName = m.getName();

            // 如果这个方法已经 Hook 过,就跳过
            if (hooked.has(methodName)) return;
            hooked.add(methodName);

            try {
                hook_method(className, methodName);
            } catch (e) {
                console.error("❌ Failed to hook " + methodName + ": " + e);
            }
        });
    });
}


setImmediate(function () {
    hook_all_methods("*******************************************");
});

// frida -H 127.0.0.1:1234 -F -l hook_class_methods.js

得到日志如下:

================= HOOK START =================
🎯 Class: *******************************************
🔧 Method: hdic
📥 Arguments:
  [0]: com.superbock.App@48a2d8d
  [1]: com.superbock.ui.StartupActivity
  [2]: 1
📤 Return value: undefined
================== HOOK END ==================

调用的是一个 native 方法:public final native void hdic(Context context, String mainActivityClassName, int type);

word/media/image1.png

JNI 函数地址追踪

so 中并没有找到对应的 native 方法,说明是动态注册的。

word/media/image2.png

通过 jni_addr.py 脚本追踪一下目标类的 jni 函数地址

def main():
    class_name = "*******************************************"

    device = frida.get_device_manager().add_remote_device("127.0.0.1:1234")
    pid = device.get_frontmost_application().pid
    session: frida.core.Session = device.attach(pid)
    script = session.create_script(read_frida_js_source("jni_addr.js"))
    script.on('message', on_message)
    script.load()

    script.exports.getjnimethodaddr(class_name)

    # 退出
    session.detach()

具体参考:逆向 JNI 函数找不到入口?动态注册定位技巧全解析

得到日志如下,hdic 函数入口在 libhexymsb.so,偏移 0x37c3c 的位置

------------ [ #34 Native Method ] ------------
Method Name     : public final native void *******************************************.hdic(android.content.Context,java.lang.String,int)
ArtMethod Ptr   : 0x7d52b1bb38
Native Addr     : 0x7c682c4c3c
Module Name     : libhexymsb.so
Module Offset   : 0x37c3c
Module Base     : 0x7c6828d000
Module Size     : 335872 bytes
Module Path     : /data/app/com.demotestapp.test-9uO6vVLGXVJ71eZiJvIxYQ==/lib/arm64/libhexymsb.so
------------------------------------------------

so 反汇编代码分析

使用 IDA Pro 打开 libhexymsb.so 按 G 跳转到 0x37c3c ,再 F5 一下得到反汇编代码如下:

word/media/image3.png

反汇编代码分析:

1、参数准备逻辑

v15[2] = a3;
v15[3] = a4;
v15[0] = 0;
v15[1] = a2;
v15[4] = a5;

把 a2~a5 填入参数数组 v15,供后续解释器读取。a1 是 JNIEnv*,a2 是 jboject,a3 是 Context,a4 是 String,a5 是 int

2、结构体和 flag 设置

v11 = 0;
v12 = 257;
v14 = 0;
v13 = 1;

这些字节构成一个紧凑的 flag 结构,代表不同标志配置,可能是传递给 vmInterpret 函数的执行参数(例如:执行类型、安全等级、是否调试模式等)。

3、准备参数指针

v6 = &unk_24202;
v7 = 32;
v8 = v15;
v9 = &v11;
v10 = 0;

v6 是一个指针,指向 .rodata 段中的某个地址,由于 IDA 并不知道这块内存是什么类型,所以叫 unk_,即“未知类型”。

.rodata + 0x24202 是一个只读区域的地址,它的第一个字节是 0xC7。

.rodata:0000000000024202 C7                            unk_24202 DCB 0xC7                      ; DATA XREF: sub_37C3C+20↓o

这些变量构成一个参数结构体,最终会被传入:

return vmInterpret(a1, &v6, &off_46C50);
  • a1: JNIEnv指针。

  • &v6: 应该是所有参数结构组合的起始指针。

  • off_46C50: 通常是某个回调表或解释器指令表。

off_46C50 保存的是 sub_438F4 函数地址

.data.rel.ro:0000000000046C50 F4 38 04 00 00 00 00 00       off_46C50 DCQ sub_438F4                 ; DATA XREF: sub_33FF0+3C↑o

sub_438F4 函数地址反汇编代码如下:

word/media/image4.png

VMP 壳分析

使用 Frida Hook 一下 sub_438F4 函数并打印堆栈、参数和返回值

const libName = "libhexymsb.so";
const offset = 0x438F4;

function hookFunction() {
    let baseAddr = Module.findBaseAddress(libName);
    if (!baseAddr) {
        console.error(`❌ 找不到模块 ${libName}`);
        return;
    }
    let funcAddr = baseAddr.add(offset);

    console.log(`✅ Hooking ${libName} at offset 0x${offset.toString(16)} => ${funcAddr}`);

    let callCount = 0;

    Interceptor.attach(funcAddr, {
        onEnter(args) {
            this.callId = ++callCount;
            this.jniEnv = args[0];
            this.arg = args[1].toUInt32();

            this.log = [];
            this.log.push(`\n================== 🚀 Call #${this.callId} Start ==================`);
            this.log.push(`🧵 ThreadId: ${Process.getCurrentThreadId()}`);
            this.log.push(`🔹 JNIEnv*: ${this.jniEnv}`)
            this.log.push(`🔹 unsigned int a2: ${this.arg}`);

            // 可选打印调用栈
            let backtrace = Thread.backtrace(this.context, Backtracer.ACCURATE)
                .map(DebugSymbol.fromAddress)
                .join("\n    ");
            this
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值