frida绕过检测学习
arch3rn4r

检测点

检测Frida的机制一般在Native层实现,通常会创建几个线程轮询检测。

查看检测的so

先检查检测部分的代码在哪里

1
2
3
4
5
6
7
8
9
10
11
Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"),  
{
onEnter: function (args) {
var pathptr = args[0];
if (pathptr !== undefined && pathptr != null) {
var path = ptr(pathptr).readCString();
console.log("load " + path);
}
}
}
);

然后运行,查看在哪里停止加载
现在得到了它检测的so文件
libmsaoaidsec.so

脚本执行方法

这里尝试使用了两种方法来执行frida脚本

  • Attach 模式:适合调试已经运行的应用,但可能错过应用启动阶段的重要代码。
  • Spawn 模式:通过启动新进程并提前注入脚本,确保捕获从启动开始的所有行为,适合需要早期 Hook 的情况。
    Attach
    frida -U "进程名“ -l 脚本文件 失败了
    image

Spawn
frida -U -f 包名 -l 脚本名称 成功了

image

so的加载流程

dlopen用来打开一个动态链接库,将其装入内存

dlopen内存装载,loadlibrary加载调用
在高版本Android,dlopen改成android_dlopen_ext

linker会先对so进行加载与链接,然后调用so的.init_proc函数,接着调用.init_array中的函数,最后才是JNI_OnLoad函数。

检测具体加载点
使用frida hook JNI_OnLoad函数,如果调用了该函数就输出一行日志,如果没有日志输出,那么就说明检测点在.init_xxx函数中,注入的时机可以选择dlopen加载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
function hook_dlopen(soName = '') {  
Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"),
{
onEnter: function (args) {
var pathptr = args[0];
if (pathptr !== undefined && pathptr != null) {
var path = ptr(pathptr).readCString();
if (path.indexOf(soName) >= 0) {
this.is_can_hook = true;
}
}
},
onLeave: function (retval) {
if (this.is_can_hook) {
hook_JNI_OnLoad()
}
}
}
);
}

function hook_JNI_OnLoad(){
let module = Process.findModuleByName("libmsaoaidsec.so")
Interceptor.attach(module.base.add(0xC6DC + 1), {
onEnter(args){
console.log("call JNI_OnLoad")
}
})
}

setImmediate(hook_dlopen, "libmsaoaidsec.so")

直接结束了
image
说明关键检测点在.init_proc 或.init_array函数里

但是dlopen函数调用完之后.init_xxxx函数已经执行完了,这个时候不容易使用frida进行hook
hook linker的call_function并不容易,这里面涉及到linker的自举。所以这里有一个新的思路:在.init_proc函数中找一个调用了外部函数的位置,时机越早越好

pthread_create

pthread_create 是 POSIX 线程库中的函数,用于创建新线程。许多应用程序利用它来实现反调试或反 Frida 检测,例如通过线程检查进程内存或状态,检测 Frida 的存在。
应用程序的反 Frida 保护通常会通过 pthread_create 创建守护线程,周期性地扫描内存(如 /proc/self/maps)或检查特定字符串(如“frida”),以检测 Frida 的注入。

那么先查找
没有直接出现pthread_createimage
尝试hook

1
2
3
4
5
6
7
8
9
10
11
12
13
var interceptor = Interceptor.attach(Module.findExportByName(null, "pthread_create"),  
{
onEnter: function (args) {
var module = Process.findModuleByAddress(ptr(this.returnAddress))
if (module != null) {
console.log("[pthread_create] called from", module.name)
}
else {
console.log("[pthread_create] called from", ptr(this.returnAddress))
}
},
}
)

检测到了libmsaoaidsec.so对pthread_create的使用
image

查看检测的线程——查找偏移

Hook pthread_create 函数,记录调用该函数创建线程的模块信息(模块名、线程函数偏移量、参数)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function check_pthread_create(name = null) {  
var pthread_create_addr = Module.findExportByName(null, 'pthread_create');

var pthread_create = new NativeFunction(pthread_create_addr, "int", ["pointer", "pointer", "pointer", "pointer"]);
Interceptor.replace(pthread_create_addr, new NativeCallback(function (parg0, parg1, parg2, parg3) {
var module = Process.findModuleByAddress(parg2)
var so_base = module.base;
var off = "0x" + parg2.sub(so_base).toString(16)
var so_name = module.name;
console.log(so_name, off, parg3)

return pthread_create(parg0, parg1, parg2, parg3);


}, "int", ["pointer", "pointer", "pointer", "pointer"]))
}
setImmediate(check_pthread_create)

能看到libmsaoaidsec.so具体检测的地方
在这里终止!
image
可以得到它加载的线程和偏移
接下来的思路就是nop掉它进行检测的线程

尝试绕过

静态分析so——确定具体hook点

解包apk来获取libmsaoaidsec.so

1
java -jar apktool.jar d malware.apk -o output_folder

从这个函数开始看 .init_proc
image
在这里选择一个尽量早执行并且使用外部函数的函数
得到sub_123f0函数,可以看到它执行了一个sdk
image
接下来就关注_system_property_get

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
function locate_init() {  
let r = null
Interceptor.attach(Module.findExportByName(null, "__system_property_get"),
{
onEnter: function (args) {
var name = args[0];
if (name !== undefined && name != null) {
name = ptr(name).readCString();
console.log(name)
if (name.indexOf("ro.build.version.sdk") >= 0) {
console.log(Process.findModuleByName("libmsaoaidsec.so").base)
}
}
}
}
);
}

Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"),
{
onEnter: function (args) {
var pathptr = args[0];
if (pathptr !== undefined && pathptr != null) {
var path = ptr(pathptr).readCString();
if(path.search("libmsaoaidsec.so") != -1){
this.hook = true
locate_init()
}
}
}
}
);

控制台的输出
得到了基址0x71ea845000,接着去尝试nop掉关键部分就好了
image

平坦流去混淆

image
代码结构中有大量的while和if else
分析完这个后可以找到加载早的函数有哪些

在获取了一个非常早的注入时机之后,就可以定位具体的frida检测点了。网上对frida的检测通常会使用openat、open、strstr、pthread_create、snprintf、sprintf、readlinkat等一系列函数,

关键nop

locate_init函数在检测到ro.build.version.sdk属性被读取时,会找到目标库的基地址,并对三个偏移地址(0x1c544、0x1b8d4、0x26e5c)调用nop_64函数。nop_64函数的作用是将指定地址的指令替换为ret返回指令,从而绕过这些函数的执行。

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
function locate_init() {  
let r = null
Interceptor.attach(Module.findExportByName(null, "__system_property_get"),
{
onEnter: function (args) {
var name = args[0];
if (name !== undefined && name != null) {
name = ptr(name).readCString();
//console.log(name)
if (name.indexOf("ro.build.version.sdk") >= 0) {
var r = Process.findModuleByName("libmsaoaidsec.so")
nop_64(r.base.add("0x1c544"))
nop_64(r.base.add("0x1b8d4"))
nop_64(r.base.add("0x26e5c"))
}
}
}
}
);
}

Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"),
{
onEnter: function (args) {
var pathptr = args[0];
if (pathptr !== undefined && pathptr != null) {
var path = ptr(pathptr).readCString();
if(path.search("libmsaoaidsec.so") != -1){
this.hook = true
locate_init()
}
}
}
}
);

function nop_64(addr) {
Memory.protect(addr, 4 , 'rwx');
var w = new Arm64Writer(addr);
w.putRet();
w.flush();
w.dispose();
}

成功进去
image

尝试绕过pthread_create检测——其他思路

在执行libmsaoaidsec.so时nop掉pthread_create,或者替换它的返回结果
来自https://blog.csdn.net/A_fanyifan/article/details/143864007
直接使用就可以,甚至偏移都是一样的

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
function hook_dlopen(soName = '') {  
Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"),
{
onEnter: function (args) {
var pathptr = args[0];
if (pathptr !== undefined && pathptr != null) {
var path = ptr(pathptr).readCString();
if (path.indexOf(soName) >= 0) {
locate_init()
}
}
}
}
);
}

function locate_init() {
let secmodule = null
Interceptor.attach(Module.findExportByName(null, "__system_property_get"),
{
// _system_property_get("ro.build.version.sdk", v1);
onEnter: function (args) {
secmodule = Process.findModuleByName("libmsaoaidsec.so")
var name = args[0];
if (name !== undefined && name != null) {
name = ptr(name).readCString();
if (name.indexOf("ro.build.version.sdk") >= 0) {
// 这是.init_proc刚开始执行的地方,是一个比较早的时机点
// do something
// hook_pthread_create() bypass()
}
}
}
}
);
}

function hook_pthread_create() {
console.log("libmsaoaidsec.so --- " + Process.findModuleByName("libmsaoaidsec.so").base)
Interceptor.attach(Module.findExportByName("libc.so", "pthread_create"), {
onEnter(args) {
let func_addr = args[2]
console.log("The thread function address is " + func_addr)
}
})
}

function nopFunc(parg2) {
// 修改内存保护,使其可写
Memory.protect(parg2, 4, 'rwx');
// 使用 Arm64Writer 写入 'ret' 指令
var writer = new Arm64Writer(parg2);
writer.putRet();
writer.flush();
writer.dispose();
console.log("nop " + parg2 + " success");
}

function bypass(){
let module = Process.findModuleByName("libmsaoaidsec.so")
nopFunc(module.base.add(0x1c544))
nopFunc(module.base.add(0x1b8d4))
nopFunc(module.base.add(0x26e5c))

}
// pthread\_create libmsaoaidsec.so 0x1c544 0x731552b960\
// pthread\_create libmsaoaidsec.so 0x1b8d4 0x0\
// pthread\_create libmsaoaidsec.so 0x26e5c 0x0

setImmediate(hook_dlopen, "libmsaoaidsec.so")

fake替换

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
function create_fake_pthread_create() {  
const fake_pthread_create = Memory.alloc(4096)
Memory.protect(fake_pthread_create, 4096, "rwx")
Memory.patchCode(fake_pthread_create, 4096, code => {
const cw = new Arm64Writer(code, { pc: ptr(fake_pthread_create) })
cw.putRet()
})
return fake_pthread_create
}

function hook_dlsym() {
var count = 0
console.log("=== HOOKING dlsym ===")
var interceptor = Interceptor.attach(Module.findExportByName(null, "dlsym"),
{
onEnter: function (args) {
const name = ptr(args[1]).readCString()
console.log("[dlsym]", name)
if (name == "pthread_create") {
count++
}
},
onLeave: function(retval) {
if (count == 1) {
retval.replace(fake_pthread_create)
}
else if (count == 2) {
retval.replace(fake_pthread_create)
// 完成2次替换, 停止hook dlsym
interceptor.detach()
}
}
}
)
return Interceptor
}

function hook_dlopen() {
var interceptor = Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"),
{
onEnter: function (args) {
var pathptr = args[0];
if (pathptr !== undefined && pathptr != null) {
var path = ptr(pathptr).readCString();
console.log("[LOAD]", path)
if (path.indexOf("libmsaoaidsec.so") > -1) {
hook_dlsym()
}
}
}
}
)
return interceptor
}

// 创建虚假pthread_create
var fake_pthread_create = create_fake_pthread_create()
var dlopen_interceptor = hook_dlopen()

https://bbs.kanxue.com/thread-281584.htm
image

参考资料

https://bbs.kanxue.com/thread-255674.htm
https://blog.bingyue.top/2025/01/08/an_zhuo_ni_xiang_an_li_001/
https://blog.csdn.net/A_fanyifan/article/details/143864007
https://bbs.kanxue.com/thread-281584.htm

 评论
评论插件加载失败
正在加载评论插件
由 Hexo 驱动 & 主题 Keep
总字数 41.4k 访客数