首页
社区
课程
招聘
Frida过反调试实战
发表于: 1天前 933

Frida过反调试实战

1天前
933

frida初学者最近看了几篇文章,尝试跟着复现一下,并且了解一下frida API的用法(写在注释里了)。有的地方理解还是不到位,欢迎讨论哈

过老版爱加密(hook检测线程)

参考:https://mp.weixin.qq.com/s/34c5JVJzSCEfqlPanV1FtA?from=industrynews&version=4.1.31.6017&platform=win&nwr_flag=1#wechat_redirect

同样是在启动后就Process terminated的情况。(但要注意的是,检测到之后应用没有退出,frida被杀掉了)

第一步hook android_dlopen_ext,查看检测代码在哪个so文件中。

多次运行此脚本,都是停止在libexecmain.so处。推测应该是libexecmain.so创建了检测线程,循环检测到就退出。(不是循环检测的话,那可以正常启动,然后frida附加进程,所以肯定是循环检测的)

接下来hook 线程创建的函数,查看是哪个so文件创建了检测线程,我们hook pthread_create函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function hook_pthread_create(){
    Interceptor.attach(Module.findExportByName(null, "pthread_create"),
        {
            onEnter: function (args) {
                var module = Process.findModuleByAddress(ptr(this.returnAddress))
                //this.returnAddress返回当前函数的地址,也就是谁调用的这个函数
                 
                if (module != null) {
                    console.log("[pthread_create] called from", module.name)
                }
                else {
                    console.log("[pthread_create] called from", ptr(this.returnAddress))
                }
            },
        }
)
}
hook_pthread_create()

可以看到创建线程的其实是libexec.so这个文件,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
try {
  var p_pthread_create = Module.findExportByName("libc.so", "pthread_create");
  var pthread_create = new NativeFunction(p_pthread_create, "int", ["pointer", "pointer", "pointer", "pointer"]);
  Interceptor.replace(p_pthread_create, new NativeCallback(function(ptr0, ptr1, ptr2, ptr3) {
    if (ptr1.isNull() && ptr3.isNull()) {
      console.log("Possible thread creation for checking. Disabling it");
      return -1;
    } else {
      return pthread_create(ptr0, ptr1, ptr2, ptr3);
    }
  }, "int", ["pointer", "pointer", "pointer", "pointer"]));
} catch (error) {
  console.log("Error", error)
}

直接hook掉线程创建不行,因为正常业务也有可能会调用线程创建,就算成功也不完善。我们加一个限定条件(限定libexec.so这个so创建新线程时才hook)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//hook libexec.so的线程创建
//先保存原本的pthread_create函数,再自定义一个,最后替换
function patchPthreadCreate(){
    let pthread_create = Module.findExportByName(null, "pthread_create")
        let org_pthread_create = new NativeFunction(pthread_create, "int", ["pointer", "pointer", "pointer", "pointer"])
        //nativeFunction主动调用so中的函数
        //new NativeFunction(address, returnType, argTypes[, abi])
        //address为原函数地址 returnType为返回类型 argTypes为参数类型
        //(这里的pointer为指针类型,分别对应线程属性、线程标识符、线程函数地址、线程函数)
 
        let my_pthread_create = new NativeCallback(function (a, b, c, d) {
            let m = Process.getModuleByName("libexec.so");
            let base = m.base
            console.log(Process.getModuleByAddress(c).name) // 打印当前调用的库名
            if (Process.getModuleByAddress(c).name == m.name) {
                console.log(m.name + " use pthread_create");
                return 0;
            }
            return org_pthread_create(a, b, c, d)
        }, "int", ["pointer", "pointer", "pointer", "pointer"])
        //new NativeCallback(func, returnType, argTypes[, abi])
        //func为自定义的JavaScript函数 abcd分别对应线程属性、线程标识符、线程函数地址、线程函数
        Interceptor.replace(pthread_create, my_pthread_create)
}

还是不行,我们换一个方法,直接操作pthread_createa中的内存地址,避免被检测。

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 patchPthreadCreateCaller(){
    let pthread_create = Module.findExportByName("libc.so", "pthread_create")
    Interceptor.attach(pthread_create, {
        onEnter: function (args) {
            this.isHook = false
            let m = Process.getModuleByName("libexec.so");
            let base = m.base
            console.log(Process.getModuleByAddress(args[2]).name)
            if (Process.getModuleByAddress(args[2]).name == m.name) {             
                console.log("绝对地址: " + args[2], "偏移量: "+ args[2].sub(base), "返回地址偏移量: "+this.context.lr.sub(m.base))
                // args[2] 线程函数的绝对地址。
                // args[2].sub(base) 线程函数相对于目标模块基地址的偏移。
                // this.context.lr.sub(m.base) 调用者的返回地址相对于目标模块基地址的偏移。lr代表返回值寄存器的地址。
 
                if (args[2] >= base && args[2] < base.add(m.size)) {
                    Memory.patchCode(args[2], 4, code => { //4 代表内存大小
                    //Memory.patchCode(address, size, apply) 安全修改内存地址
                    //address: 要修改的内存地址 size:要修改的内存大小 apply:自定义函数,用于修改内存
                        console.log("Patching thread function at " + args[2]);
 
                        //new Arm64Writer(codeAddress[, { pc: ptr('0x1234') }]) 将代码转为机器码
                        //这里传入的code代表要写入的地址区域 pc代表当前指令地址 是为了告诉 Arm64Writer,生成指令时,这块内存的起始地址就是 args[2]。
                        const cw = new Arm64Writer(code, {pc: args[2]});
                        cw.putLdrRegU64('x0', 0); // 将x0寄存器加载为0,x0寄存器一般存第一个参数或返回值 (修改线程函数的入口代码,使其立即返回一个 0)
                        cw.flush(); // 将修改后的代码写入内存
                      });
                }
            }
        }
    }) 
}

还是不行,推测是

  1. 使用 Memory.patchCode,修改时可能已经触发了检测逻辑。
  2. 对于pthread_create传入的参数地址还有检测,如果不是指定的地址的话就重复创建。
  3. patchCode没生效(例如代码段不可写或时间窗口不够)。

那我们不修改内存地址,直接把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
35
36
37
function hook_pthread() {
 
    var pthread_create_addr = Module.findExportByName('libc.so', 'pthread_create');
    console.log("pthread_create_addr,", pthread_create_addr);
   
    var pthread_create = new NativeFunction(pthread_create_addr, "int", ["pointer", "pointer", "pointer", "pointer"]);
   
    Interceptor.replace(pthread_create_addr, new NativeCallback(function (a,b,c,d) {
        var m = Process.getModuleByName("libexec.so");
        var base = m.base;
        var so_name = Process.getModuleByAddress(c).name;
        var so_path = Process.getModuleByAddress(c).path;
        var offset = c.sub(base);
 
        console.log("so_name", so_name, "offset", offset, "path", so_path, "parg2", c);
 
        var PC = 0;
        if ((so_name.indexOf("libexec.so") > -1) || (so_name.indexOf("xxxx") > -1)) {
        console.log("find thread func offset", so_name, offset);
 
        // 循环检测偏移量,如果命中则不创建线程,只是打印anti bypss
        if ((0x4400c === offset)) {
            console.log("anti bypass");
        } else if (0x44060 === offset) {
            console.log("anti bypass");
        } else {
            PC = pthread_create(a,b,c,d);
            console.log("ordinary sequence", PC)
        }
        } else {
        PC = pthread_create(a,b,c,d);
        // console.log("ordinary sequence", PC)
        }
        return PC;
    }, "int", ["pointer", "pointer", "pointer", "pointer"]))
   
  }

使用前。先把0x4400c === offset 改成 1 === offset,然后把打印出的地址都填进去。

成功了,但是……只有旧版本的加固方案可以,新版本疑似对pthread_create加了额外的检测,还需要去hook so文件(待确认)。

过梆梆

参考:https://bbs.kanxue.com/thread-277034.htm

还是一样的,先查看检测的so文件

定位到这个libnllvm1717052779924.so,同样的思路,hook线程创建函数

啥也没hook到,说明检测的内容还在线程创建之前(?),就是说没有通过创建线程进行检测,而是在文件代码中。

接下看下这个检测so文件有没有调用Jni_onload,为后续ida分析做准备把

打开ida,看下jni_onload的偏移量

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 exportAddress = Module.findExportByName("libnllvm1717052779924.so", "JNI_OnLoad");
    Interceptor.attach(exportAddress, {
        onEnter: function (args) {
            console.log("JNI_OnLoad called : " + exportAddress);
        }
    });
}
  
setImmediate(hook_dlopen, "libnllvm1717052779924.so")

运行下试试

图片描述

调用了jni_onload,那么检测代码很有可能在这里,并且jni_onload不像.init_proc函数一样,在dlopen后已经执行完成,所以我们可以去hookJni_onload

没有打印,说明在Jni_onload调用之前,我们要去看init_xxxx函数,让frida代码在检测代码执行之前调用

需要在init_proc中找到一个可以hook的函数作为注入点,这里我发现了一个sprintf函数

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 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() {
    var foundFirstMatch = false; // 用于标记是否已匹配到第一次
 
    Interceptor.attach(Module.findExportByName(null, "sprintf"),
        {
            onEnter: function (args) {
                if (foundFirstMatch) return; // 如果已经匹配到第一次,直接返回
 
                var secmodule = Process.findModuleByName("libnllvm1717052779924.so");
 
                console.log("args1 = " + args[0]);
                console.log("args2 = " + args[1]);
                console.log("args3 = " + args[2].toInt32());
                var formatString = args[1].readUtf8String();
                console.log("Format string: " + formatString);
                console.log("==============================");
 
                if (formatString.indexOf("/proc/%d/cmdline") >= 0 && args[0].toString() === "0x7fc82303f8") {
                    // 这是.init_proc刚开始执行的地方,是一个比较早的时机点
                    console.log("This is init_proc");
                    foundFirstMatch = true; // 设置标记,防止后续再触发
                            //hook_pthread_create();
 
                }
            }
        }
    );
}
 
function hook_pthread_create(){
    var module = Process.findModuleByName("libnllvm1717052779924.so");
    console.log("libnllvm1717052779924.so --- " + module.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);
            console.log("调用的module是 : " + Process.findModuleByAddress(func_addr).name);
            if(func_addr < module.base.add(module.size) && func_addr > module.base){
                console.log("Offset is : " + func_addr.sub(module.base));
                args[2].replace
                // bypass();
            }
            console.log("-----------------------------------");
 
        }
    })
}

我们只hook第一次的调用,因为其他的都不是init_proc中的调用。

用这个偏移量替换if条件,现在我们就hook到了init_proc,在这里可以执行我们的代码,不会被检测到。

这里再执行下线程创建的枚举

发现了创建线程的函数,我们现在就把它hook掉,可以直接写入机器码,将检测函数nop掉。

还是寄了,给哥们整不会了。

试试老办法

我觉得应该已经过了这部分,只是还有其他检测。


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 1
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//