Frida进阶——libnesec.so反调试机制绕过

在安卓应用安全领域,libnesec.so 是一个常见的安全加固库,常用于防止应用被逆向分析和调试。本文将结合实战经验,利用 Frida 绕过 libnesec.so 的反调试检测。

一、Hook android_dlopen_ext

逆向的第一步是信息收集。我们需要准确地知道 libnesec.so 何时被加载到内存中,以便抓准时机采取行动。最直接有效的方法是 Hook android_dlopen_ext

通过 Hook 这个函数,我们可以在其 onEnter 和 onLeave 回调中监控所有被加载的动态库。

function hook_dlopen() {
    const targetLibrary = "libnesec.so";
    var baseAddress = null;

    Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
        onEnter: function (args) {
            var libraryPath = args[0].readCString();
            if (libraryPath && libraryPath.includes(targetLibrary)) {
                console.log("[+] Loading " + targetLibrary + " from: " + libraryPath);
                this.isTargetLib = true;
            }
        },
        onLeave: function (retval) {
            if (this.isTargetLib) {
                console.log("[+] " + targetLibrary + " loaded, handle: " + retval);
                baseAddress = Module.findBaseAddress(targetLibrary);
                console.log("[+] Base address of " + targetLibrary + " is: " + baseAddress);
                // 在这里执行我们的核心绕过逻辑
                bypass_anti_debug(baseAddress);
                this.isTargetLib = false;
            }
        }
    });
}

二、Hook pthread_create

libnesec.so 的一个典型反调试策略是创建新的线程来执行检测任务。这样做的好处是检测逻辑不会阻塞应用主线程,同时也增加了逆向分析的难度。我们的核心任务就是找到这些“间谍”线程,并阻止它们执行。

pthread_create 是 Linux/Android 环境下创建线程的标准函数。通过 Hook 它可以捕获所有新线程的创建动作。其方法原型如下:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);

参数解释:

  • pthread_t *thread(新线程 ID)
  • pthread_attr_t *attr(线程属性,默认 NULL)
  • void *(*start_routine)(void *)(线程入口函数地址)
  • void *arg(传递给线程的参数)

该函数的第三个参数 start_routine 是线程的入口点函数地址,通过捕获第三个参数的值再结合so文件的基地址我们便能找出内存中对应的线程地址;其代码如下:

function bypass_anti_debug(baseAddress) {
  Interceptor.attach(Module.findExportByName("libc.so", "pthread_create"), {
    onEnter: function (args) {
      var thread_start_routine = args[2];
      var offset = thread_start_routine.sub(baseAddress);
      console.log("[*] New thread created with start routine at: " + thread_start_routine);
      console.log("    -> Offset from libnesec.so base: " + offset);
    },
    onLeave: function (retval) {}
  });
}


对目标 app 注入上述代码可以观察到 pthread_create 执行的内存地址与 libnesec.so 基地址的偏移量,如下图所示 0xa0d1c、0xa1ab4

一般情况下,反调试环境检测的线程只会拉起一次,所以我们需要构建一个空的 native 函数将偏移地址为 0xa0d1c 的函数替换换掉即可。如下代码所示:

function bypass_anti_debug(baseAddress) {
  Interceptor.attach(Module.findExportByName("libc.so", "pthread_create"), {
    onEnter: function (args) {
      var thread_start_routine = args[2];
      var offset = thread_start_routine.sub(baseAddress);
      console.log("[*] New thread created with start routine at: " + thread_start_routine);
      console.log("    -> Offset from libnesec.so base: " + offset);

      // 关键:检查是否是我们已知的反调试函数偏移
      // 这个偏移地址需要通过多次运行和观察来确定,例如 0xa05a8
      if (offset.toString() === "0xa0d1c") {
        console.warn("[!] Anti-debugging thread detected at offset: " + offset);
        // 替换它的入口点为一个空函数,让它什么都不做
        Interceptor.replace(thread_start_routine, new NativeCallback(function () {
          console.log("[+] Anti-debugging thread at " + offset + " neutralized.");
          return ptr(0); // 线程函数通常返回一个指针
        }, 'pointer', []));
      }
    },
    onLeave: function (retval) {}
  });
}

三、总结与完整代码

绕过 libnesec.so 的反调试是一个系统工程,需要将多种技术结合起来。核心思想是:

  1. 尽早注入:使用 frida -f 以 spawn 模式启动应用,确保我们的脚本在所有反调试逻辑执行前加载。
  2. 监控加载:通过 Hook android_dlopen_ext来捕获 libnesec.so 的加载时机。
  3. 定位核心:Hook pthread_create,通过分析反调试线程入口点的偏移地址,找到关键的反调试函数。
  4. 釜底抽薪:使用 Interceptor.replace 将反调试函数替换为空函数。

完整代码如下:

function hook_dlopen() {
    const targetLibrary = "libnesec.so";
    var baseAddress = null;

    Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
        onEnter: function (args) {
            var libraryPath = args[0].readCString();
            if (libraryPath && libraryPath.includes(targetLibrary)) {
                console.log("[+] Loading " + targetLibrary + " from: " + libraryPath);
                this.isTargetLib = true;
            }
        },
        onLeave: function (retval) {
            if (this.isTargetLib) {
                console.log("[+] " + targetLibrary + " loaded, handle: " + retval);
                baseAddress = Module.findBaseAddress(targetLibrary);
                console.log("[+] Base address of " + targetLibrary + " is: " + baseAddress);
                // 在这里执行我们的核心绕过逻辑
                bypass_anti_debug(baseAddress);
                this.isTargetLib = false;
            }
        }
    });
}

function bypass_anti_debug(baseAddress) {
    Interceptor.attach(Module.findExportByName("libc.so", "pthread_create"), {
        onEnter: function (args) {
            var thread_start_routine = args[2];
            var offset = thread_start_routine.sub(baseAddress);
            console.log("[*] New thread created with start routine at: " + thread_start_routine);
            console.log("    -> Offset from libnesec.so base: " + offset);

            // 关键:检查是否是我们已知的反调试函数偏移
            // 这个偏移地址需要通过多次运行和观察来确定,例如 0xa05a8
            if (offset.toString() === "0xa0d1c") {
                console.warn("[!] Anti-debugging thread detected at offset: " + offset);
                // 替换它的入口点为一个空函数,让它什么都不做
                Interceptor.replace(thread_start_routine, new NativeCallback(function () {
                    console.log("[+] Anti-debugging thread at " + offset + " neutralized.");
                    return ptr(0); // 线程函数通常返回一个指针
                }, 'pointer', []));
            }
        },
        onLeave: function (retval) {}
    });
}

hook_dlopen()

通过以下命令使用即可:

frida -U -f com.example.app -l frida_bypass_nesec.js

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值