在Android逆向工程中,Frida作为最流行的动态插桩工具,自然成为了App防护体系的首要针对目标。厂商们设计了层层检测机制,其中基于线程的检测是一种常见且有效的手段。本文将深入探讨这类检测的原理,并分享一种更为优雅的绕过思路。

很多App的检测逻辑会单独开辟一个线程,在后台持续运行各种检测任务——扫描端口、检查/proc文件系统、检测Frida特征等等。这种设计让主线程得以保持流畅,同时检测逻辑得以持续运行。

既然检测在新线程中执行,一个直接的想法便应运而生:拦截线程的创建过程,在检测线程启动之前就将其扼杀在摇篮里。

pthread_create函数是创建新线程的函数,hook这个函数,似乎可以绕过检测?

厂商们同样清楚pthread_create的重要性,早已将其纳入检测范围。如果你尝试Hook这个函数,检测代码会立即触发告警——这是一个典型的"陷阱API"。过度依赖这个入口,等于自己走进了包围圈。我们只能深入pthread_create 内部去找其他的hook点

pthread_create函数的执行流程如下:

pthread_create-> ... -> clone-> 新线程 -> __start_thread-> __pthread_start-> 执行用户代码

  • __start_thread是新线程的入口函数,它将执行__pthread_start函数

  • __pthread_start是新线程的入口函数,它在新线程环境中执行

IDA分析

libc.so是Android系统中C标准库的动态链接实现,libc最重要的作用是封装Linux系统调用,为上层应用提供统一的调用接口。

所以我们需要将libc.so文件拖入到ida中进行分析,先将libc.so文件导出到本地

adb pull /system/lib64/libc.so

拖入到ida中直接搜索对应的pthread_create 函数,我们往下翻

这里有一个clone函数,clone() 是 Linux 中一个强大的系统调用,用于创建新的进程或线程。它是 fork() 的增强版本,可以更精细地控制父子进程之间共享的资源

__pthread_start是新线程启动后执行的函数,这是 glibc 线程库的内部启动函数,最终会调用用户定义的线程函数,我们追进去看

这里就是一个回调函数,去调用用户自定义的函数,我们hook这个__pthread_start函数的时候我们可以hook到线程的创建行为,并且还可以拿到用户定义的线程函数的函数地址

我们把鼠标放在这里按下tab键,查看汇编代码

这里在做的事情就是先从 X19+60内存 中成对加载数据到X8、X0寄存器BLR指令就是函数的间接调用X8就是用户定义的函数,那么这个X19是什么呢?我们往上找

这个X19就是从X0来的,也就是__pthread_start函数的第一个参数

hook __pthread_start

function addr2nice(addr) {
  // 把地址转换为模块名和偏移,例如:libc.so!0x1234
  var module = Process.findModuleByAddress(addr);
  if (module) {
    return module.name + "!" + (addr - module.base).toString(16);
  } else {
    return addr.toString(16);
  }
}

function hook_start_thread() {
  var libc = Process.findModuleByName("libc.so");
  var addr_start_thread = libc.base.add(0xEB828);  // args 这里要根据实际情况修改成对应的偏移地址
  var original_start_thread = new NativeFunction(addr_start_thread, "void", ["pointer"]);
  Interceptor.replace(addr_start_thread, new NativeCallback(function (thread) {
    // args 这里0x60需要替换成实际的偏移
    // 函数第一个参数的地址值偏移60得到用户定义函数的地址
    var user_func_addr = thread.add(0x60).readPointer();
    var nice_addr = addr2nice(user_func_addr);
    console.log("thread start:" + nice_addr);
    if (nice_addr.includes("libfrida.so")) {
      // 检测是否为恶意模块创建的线程
      console.log("frida check thread detected");
    } else {
      // 只有不是恶意模块创建的线程才执行原函数
      original_start_thread(thread);
    }
  }, "void", ["pointer"]));
}

hook_start_thread()