libmsaoaidsec.so 分析

分析 app 的过程中,需要用 frida hook 的话一般就需要对抗 frida 检测,这里以一个经典的 so 来学习一下 frida 检测的绕过

样本是豆瓣,本篇文章使用的 frida 版本: 17.7.3

hook dlopen

hook 它的 dlopen 看一下 so 的加载流程,定位检测的 so

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
function hk_dlopen(): void {
console.log("[*] Hook dlopen");

const ignoreLibs = new Set<string>([
"libaudioclientimpl.so",
"libmigui.so",
"libkoom-monitor.so",
"libhprof.so"
]);

const jniCache: Record<string, NativePointer | null> = {};

function shouldIgnore(libName: string): boolean {
if (!libName) return false;
for (const k of ignoreLibs) {
if (libName.indexOf(k) !== -1) return true;
}
return false;
}

function installHook(symbol: "dlopen" | "android_dlopen_ext"): void {
let target: NativePointer;
try {
target = Module.getGlobalExportByName(symbol);
} catch (_e) {
console.log(`[-] ${symbol} not found`);
return;
}

Interceptor.attach(target, {
onEnter(args: NativePointer[]) {
const libName = args[0].isNull() ? "" : args[0].readUtf8String() || "";
const flags = args[1].toInt32();

(this as any).libName = libName;
(this as any).ignore = shouldIgnore(libName);

if (!(this as any).ignore) {
console.log(`[*] ${symbol} called with: ${libName} (flags: 0x${flags.toString(16)})`);
}
},
onLeave(retval: NativePointer) {
if ((this as any).ignore) return;

const libName: string = (this as any).libName || "";
if (retval.isNull()) {
console.log(`[-] Failed to load: ${libName}`);
return;
}

console.log(`[+] Successfully loaded: ${libName}`);

}
});

console.log(`[+] ${symbol} hook installed`);
}

installHook("dlopen");
installHook("android_dlopen_ext");
}

hk_dlopen();
1
2
frida-compile hook_dlopen.ts -o hook_dlopen.js
frida -U -f com.douban.frodo -l.\hook_dlopen.js

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Spawning `com.douban.frodo`...
[*] Hook dlopen
[+] dlopen hook installed
[+] android_dlopen_ext hook installed
Spawned `com.douban.frodo`. Resuming main thread!
[*] android_dlopen_ext called with: libframework-connectivity-tiramisu-jni.so (flags: 0x2)
[Pixel 6::com.douban.frodo ]-> [+] Successfully loaded: libframework-connectivity-tiramisu-jni.so
[*] android_dlopen_ext called with: /data/app/~~Cf51eG3ZHEZ1OKV2HLZsZg==/com.douban.frodo-ulsR97QYcr8aOckQpu-AYQ==/oat/arm64/base.odex (flags: 0x2)
[+] Successfully loaded: /data/app/~~Cf51eG3ZHEZ1OKV2HLZsZg==/com.douban.frodo-ulsR97QYcr8aOckQpu-AYQ==/oat/arm64/base.odex
[*] android_dlopen_ext called with: /data/app/~~Cf51eG3ZHEZ1OKV2HLZsZg==/com.douban.frodo-ulsR97QYcr8aOckQpu-AYQ==/lib/arm64/libmsaoaidsec.so (flags: 0x2)
[*] dlopen called with: libc.so (flags: 0x2)
[+] Successfully loaded: libc.so
[*] dlopen called with: libc.so (flags: 0x2)
[+] Successfully loaded: libc.so
Process terminated

hook pthread_create

看到 libmsaoaidsec.so,hook pthread_create 看看它的线程创建流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function hook_pthread_create(): void {
console.log("[*] Hooking pthread_create...");
const libc = Process.getModuleByName("libc.so");
const pthread_create = libc.getExportByName("pthread_create");
const libcAddr = libc.base;
console.log("[*] libc base address: " + libcAddr);

Interceptor.attach(pthread_create, {
onEnter: function (args) {
let func_addr = args[2];
let secmod = Process.findModuleByName("libmsaoaidsec.so");

if (!secmod) {
// console.log("[-] module not loaded yet");
return;
}

let secmodAddr = secmod.base;
let secmodSize = secmod.size;
let secmodEnd = secmodAddr.add(secmodSize);

// 只有当 func_addr 落在 libmsaoaidsec.so 的起始地址和结束地址之间时,才打印
if (func_addr.compare(secmodAddr) >= 0 && func_addr.compare(secmodEnd) < 0) {
console.log("-----------------------------------------");
console.log("[*] Target pthread_create intercepted!");
console.log("[*] libmsaoaidsec.so base: " + secmodAddr);
console.log("[*] func_addr: " + func_addr);
console.log("[*] func_offset: 0x" + func_addr.sub(secmodAddr).toString(16));
}
}
});
}

setImmediate(hook_pthread_create);

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Spawning `com.douban.frodo`...
[*] Hooking pthread_create...
[*] libc base address: 0x7a8a6cc000
Spawned `com.douban.frodo`. Resuming main thread!
[Pixel 6::com.douban.frodo ]-> -----------------------------------------
[*] Target pthread_create intercepted!
[*] libmsaoaidsec.so base: 0x76b7842000
[*] func_addr: 0x76b785e544
[*] func_offset: 0x1c544
-----------------------------------------
[*] Target pthread_create intercepted!
[*] libmsaoaidsec.so base: 0x76b7842000
[*] func_addr: 0x76b785d8d4
[*] func_offset: 0x1b8d4
-----------------------------------------
[*] Target pthread_create intercepted!
[*] libmsaoaidsec.so base: 0x76b7842000
[*] func_addr: 0x76b7868e5c
[*] func_offset: 0x26e5c
Process terminated
[Pixel 6::com.douban.frodo ]->

可以看到它创建了三个线程,分别位于 libmsaoaidsec.so0x1c544, 0x1b8d4, 0x26e5c 这三个偏移地址上

先尝试直接让它们的线程函数返回 0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function anti():void{
const libc = Process.getModuleByName("libc.so");
const pthread_create = libc.getExportByName("pthread_create");
if(!pthread_create){
console.log("[-] pthread_create not found in libc.so");
return;
}
let pthread_create_native = new NativeFunction(pthread_create, 'int', ['pointer', 'pointer', 'pointer', 'pointer']);
Interceptor.replace(pthread_create, new NativeCallback(function (thread: NativePointer, attr: NativePointer, start_routine: NativePointer, arg: NativePointer) {
let module = Process.findModuleByAddress(start_routine);
let moduleName = module ? module.name : "unknown";
let offset = module ? start_routine.sub(module.base).toString(16) : "unknown offset";
if(moduleName.indexOf("libmsaoaidsec.so") !== -1){
console.log("kill thread at offset 0x" + offset + " in " + moduleName);
return 0;
}
return pthread_create_native(thread, attr, start_routine, arg);
}, 'int', ['pointer', 'pointer', 'pointer', 'pointer']));
}

setImmediate(anti);

但是这么做并不能绕过

thread at offset 0x1c544 in libmsaoaidsec.so
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
kill thread at offset 0x1b8d4 in libmsaoaidsec.so
kill thread at offset 0x26e5c in libmsaoaidsec.so
Process crashed: Bad access due to invalid address

***
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'google/oriole/oriole:16/BP2A.250605.031.A2/13578606:user/release-keys'
Revision: 'MP1.0'
ABI: 'arm64'
Timestamp: 2026-03-03 08:25:26.174351187+0800
Process uptime: 2s
Cmdline: com.douban.frodo
pid: 13341, tid: 13341, name: om.douban.frodo >>> com.douban.frodo <<<
uid: 10283
tagged_addr_ctrl: 0000000000000001 (PR_TAGGED_ADDR_ENABLE)
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x000000000000076c
Cause: null pointer dereference
x0 00000076b8d61730 x1 0000000000010000 x2 0000000000000001 x3 0000000000000001
x4 0000000000000100 x5 0000000000000000 x6 000000000000002c x7 00000076b8d4675d
x8 0000000000000024 x9 b181773333c6d7d7 x10 000000000000c000 x11 000000003df61213
x12 0000000000000000 x13 0000000000000005 x14 0000000000000001 x15 0000000000000006
x16 00000076b8d5da68 x17 00000076b8d3d950 x18 00000076b8d450ac x19 00000076b8d5e01c
x20 00000000000005e4 x21 0000000000000000 x22 0000007aa4fba880 x23 00000000bb1e2113
x24 000000000e3ba312 x25 00000000de366c1c x26 0000000019437faa x27 000000004f03bfb2
x28 00000000e7e21422 x29 0000007fc4cbe700
lr 00000076b8d36d90 sp 0000007fc4cbd4c0 pc 00000076b8d36d10 pst 0000000020001000
41 total frames
backtrace:
#00 pc 0000000000020d10 /data/app/~~Cf51eG3ZHEZ1OKV2HLZsZg==/com.douban.frodo-ulsR97QYcr8aOckQpu-AYQ==/lib/arm64/libmsaoaidsec.so
#01 pc 0000000000011f64 /data/app/~~Cf51eG3ZHEZ1OKV2HLZsZg==/com.douban.frodo-ulsR97QYcr8aOckQpu-AYQ==/lib/arm64/libmsaoaidsec.so
#02 pc 00000000000095f8 /data/app/~~Cf51eG3ZHEZ1OKV2HLZsZg==/com.douban.frodo-ulsR97QYcr8aOckQpu-AYQ==/lib/arm64/libmsaoaidsec.so
#03 pc 0000000000014824 /data/app/~~Cf51eG3ZHEZ1OKV2HLZsZg==/com.douban.frodo-ulsR97QYcr8aOckQpu-AYQ==/lib/arm64/libmsaoaidsec.so
#04 pc 00000000000529a0 /apex/com.android.runtime/bin/linker64 (__dl__ZN6soinfo17call_constructorsEv+640) (BuildId: 87182a913672e7d75e7da85dcea916a0)
#05 pc 0000000000051b50 /apex/com.android.runtime/bin/linker64 (__dl__Z9do_dlopenPKciPK17android_dlextinfoPKv+1360) (BuildId: 87182a913672e7d75e7da85dcea916a0)
#06 pc 0000000000051558 /apex/com.android.runtime/bin/linker64 (__dl__ZL10dlopen_extPKciPK17android_dlextinfoPKv.__uniq.234527301065430621646263515731762262959+72) (BuildId: 87182a913672e7d75e7da85dcea916a0)
#07 pc 0000000000001110 /apex/com.android.runtime/lib64/bionic/libdl.so (android_dlopen_ext+16) (BuildId: 43d87b46ed1b46fe9dcfd125aff58cd8)
#08 pc 000000000001dae0 /apex/com.android.art/lib64/libnativeloader.so (android::NativeLoaderNamespace::Load(char const*) const+140) (BuildId: b6e77688d559c566e6ab929caf51bee3)
#09 pc 000000000000c880 /apex/com.android.art/lib64/libnativeloader.so (OpenNativeLibrary+916) (BuildId: b6e77688d559c566e6ab929caf51bee3)
#10 pc 0000000000580734 /apex/com.android.art/lib64/libart.so (art::JavaVMExt::LoadNativeLibrary(_JNIEnv*, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&, _jobject*, _jclass*, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>>*)+736) (BuildId: b229f9d1b6196afaae086f29f029f907)
#11 pc 000000000000575c /apex/com.android.art/lib64/libopenjdkjvm.so (JVM_NativeLoad+368) (BuildId: 59b6fc99630ea9ddc24ff84c08f7c890)
#12 pc 0000000000d49d44 /data/misc/apexdata/com.android.art/dalvik-cache/arm64/boot.oat (art_jni_trampoline+148)
#13 pc 0000000000365270 /data/misc/apexdata/com.android.art/dalvik-cache/arm64/boot.oat (java.lang.Runtime.loadLibrary0+320)
#14 pc 000000000036639c /data/misc/apexdata/com.android.art/dalvik-cache/arm64/boot.oat (java.lang.Runtime.loadLibrary0+428)
#15 pc 0000000000376984 /data/misc/apexdata/com.android.art/dalvik-cache/arm64/boot.oat (java.lang.System.loadLibrary+84)
#16 pc 0000000000689588 /apex/com.android.art/lib64/libart.so (nterp_helper+152) (BuildId: b229f9d1b6196afaae086f29f029f907)
#17 pc 00000000043762bc /data/app/~~Cf51eG3ZHEZ1OKV2HLZsZg==/com.douban.frodo-ulsR97QYcr8aOckQpu-AYQ==/oat/arm64/base.vdex (com.douban.frodo.FrodoApplication.p+16)
#18 pc 000000000068a444 /apex/com.android.art/lib64/libart.so (nterp_helper+3924) (BuildId: b229f9d1b6196afaae086f29f029f907)
#19 pc 000000000437657e /data/app/~~Cf51eG3ZHEZ1OKV2HLZsZg==/com.douban.frodo-ulsR97QYcr8aOckQpu-AYQ==/oat/arm64/base.vdex (com.douban.frodo.FrodoApplication.onCreate+250)
#20 pc 00000000006a58a0 /data/misc/apexdata/com.android.art/dalvik-cache/arm64/boot.oat (android.app.Instrumentation.callApplicationOnCreate+48)
#21 pc 000000000061f634 /data/misc/apexdata/com.android.art/dalvik-cache/arm64/boot.oat (android.app.ActivityThread.handleBindApplication+5044)
#22 pc 0000000000619be0 /data/misc/apexdata/com.android.art/dalvik-cache/arm64/boot.oat (android.app.ActivityThread.-$$Nest$mhandleBindApplication+48)
#23 pc 00000000006186c0 /data/misc/apexdata/com.android.art/dalvik-cache/arm64/boot.oat (android.app.ActivityThread$H.handleMessage+8240)
#24 pc 00000000008e1068 /data/misc/apexdata/com.android.art/dalvik-cache/arm64/boot.oat (android.os.Handler.dispatchMessage+152)
#25 pc 000000000091dccc /data/misc/apexdata/com.android.art/dalvik-cache/arm64/boot.oat (android.os.Looper.loopOnce+1260)
#26 pc 000000000091d764 /data/misc/apexdata/com.android.art/dalvik-cache/arm64/boot.oat (android.os.Looper.loop+244)
#27 pc 0000000000628b04 /data/misc/apexdata/com.android.art/dalvik-cache/arm64/boot.oat (android.app.ActivityThread.main+1492)
#28 pc 0000000000328460 /apex/com.android.art/lib64/libart.so (art_quick_invoke_static_stub+640) (BuildId: b229f9d1b6196afaae086f29f029f907)
#29 pc 00000000003224a0 /apex/com.android.art/lib64/libart.so (_jobject* art::InvokeMethod<(art::PointerSize)8>(art::ScopedObjectAccessAlreadyRunnable const&, _jobject*, _jobject*, _jobject*, unsigned long)+544) (BuildId: b229f9d1b6196afaae086f29f029f907)
#30 pc 00000000005c5af8 /apex/com.android.art/lib64/libart.so (art::Method_invoke(_JNIEnv*, _jobject*, _jobject*, _jobjectArray*) (.__uniq.165753521025965369065708152063621506277)+32) (BuildId: b229f9d1b6196afaae086f29f029f907)
#31 pc 0000000000d498d4 /data/misc/apexdata/com.android.art/dalvik-cache/arm64/boot.oat (art_jni_trampoline+116)
#32 pc 0000000000c10ba4 /data/misc/apexdata/com.android.art/dalvik-cache/arm64/boot.oat (com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run+116)
#33 pc 0000000000c19014 /data/misc/apexdata/com.android.art/dalvik-cache/arm64/boot.oat (com.android.internal.os.ZygoteInit.main+3204)
#34 pc 0000000000328460 /apex/com.android.art/lib64/libart.so (art_quick_invoke_static_stub+640) (BuildId: b229f9d1b6196afaae086f29f029f907)
#35 pc 0000000000327098 /apex/com.android.art/lib64/libart.so (art::JValue art::InvokeWithVarArgs<_jmethodID*>(art::ScopedObjectAccessAlreadyRunnable const&, _jobject*, _jmethodID*, std::__va_list)+800) (BuildId: b229f9d1b6196afaae086f29f029f907)
#36 pc 000000000064a850 /apex/com.android.art/lib64/libart.so (art::JNI<true>::CallStaticVoidMethodV(_JNIEnv*, _jclass*, _jmethodID*, std::__va_list)+156) (BuildId: b229f9d1b6196afaae086f29f029f907)
#37 pc 00000000000fe568 /system/lib64/libandroid_runtime.so (_JNIEnv::CallStaticVoidMethod(_jclass*, _jmethodID*, ...)+104) (BuildId: 28384e4cde81e0fe31efa0d6350b2a2c)
#38 pc 0000000000129a6c /system/lib64/libandroid_runtime.so (android::AndroidRuntime::start(char const*, android::Vector<android::String8> const&, bool)+908) (BuildId: 28384e4cde81e0fe31efa0d6350b2a2c)
#39 pc 000000000000259c /system/bin/app_process64 (main+1212) (BuildId: df6fb853fe7081ef957fc51f17d1a5eb)
#40 pc 0000000000069818 /apex/com.android.runtime/lib64/bionic/libc.so (__libc_init+120) (BuildId: 56d1c072e220860e239a4a1824a78f97)
***

进一步分析

主要可能有两个问题,一是 pthread_create 的第一个参数 pthread_t *thread 没有被赋值,正常的 pthread_create 执行成功后会将新线程的句柄写入这个指针;二是 libmsaoaidsec.so 在初始化时,会扫描内存检查 libc.so 中的 pthread_create 函数头是否被修改,而 Interceptor.replace 会在函数头写入一条跳转指令即 Trampoline,这会被检测到并认为是被篡改了,所以直接崩溃了

所以现在只能换种方式,推测检测逻辑在 libmsaoaidsec.so.init 函数里

so 的加载

so 的加载流程大致是这样的:

graph TD
    %% 全局样式
    classDef default fill:#f9f9f9,stroke:#333,stroke-width:1px;
    classDef highlight fill:#e1f5fe,stroke:#01579b,stroke-width:2px;
    classDef core fill:#fff3e0,stroke:#e65100,stroke-width:2px;
    
    %% 第一阶段:Java 层逻辑
    subgraph Phase1 [Phase 1: Java Framework]
        A1[System.loadLibrary libname] --> A2[Runtime.loadLibrary0]
        A2 --> A3{ClassLoader 类型?}
        A3 -- PathClassLoader --> A4[DexPathList.findLibrary]
        A4 --> A5["搜索 /data/app/.../lib/arm64/"]
        A5 --> A6[获取绝对路径: /path/to/libtest.so]
    end

    %% 第二阶段:ART 运行时与命名空间
    subgraph Phase2 [Phase 2: ART Runtime]
        A6 --> B1["Runtime.nativeLoad(path, loader)"]
        B1 --> B2["JavaVMExt::LoadNativeLibrary (art/runtime)"]
        B2 --> B3["NativeLoader (libnativeloader.so)"]
        B3 --> B4["Find or Create LinkerNamespace"]
        B4 -- "关联 ClassLoader" --> B5["生成 android_dlextinfo (含 namespace)"]
    end

    %% 第三阶段:Bionic Linker 内核逻辑 (核心)
    subgraph Phase3 [Phase 3: Bionic Linker]
        B5 --> C1["android_dlopen_ext (linker/linker.cpp)"]
        C1 --> C2["find_library (查找 soinfo)"]
        C2 --> C3["ElfReader::Load (解析 ELF Header)"]
        
        %% 内存布局
        C3 --> C4["mmap PT_LOAD Segments (预留地址空间)"]
        C4 --> C5["mmap .text / .data (物理映射)"]
        
        %% 依赖处理
        C5 --> C6["BFS: 广度优先解析 DT_NEEDED (依赖库)"]
        C6 -- "递归加载" --> C1
        
        %% 符号与重定位
        C6 --> C7["Symbol Lookup (遍历符号表 .dynsym)"]
        C7 --> C8["Relocation (修正 GOT/PLT 地址)"]
        C8 --> C9["protect_relro (内存只读保护)"]
    end

    %% 第四阶段:初始化与 JNI 握手
    subgraph Phase4 [Phase 4: Initialization]
        C9 --> D1["call_constructors (linker.cpp)"]
        D1 --> D2["DT_INIT (_init 函数)"]
        D2 --> D3["DT_INIT_ARRAY (C++ 全局构造函数)"]
        
        D3 -- "返回 handle" --> D4["SharedLibrary::FindSymbol(JNI_OnLoad)"]
        D4 --> D5{存在 JNI_OnLoad?}
        D5 -- Yes --> D6["执行 JNI_OnLoad (动态注册)"]
        D5 -- No --> D7["等待 Java 层调用 (静态匹配)"]
    end

    %% 关键数据结构标注
    class A6,B5,C2,D4 highlight;
    class C3,C4,C5 core;

init 函数

因为检测逻辑很有可能在初始化函数阶段,所以选择在 android_dlopen_extonEnter 阶段 hook .init 函数

由于 so 没有加载完全,无法 hook 导出函数或者通过基地址+偏移的方式 hook,所以就通过在 .init 函数里找一个导入函数进行 hook

反编译 libmsaoaidsec.so,找到了它的 .init_proc 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
void init_proc()
{
const char *v0; // x23
__int64 v1; // x0
__pid_t v2; // w0
unsigned __int64 StatusReg; // [xsp+0h] [xbp-870h] BYREF
unsigned __int64 *v4; // [xsp+8h] [xbp-868h]
int v6; // [xsp+18h] [xbp-858h]
int v7; // [xsp+1Ch] [xbp-854h]
unsigned __int64 *v8; // [xsp+20h] [xbp-850h]
char *v10; // [xsp+30h] [xbp-840h]
FILE *v11; // [xsp+38h] [xbp-838h]
char v12[2000]; // [xsp+40h] [xbp-830h] BYREF
__int64 v13; // [xsp+810h] [xbp-60h]

StatusReg = _ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2));
v13 = *(_QWORD *)(StatusReg + 40);
v4 = &StatusReg - 250;
*off_47FB8 = sub_123F0();
sub_12550();
sub_12440();
if ( *off_47FB8 > 23 )
*off_47ED8 = 1;
if ( (sub_25A48() & 1) == 0 )
{
v10 = v12;
memset(v10, 0, 0x7D0uLL);
v2 = getpid();
_sprintf_chk(v12, 0LL, 2000LL, "/proc/%d/cmdline", v2);
v11 = fopen(v12, "r");
if ( v11 )
{
v8 = v4;
memset(v4, 0, 0x7D0uLL);
v0 = (const char *)v4;
fscanf(v11, "%s", v4);
fclose(v11);
if ( !strchr(v0, 58) )
sub_1BEC4();
}
v1 = sub_13728();
sub_23AD4(v1);
v6 = sub_C830();
if ( v6 != 1 || (v7 = sub_95C8()) != 0 )
sub_9150();
}
}

__int64 sub_123F0()
{
_QWORD v1[2]; // [xsp+0h] [xbp-20h] BYREF

v1[1] = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
v1[0] = 0LL;
_system_property_get("ro.build.version.sdk", v1);
return atoi((const char *)v1);
}

char *sub_12550()
{
char *result; // x0
char haystack[16]; // [xsp+8h] [xbp-78h] BYREF
__int128 v2; // [xsp+18h] [xbp-68h]
__int128 v3; // [xsp+28h] [xbp-58h]
__int128 v4; // [xsp+38h] [xbp-48h]
__int128 v5; // [xsp+48h] [xbp-38h]
__int64 v6; // [xsp+58h] [xbp-28h]
int v7; // [xsp+60h] [xbp-20h]
__int64 v8; // [xsp+68h] [xbp-18h]

v8 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
v7 = 0;
v6 = 0LL;
v5 = 0u;
v4 = 0u;
v3 = 0u;
v2 = 0u;
*(_OWORD *)haystack = 0u;
if ( !(unsigned int)_system_property_get("persist.sys.dalvik.vm.lib", haystack) || *off_47FB8 >= 21 )
_system_property_get("persist.sys.dalvik.vm.lib.2", haystack);
result = strstr(haystack, "art");
if ( result )
*off_47FB0[0] = 1;
return result;
}

char *sub_12440()
{
char *result; // x0
int *v1; // x20
char s[16]; // [xsp+8h] [xbp-88h] BYREF
__int128 v3; // [xsp+18h] [xbp-78h]
__int128 v4; // [xsp+28h] [xbp-68h]
__int128 v5; // [xsp+38h] [xbp-58h]
__int128 v6; // [xsp+48h] [xbp-48h]
__int64 v7; // [xsp+58h] [xbp-38h]
int v8; // [xsp+60h] [xbp-30h]
__int64 v9; // [xsp+68h] [xbp-28h]

v9 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
v8 = 0;
v7 = 0LL;
v6 = 0u;
v5 = 0u;
v4 = 0u;
v3 = 0u;
*(_OWORD *)s = 0u;
_system_property_get("ro.build.version.release_or_codename", s);
result = strchr(s, 83);
if ( result || (result = strstr(s, "12")) != 0LL )
{
if ( *off_47FB8 <= 32 )
*off_47FB8 = 31;
}
else
{
v1 = off_47FB8;
if ( *off_47FB8 == 32 )
{
v8 = 0;
v7 = 0LL;
v6 = 0u;
v5 = 0u;
v4 = 0u;
v3 = 0u;
*(_OWORD *)s = 0u;
_system_property_get("ro.build.version.security_patch", s);
result = strstr(s, "2022-02");
if ( result )
*v1 = 31;
}
}
return result;
}

注意到 __system_property_get 这个导入函数,可以在它被调用并且传入特定参数(比如 “ro.build.version.sdk”)的时候进行 hook,此时就能确定是在 .init_proc 阶段了;然后进行 frida 注入,replace 掉前面三个偏移处的函数

绕过检测代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
function nop_64(addr:NativePointer):void{
try{
Memory.protect(addr, 4 , 'rwx');
let ins = Instruction.parse(addr);
console.log(`[*] Original instruction at ${addr}: ${ins.toString()}`);
let w = new Arm64Writer(addr);
w.putRet();
w.flush();
w.dispose();
} catch(e) {
console.log("[-]Error in nop_64: " + e);
}
}

function hook_init(): void {
const libc: Module | null = Process.getModuleByName("libc.so");
const target: NativePointer | null = libc.findExportByName("__system_property_get");

if (target === null) {
console.log("[-] __system_property_get not found");
return;
}

Interceptor.attach(target, {
onEnter: function(this: InvocationContext, args: InvocationArguments): void {

let arg0: string | null = args[0].readUtf8String();
if (arg0 && arg0.indexOf("ro.build.version.sdk") !== -1) {
console.log("[+] system_property_get called");

let mod: Module | null = Process.findModuleByName("libmsaoaidsec.so");
if (mod) {
console.log("[+] so base: " + mod.base);

let offsets: number[] =[0x1c544, 0x1b8d4, 0x26e5c];
offsets.forEach((offset: number) => {
let func_addr: NativePointer = mod.base.add(offset);
nop_64(func_addr);
console.log("[+] NOP Offset 0x" + offset.toString(16));
})

}
}
}
});
}

function hook_dlopen(): void {
const dlopen_ext = Module.getGlobalExportByName("android_dlopen_ext");
Interceptor.attach(dlopen_ext,{
onEnter:function(args){
let path = args[0].readUtf8String();
if(path && path.indexOf("libmsaoaidsec.so") !== -1){
console.log("[+] android_dlopen_ext called with: " + path);
hook_init();
}

}
})
}

setImmediate(hook_dlopen);

ps

要是这样绕不过的话可能还要考虑更多其他的检测

比如字符串的检测,像 fridagum-jsgmainREJECT等等,这些字符通常来自 Frida agent、本地 Gum 运行时、GLib 事件循环线程名称或相关错误信息,检测可能会扫 proc/self/mapsproc/self/status、线程名、内存、符号表等来进行字符串匹配,对抗的话就需要 hook libc 中的 strstrstrcmp 等字符串比较函数,在这些函数执行时拦截参数,替换掉关键词或者篡改匹配结果,使得包含上述特征字符串的检测始终返回未匹配

安全库可能会扫描 proc/self/maps,查看是否有可疑的内存映射,比如 frida 注入产生的 so、匿名可执行映射或者带有特征路径的映射项,检测流程一般是打开 /proc/self/maps,逐行读取内容,解析权限、路径、匿名段信息,再根据规则判断是否可疑,对抗的话一般是两条思路:一是让检测器拿不到真实的 maps 视图,另一种则是降低 maps 中的可疑特征

另外,如果目标程序没有通过 libc 的 openfread 等封装函数,而是自实现了相关函数,直接使用 syscall(svc) 进行系统调用,那么在 libc 层的 hook 就不能生效了,这种情况下,可以用 stackplz 或者其他工具在内核态捕获 openat 等并结合调用栈信息定位到用户态发起调用的位置


libmsaoaidsec.so 分析
http://example.com/2026/03/21/douban/
作者
Eleven
发布于
2026年3月21日
许可协议