对一些app进行初步的分析之后发现,很多app都集成了该sdk,绕过方法网上记载的也是很多,这篇文章深入挖掘一下如何绕过检测的始末。
这里直接选用一款使用该sdk的app作为demo展开分析,直接挂载frida之后会直接死掉,所以先检查一下是在挂载了哪个具体的so文件之后挂掉的,定位到该so文件之后挂掉了

然后需要具体定位一下是在该so文件的具体哪个地方导致的frida死掉,使用下面这段脚本,过滤到目标so之后,看是否能正常打印Jni_Onload的函数
结果如下,很明显是没有走到Jni_OnLoad的,故而可以初步判断该检测是在so的init_proc中或者init_array段做的:

为了进一步去判断该程序的检测退出的触发,既然挂上Frida后进程就退出,那么我们就来分析是调用哪个系统调用退出的,可以通过strace查看系统调用,但在执行strace时需要在dlopen加载目标so文件之前让线程sleep 10秒,以便留出strace执行时机。使用脚本如下:
上述脚本执行之后,找到目标进程的pid之后执行命令strace -e trace=process -i -f -p "目标pid",挂载系统函数的调用监视
使用strace在进入目标检测so之前挂载上之后截取了目标进程的一系列系统调用,主要关注导致进程退出的系统调用,由下图可见是在进程下面的一个线程实现的闪退效果,执行了系统调用exit_group(0 <unfinished ...>
同时查看了目标进程中的libc的地址也与导致闪退的地址不符,这就说明这个内容是动态加载的,故而会有一个mmap创建内存的操作

动态释放代码一定是要操作内存的,接下来我们用前面相同的逻辑,用strace查看调用了哪些和内存相关的系统调用,strace -e trace=process,memory -i -f -p "目标pid",如下图,mmap一块儿内存之后执行了造成闪退的系统调用(注意:下图是再一次的启动进程打印出来的内容,故而地址与上图可能有点出入)

于是我们可以hook mmap打印一下调用栈,从而定位到在目标so文件中创建这块内存造成闪退的偏移地址
会发现能够打印mmap的地址,但是还没到打印使用该函数的调用栈的时候就挂掉了,此处我理解的是出现这种问题的原因是做hook的时机还是不对,我们的hook还是被检测到了

因此对于hook的时机问题再探究一下,这里借用一张安卓中so文件的加载图来分析一下何时hook才能实现我们的目的:

上图描述的更多的是一个流程,结合如下的安卓源码,我理解的是do_dlopen和call_constructors应该是包含关系,call_constructors于do_dlopen的流程内执行
跟进到call_constructors函数之后会发现会先调用.init_xxxx函数,接着调用.init_array中的函数

故而我们的hook应该更精确到call_constructors,所以可以把钩子挂载在call_constructors函数处,来执行我们对目标so文件中mmap系统调用的拦截
结果如下,成功得到了崩溃前对应的mmap函数的调用栈

那么现在我们能够通过上述最后hook到的mmap调用的偏移定位到目标so中可能存在检测的位置,从而一步步往上交叉引用来进行分析,由调用栈的最顶层0x2358C可以跟到下图这个位置,可以大概看出似乎是在解密一段shellcode然后开辟内存来执行

使用脚本将此执行的shellcode dump下来之后选择arm64架构进行反编译之后发现还是一段跳板,有跳转到指定地址执行的逻辑,看来是为了防止直接调用系统函数被轻易追踪到而设计。由函数sub_234E0一路交叉引用上去,其上一层有一段检查dex文件完整性的逻辑


继续由函数sub_11FA4往上回溯,发现于函数sub_19E0C中出现一段无限循环持续检测设备是否处于ADB调试模式的逻辑,当在 sub_19E0C 函数中检测到ADB调试模式时,会调用sub_17C8C和sub_19A58这两个函数进行进一步的环境验证,如若被检测出来了的话直接走我们刚刚交叉引用上来的闪退逻辑

继续往上回溯,发现下面第二张图中调用了sub_19E0C,但是调用的方法有点奇怪,是作为v24函数的参数进行使用的,同时该段通过动态解密的操作,使用解密后的第一个字符串作为库名,调用dlopen动态加载该库,使用解密后的第二个字符串作为函数名,调用dlsym获取该函数地址,同时最后获取到的函数使用了sub_1B8D4和sub_19E0C作为参数,sub_19E0C是我们回溯上来的引线


所以这里要搞懂这个函数中做了一件什么事,就得知道它调用dlsym获取了哪个函数的地址,从而进行使用,使用下列frida的脚本进行hook,同样是在call_constructors处做挂载
得到结果如下,很明显那么上面那段函数就是去动态获取了libc.so库中的地址,读取了函数pthread_create的地址,然后创建了两个线程,最后都有执行到闪退函数sub_234E0的逻辑

那么这段逻辑就稍微明了了,sub_1B924中动态解密了库名libc.so和函数名pthread_create,开创了两个检测的线程

对此处几个关键函数进行分析之后可知如下图,其中标红的三个函数中都有开创新线程执行持续扫描一些特征的操作:

那么此时我们继续沿着上图有三处创建新线程的框架函数继续往上回溯,发现到达了init_proc,也就是so加载之后走的第一个流程块,其中框出来的即是我们上图中的内容,因此我们可以根据造成程序异常退出的这三处创建处以及根据创建线程必走的一些系统调用来bypass掉这个检测。

由上述分析我们可以得知,三处创建线程逻辑的地址,分别于sub_1BEC4函数中建立,根据上文中的详细描述可以锁定到这几处:


可以知道创建线程之后执行的函数分别是sub_1B8D4、sub_19E0C、sub_1C544,故而可以直接将几个线程执行的函数直接patch掉,实现绕过,但是是有下列脚本之后还是被杀掉了

所以还是有点问题,我们继续分析,发现sub_1B8D4和sub_19E0C两个函数最终都会调用sub_234E0这个函数来实现闪退,那么我们直接patch掉这个函数不就等同于对这两个线程函数检测的bypass了嘛

然后发现在线程函数sub_1C544中对应执行类似的持续扫描闪退的操作的函数应该是sub_26334函数


有此重大发现之后可以将bypass的脚本更新为如下,如此可以最小程度上的对程序进行修改的同时实现bypass:
可以成功绕过:

此文分析仅代表个人见解,希望师傅们多多指正捏~
参考文章:参考文章1
function hookDlopen() {
let linker64_base_addr = Module.getBaseAddress('linker64')
let offset = 0x3ba00 // __dl__Z9do_dlopenPKciPK17android_dlextinfoPKv
let android_dlopen_ext = linker64_base_addr.add(offset)
Interceptor.attach(android_dlopen_ext, {
onEnter: function(args){//主要逻辑是hook linker64中的do_dlopen函数,然后在该函数返回的时候检查目标检测库是否加载
this.name = args[0].readCString()
console.log(`dlopen onEnter ${this.name}`)
}, onLeave: function(retval){
console.log(`dlopen onLeave name: ${this.name}`)
if (this.name != null && this.name.indexOf('目标so文件') >= 0) {
let JNI_OnLoad = Module.getExportByName(this.name, 'JNI_OnLoad')
console.log(`dlopen onLeave JNI_OnLoad: ${JNI_OnLoad}`)
}//如果加载了目标库的话则获取该库的 JNI_OnLoad 函数地址并打印
}
})
}
function hookDlopen() {
let linker64_base_addr = Module.getBaseAddress('linker64')
let offset = 0x3ba00 // __dl__Z9do_dlopenPKciPK17android_dlextinfoPKv
let android_dlopen_ext = linker64_base_addr.add(offset)
Interceptor.attach(android_dlopen_ext, {
onEnter: function(args){//主要逻辑是hook linker64中的do_dlopen函数,然后在该函数返回的时候检查目标检测库是否加载
this.name = args[0].readCString()
console.log(`dlopen onEnter ${this.name}`)
}, onLeave: function(retval){
console.log(`dlopen onLeave name: ${this.name}`)
if (this.name != null && this.name.indexOf('目标so文件') >= 0) {
let JNI_OnLoad = Module.getExportByName(this.name, 'JNI_OnLoad')
console.log(`dlopen onLeave JNI_OnLoad: ${JNI_OnLoad}`)
}//如果加载了目标库的话则获取该库的 JNI_OnLoad 函数地址并打印
}
})
}
function hookDlopen() {
// 获取libc中的sleep函数
let sleep = new NativeFunction(Module.getExportByName('libc.so', 'sleep'), 'int', ['int'])
let linker64_base_addr = Module.getBaseAddress('linker64')
let offset = 0x3ba00 // __dl__Z9do_dlopenPKciPK17android_dlextinfoPKv
//do_dlopen的offset使用命令:readelf -sW /apex/com.android.runtime/bin/linker64 | grep do_dlopen
let android_dlopen_ext = linker64_base_addr.add(offset)
Interceptor.attach(android_dlopen_ext, {
onEnter: function(args){
this.name = args[0].readCString()
console.log(`dlopen onEnter ${this.name}`)
if (this.name != null && this.name.indexOf('目标so文件') >= 0) {
sleep(10) // sleep10秒留出strace执行时机
}
}, onLeave: function(retval){
console.log(`dlopen onLeave name: ${this.name}`)
}
})
}
setImmediate(hookDlopen)
function hookDlopen() {
// 获取libc中的sleep函数
let sleep = new NativeFunction(Module.getExportByName('libc.so', 'sleep'), 'int', ['int'])
let linker64_base_addr = Module.getBaseAddress('linker64')
let offset = 0x3ba00 // __dl__Z9do_dlopenPKciPK17android_dlextinfoPKv
//do_dlopen的offset使用命令:readelf -sW /apex/com.android.runtime/bin/linker64 | grep do_dlopen
let android_dlopen_ext = linker64_base_addr.add(offset)
Interceptor.attach(android_dlopen_ext, {
onEnter: function(args){
this.name = args[0].readCString()
console.log(`dlopen onEnter ${this.name}`)
if (this.name != null && this.name.indexOf('目标so文件') >= 0) {
sleep(10) // sleep10秒留出strace执行时机
}
}, onLeave: function(retval){
console.log(`dlopen onLeave name: ${this.name}`)
}
})
}
setImmediate(hookDlopen)
function hook_mmap() {//打印一下mmap系统调用的调用栈,从而追踪调用源
const mmap = Module.getExportByName("libc.so", "mmap");
console.log("mmap address: " + mmap);
Interceptor.attach(mmap, {
onEnter: function (args) {
let length = args[1].toString(16)
if (parseInt(length, 16) == 28) {
console.log('backtrace:\n' + Thread.backtrace(this.context, Backtracer.ACCURATE)
.map(DebugSymbol.fromAddress).join('\n') + '\n');
}
}
})
}
function hookDlopen() {
// 获取libc中的sleep函数
let sleep = new NativeFunction(Module.getExportByName('libc.so', 'sleep'), 'int', ['int'])
let linker64_base_addr = Module.getBaseAddress('linker64')
let offset = 0x3ba00 // __dl__Z9do_dlopenPKciPK17android_dlextinfoPKv
let android_dlopen_ext = linker64_base_addr.add(offset)
Interceptor.attach(android_dlopen_ext, {
onEnter: function(args){
this.name = args[0].readCString()
console.log(`dlopen onEnter ${this.name}`)
if (this.name != null && this.name.indexOf('目标so文件') >= 0) {
//sleep(15) // sleep10秒留出strace执行时机
hook_mmap()
}
}, onLeave: function(retval){
console.log(`dlopen onLeave name: ${this.name}`)
}
})
}
hookDlopen()
function hook_mmap() {//打印一下mmap系统调用的调用栈,从而追踪调用源
const mmap = Module.getExportByName("libc.so", "mmap");
console.log("mmap address: " + mmap);
Interceptor.attach(mmap, {
onEnter: function (args) {
let length = args[1].toString(16)
if (parseInt(length, 16) == 28) {
console.log('backtrace:\n' + Thread.backtrace(this.context, Backtracer.ACCURATE)
.map(DebugSymbol.fromAddress).join('\n') + '\n');
}
}
})
}
function hookDlopen() {
// 获取libc中的sleep函数
let sleep = new NativeFunction(Module.getExportByName('libc.so', 'sleep'), 'int', ['int'])
let linker64_base_addr = Module.getBaseAddress('linker64')
let offset = 0x3ba00 // __dl__Z9do_dlopenPKciPK17android_dlextinfoPKv
let android_dlopen_ext = linker64_base_addr.add(offset)
Interceptor.attach(android_dlopen_ext, {
onEnter: function(args){
this.name = args[0].readCString()
console.log(`dlopen onEnter ${this.name}`)
if (this.name != null && this.name.indexOf('目标so文件') >= 0) {
//sleep(15) // sleep10秒留出strace执行时机
hook_mmap()
}
}, onLeave: function(retval){
console.log(`dlopen onLeave name: ${this.name}`)
}
})
}
hookDlopen()
void* do_dlopen(const char* name, int flags,
const android_dlextinfo* extinfo,
const void* caller_addr) {
std::string trace_prefix = std::string("dlopen: ") + (name == nullptr ? "(nullptr)" : name);
ScopedTrace trace(trace_prefix.c_str());
ScopedTrace loading_trace((trace_prefix + " - loading and linking").c_str());
soinfo* const caller = find_containing_library(caller_addr);
android_namespace_t* ns = get_caller_namespace(caller);
LD_LOG(kLogDlopen,
"dlopen(name=\"%s\", flags=0x%x, extinfo=%s, caller=\"%s\", caller_ns=%s@%p, targetSdkVersion=%i) ...",
name,
flags,
android_dlextinfo_to_string(extinfo).c_str(),
caller == nullptr ? "(null)" : caller->get_realpath(),
ns == nullptr ? "(null)" : ns->get_name(),
ns,
get_application_target_sdk_version());
auto purge_guard = android::base::make_scope_guard([&]() { purge_unused_memory(); });
auto failure_guard = android::base::make_scope_guard(
[&]() { LD_LOG(kLogDlopen, "... dlopen failed: %s", linker_get_error_buffer()); });
if ((flags & ~(RTLD_NOW|RTLD_LAZY|RTLD_LOCAL|RTLD_GLOBAL|RTLD_NODELETE|RTLD_NOLOAD)) != 0) {
DL_OPEN_ERR("invalid flags to dlopen: %x", flags);
return nullptr;
}
if (extinfo != nullptr) {
if ((extinfo->flags & ~(ANDROID_DLEXT_VALID_FLAG_BITS)) != 0) {
DL_OPEN_ERR("invalid extended flags to android_dlopen_ext: 0x%" PRIx64, extinfo->flags);
return nullptr;
}
if ((extinfo->flags & ANDROID_DLEXT_USE_LIBRARY_FD) == 0 &&
(extinfo->flags & ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET) != 0) {
DL_OPEN_ERR("invalid extended flag combination (ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET without "
"ANDROID_DLEXT_USE_LIBRARY_FD): 0x%" PRIx64, extinfo->flags);
return nullptr;
}
if ((extinfo->flags & ANDROID_DLEXT_USE_NAMESPACE) != 0) {
if (extinfo->library_namespace == nullptr) {
DL_OPEN_ERR("ANDROID_DLEXT_USE_NAMESPACE is set but extinfo->library_namespace is null");
return nullptr;
}
ns = extinfo->library_namespace;
}
}
// Workaround for dlopen(/system/lib/<soname>) when .so is in /apex. http://b/121248172
// The workaround works only when targetSdkVersion < Q.
std::string name_to_apex;
if (translateSystemPathToApexPath(name, &name_to_apex)) {
const char* new_name = name_to_apex.c_str();
LD_LOG(kLogDlopen, "dlopen considering translation from %s to APEX path %s",
name,
new_name);
// Some APEXs could be optionally disabled. Only translate the path
// when the old file is absent and the new file exists.
// TODO(b/124218500): Re-enable it once app compat issue is resolved
/*
if (file_exists(name)) {
LD_LOG(kLogDlopen, "dlopen %s exists, not translating", name);
} else
*/
if (!file_exists(new_name)) {
LD_LOG(kLogDlopen, "dlopen %s does not exist, not translating",
new_name);
} else {
LD_LOG(kLogDlopen, "dlopen translation accepted: using %s", new_name);
name = new_name;
}
}
// End Workaround for dlopen(/system/lib/<soname>) when .so is in /apex.
std::string translated_name_holder;
assert(!g_is_hwasan || !g_is_asan);
const char* translated_name = name;
if (g_is_asan && translated_name != nullptr && translated_name[0] == '/') {
char original_path[PATH_MAX];
if (realpath(name, original_path) != nullptr) {
translated_name_holder = std::string(kAsanLibDirPrefix) + original_path;
if (file_exists(translated_name_holder.c_str())) {
soinfo* si = nullptr;
if (find_loaded_library_by_realpath(ns, original_path, true, &si)) {
PRINT("linker_asan dlopen NOT translating \"%s\" -> \"%s\": library already loaded", name,
translated_name_holder.c_str());
} else {
PRINT("linker_asan dlopen translating \"%s\" -> \"%s\"", name, translated_name);
translated_name = translated_name_holder.c_str();
}
}
}
} else if (g_is_hwasan && translated_name != nullptr && translated_name[0] == '/') {
char original_path[PATH_MAX];
if (realpath(name, original_path) != nullptr) {
// Keep this the same as CreateHwasanPath in system/linkerconfig/modules/namespace.cc.
std::string path(original_path);
auto slash = path.rfind('/');
if (slash != std::string::npos || slash != path.size() - 1) {
translated_name_holder = path.substr(0, slash) + "/hwasan" + path.substr(slash);
}
if (!translated_name_holder.empty() && file_exists(translated_name_holder.c_str())) {
soinfo* si = nullptr;
if (find_loaded_library_by_realpath(ns, original_path, true, &si)) {
PRINT("linker_hwasan dlopen NOT translating \"%s\" -> \"%s\": library already loaded", name,
translated_name_holder.c_str());
} else {
PRINT("linker_hwasan dlopen translating \"%s\" -> \"%s\"", name, translated_name);
translated_name = translated_name_holder.c_str();
}
}
}
}
ProtectedDataGuard guard;
soinfo* si = find_library(ns, translated_name, flags, extinfo, caller);
loading_trace.End();
if (si != nullptr) {
void* handle = si->to_handle();
LD_LOG(kLogDlopen,
"... dlopen calling constructors: realpath=\"%s\", soname=\"%s\", handle=%p",
si->get_realpath(), si->get_soname(), handle);
si->call_constructors();//!!!重点看这里!!!
failure_guard.Disable();
LD_LOG(kLogDlopen,
"... dlopen successful: realpath=\"%s\", soname=\"%s\", handle=%p",
si->get_realpath(), si->get_soname(), handle);
return handle;
}
return nullptr;
}
void* do_dlopen(const char* name, int flags,
const android_dlextinfo* extinfo,
const void* caller_addr) {
std::string trace_prefix = std::string("dlopen: ") + (name == nullptr ? "(nullptr)" : name);
ScopedTrace trace(trace_prefix.c_str());
ScopedTrace loading_trace((trace_prefix + " - loading and linking").c_str());
soinfo* const caller = find_containing_library(caller_addr);
android_namespace_t* ns = get_caller_namespace(caller);
LD_LOG(kLogDlopen,
"dlopen(name=\"%s\", flags=0x%x, extinfo=%s, caller=\"%s\", caller_ns=%s@%p, targetSdkVersion=%i) ...",
name,
flags,
android_dlextinfo_to_string(extinfo).c_str(),
caller == nullptr ? "(null)" : caller->get_realpath(),
ns == nullptr ? "(null)" : ns->get_name(),
ns,
get_application_target_sdk_version());
auto purge_guard = android::base::make_scope_guard([&]() { purge_unused_memory(); });
auto failure_guard = android::base::make_scope_guard(
[&]() { LD_LOG(kLogDlopen, "... dlopen failed: %s", linker_get_error_buffer()); });
if ((flags & ~(RTLD_NOW|RTLD_LAZY|RTLD_LOCAL|RTLD_GLOBAL|RTLD_NODELETE|RTLD_NOLOAD)) != 0) {
DL_OPEN_ERR("invalid flags to dlopen: %x", flags);
return nullptr;
}
if (extinfo != nullptr) {
if ((extinfo->flags & ~(ANDROID_DLEXT_VALID_FLAG_BITS)) != 0) {
DL_OPEN_ERR("invalid extended flags to android_dlopen_ext: 0x%" PRIx64, extinfo->flags);
return nullptr;
}
if ((extinfo->flags & ANDROID_DLEXT_USE_LIBRARY_FD) == 0 &&
(extinfo->flags & ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET) != 0) {
DL_OPEN_ERR("invalid extended flag combination (ANDROID_DLEXT_USE_LIBRARY_FD_OFFSET without "
"ANDROID_DLEXT_USE_LIBRARY_FD): 0x%" PRIx64, extinfo->flags);
return nullptr;
}
if ((extinfo->flags & ANDROID_DLEXT_USE_NAMESPACE) != 0) {
if (extinfo->library_namespace == nullptr) {
DL_OPEN_ERR("ANDROID_DLEXT_USE_NAMESPACE is set but extinfo->library_namespace is null");
return nullptr;
}
ns = extinfo->library_namespace;
}
}
// Workaround for dlopen(/system/lib/<soname>) when .so is in /apex. http://b/121248172
// The workaround works only when targetSdkVersion < Q.
std::string name_to_apex;
if (translateSystemPathToApexPath(name, &name_to_apex)) {
const char* new_name = name_to_apex.c_str();
LD_LOG(kLogDlopen, "dlopen considering translation from %s to APEX path %s",
name,
new_name);
// Some APEXs could be optionally disabled. Only translate the path
// when the old file is absent and the new file exists.
// TODO(b/124218500): Re-enable it once app compat issue is resolved
/*
if (file_exists(name)) {
LD_LOG(kLogDlopen, "dlopen %s exists, not translating", name);
} else
*/
if (!file_exists(new_name)) {
LD_LOG(kLogDlopen, "dlopen %s does not exist, not translating",
new_name);
} else {
LD_LOG(kLogDlopen, "dlopen translation accepted: using %s", new_name);
name = new_name;
}
}
// End Workaround for dlopen(/system/lib/<soname>) when .so is in /apex.
std::string translated_name_holder;
assert(!g_is_hwasan || !g_is_asan);
const char* translated_name = name;
if (g_is_asan && translated_name != nullptr && translated_name[0] == '/') {
char original_path[PATH_MAX];
if (realpath(name, original_path) != nullptr) {
translated_name_holder = std::string(kAsanLibDirPrefix) + original_path;
if (file_exists(translated_name_holder.c_str())) {
soinfo* si = nullptr;
if (find_loaded_library_by_realpath(ns, original_path, true, &si)) {
PRINT("linker_asan dlopen NOT translating \"%s\" -> \"%s\": library already loaded", name,
translated_name_holder.c_str());
} else {
PRINT("linker_asan dlopen translating \"%s\" -> \"%s\"", name, translated_name);
translated_name = translated_name_holder.c_str();
}
}
}
} else if (g_is_hwasan && translated_name != nullptr && translated_name[0] == '/') {
char original_path[PATH_MAX];
if (realpath(name, original_path) != nullptr) {
// Keep this the same as CreateHwasanPath in system/linkerconfig/modules/namespace.cc.
std::string path(original_path);
auto slash = path.rfind('/');
if (slash != std::string::npos || slash != path.size() - 1) {
translated_name_holder = path.substr(0, slash) + "/hwasan" + path.substr(slash);
}
if (!translated_name_holder.empty() && file_exists(translated_name_holder.c_str())) {
soinfo* si = nullptr;
if (find_loaded_library_by_realpath(ns, original_path, true, &si)) {
PRINT("linker_hwasan dlopen NOT translating \"%s\" -> \"%s\": library already loaded", name,
translated_name_holder.c_str());
} else {
PRINT("linker_hwasan dlopen translating \"%s\" -> \"%s\"", name, translated_name);
translated_name = translated_name_holder.c_str();
}
}
}
}
ProtectedDataGuard guard;
soinfo* si = find_library(ns, translated_name, flags, extinfo, caller);
loading_trace.End();
[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!
最后于 2025-11-29 20:26
被bananaships编辑
,原因: