Just do it.

某单词软件实战

Posted on By qfzwy

过frida检测

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

开启frida崩溃,尝试hook android_dlopen_ext函数,观察加载到哪个so的时候,触发反调试进程终止。

function hook_android_dlopen_ext() {
    Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
        onEnter: function (args) {
            var soPath = Memory.readCString(ptr(args[0]));
            console.log("android_dlopen_ext: " + soPath);
        },
        onLeave: function (retval) {
            if (retval.toInt32() != 0) {
                console.log("android_dlopen_ext: loaded");
            }
        }
    });
}

可以看到加载libmsaoaidsec.so后崩溃,所以判断检测点就在这个so文件中

image-20250303170854871

有两种方式,一种是正面对抗,一种是绕过。

正面对抗

直接将创建线程的操作nop掉

首先通过hookpthread_create看一下在哪些地方创建了线程

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"]))
}
  • parg0:指向 pthread_t 类型指针,用于存储新线程 ID
  • parg1:线程属性参数指针
  • parg2:线程入口函数地址(关键监控点)
  • parg3:传递给线程函数的参数指针

image-20250303153610182

拿到了三个偏移地址,但要想获取真实地址进行nop还需要基地址。

先尝试使用dlopen获取基地址

function hook_android_dlopen_ext() {
    Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
        onEnter: function (args) {
            var soPath = Memory.readCString(ptr(args[0]));
            console.log("android_dlopen_ext: " + soPath);
            if(soPath.indexOf("libmsaoaidsec.so") > -1){
                this.hook = true
            }
        },
        onLeave: function (retval) {
            if (retval.toInt32() != 0) {
                console.log("android_dlopen_ext: loaded");
            }
            if(this.hook){
                //尝试打印so的基址
                let r = Process.findModuleByName("libmsaoaidsec.so")
                console.log(r)
            }
        }
    });
}

结果崩溃了

安卓so的加载流程如下:

linker->init_proc->JNI_OnLoad

dlopen获取基址,是要等jni加载后的,但是现在jni还没加载我们的frida就被杀掉了,说明检测在init_proc里,我们需要寻找一个合适的hook时机。

这里做了虚假控制流混淆,但是还是不难判断哪个外部函数最先被调用

image-20250303160039268

该函数调用了_system_property_get获取sdk版本

image-20250303160143807

所以当该函数被调用时,init_proc就已经开始运行了,可以获取基地址

function anti(){
    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)
                    }
                }
            }
        }
    );
}
    function hook_android_dlopen_ext() {
        Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
            onEnter: function (args) {
                var soPath = Memory.readCString(ptr(args[0]));
                console.log("android_dlopen_ext: " + soPath);
                if(soPath.indexOf("libmsaoaidsec.so") > -1){
                    locate_init()
                }
            },
            onLeave: function (retval) {
                if (retval.toInt32() != 0) {
                    console.log("android_dlopen_ext: loaded");
                }
            }

        });
    }


    hook_android_dlopen_ext()
}

image-20250303160732301

真实地址=基地址+偏移

接下来进行nop即可,最后的anti脚本如下

  • 使用 Memory.protect 解除内存写保护
  • 通过 Arm64Writer 在目标地址写入 ret 指令(操作码 0xD65F03C0)
  • 该操作将原函数入口改为立即返回,有效禁用检测函数
function anti(){
    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) {
                       let base = Process.findModuleByName("libmsaoaidsec.so").base
                        nop(base.add("0x1c544"))
                        nop(base.add("0x1b8d4"))
                        nop(base.add("0x26e5c"))
                    }
                }
            }
        }
    );
}
    function nop(addr){
        Memory.protect(addr, 4, 'rwx');
        let writer = new Arm64Writer(addr);
        writer.putRet()
        writer.flush()
        writer.dispose()
    }
    function hook_android_dlopen_ext() {
        Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
            onEnter: function (args) {
                var soPath = Memory.readCString(ptr(args[0]));
                console.log("android_dlopen_ext: " + soPath);
                if(soPath.indexOf("libmsaoaidsec.so") > -1){
                    locate_init()
                }
            },
            onLeave: function (retval) {
                if (retval.toInt32() != 0) {
                    console.log("android_dlopen_ext: loaded");
                }
            }

        });
    }


    hook_android_dlopen_ext()
}

侧面绕过

hookpthread_create函数,创建虚假的线程替换用于检测的线程。

免去了找基地址的操作。

function bypass(){

    function hook_android_dlopen_ext() {
        Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
            onEnter: function (args) {
                var soPath = Memory.readCString(ptr(args[0]));
                console.log("android_dlopen_ext: " + soPath);
                if (soPath.indexOf("libmsaoaidsec.so") > -1) {
                    check_pthread_create()
                }
            },
            onLeave: function (retval) {
                if (retval.toInt32() != 0) {
                    console.log("android_dlopen_ext: loaded");
                }
            }
        });

        return Interceptor
    }

    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)
            if(so_name.indexOf("libmsaoaidsec.so") > -1){
                return fake_pthread_create
            }
            return pthread_create(parg0, parg1, parg2, parg3)

        }, "int", ["pointer", "pointer", "pointer", "pointer"]))
    }
    

    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
    }

    let fake_pthread_create = create_fake_pthread_create()
    hook_android_dlopen_ext()

}

功能破解

没有壳,直接分析即可

function hook_getPro(){

    let UserBean = Java.use("xxx.model.UserBean");
    UserBean["getPro"].implementation = function () {
        console.log(`UserBean.getPro is called`);
        return 1;
    };
}

hook这个之前还是做了大量分析的,最后发现,这个app是前端鉴权的。

因为经过尝试,无论是hook上面这个函数还是通过抓包修改响应,都可以使用会员功能。不过修改响应需要在登陆的时候就修改,其他时候鉴权不会产生数据包,但会触发getPro这个函数。

image-20250303163008782

因为这个UserBean在用户登陆时就构造好了,而且居然是根据前端的响应构造的,因为在登陆时将pro改为1,然后。。。似乎本地存储了,就算退出软件再进来也是会员。这我还hook什么啊!

完整代码

function bypass(){

    function hook_android_dlopen_ext() {
        Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
            onEnter: function (args) {
                var soPath = Memory.readCString(ptr(args[0]));
                console.log("android_dlopen_ext: " + soPath);
                if (soPath.indexOf("libmsaoaidsec.so") > -1) {
                    check_pthread_create()
                }
            },
            onLeave: function (retval) {
                if (retval.toInt32() != 0) {
                    console.log("android_dlopen_ext: loaded");
                }
            }
        });

        return Interceptor
    }

    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)
            if(so_name.indexOf("libmsaoaidsec.so") > -1){
                return fake_pthread_create
            }
            return pthread_create(parg0, parg1, parg2, parg3)

        }, "int", ["pointer", "pointer", "pointer", "pointer"]))
    }


    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
    }

    let fake_pthread_create = create_fake_pthread_create()
    hook_android_dlopen_ext()

}


function hoot_getUser(){
    
    let UserBean = Java.use("xxx.model.UserBean");
    UserBean["getPro"].implementation = function () {
        console.log(`UserBean.getPro is called`);
        let result = this["getPro"]();
        console.log(`UserBean.getPro result=${result}`);
        return 1;
    };
}
function main(){
    Java.perform(
        function (){
            hoot_getUser()
        }
    )
}


function anti(){
    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) {
                       let base = Process.findModuleByName("libmsaoaidsec.so").base
                        nop(base.add("0x1c544"))
                        nop(base.add("0x1b8d4"))
                        nop(base.add("0x26e5c"))
                    }
                }
            }
        }
    );
}
    function nop(addr){
        Memory.protect(addr, 4, 'rwx');
        let writer = new Arm64Writer(addr);
        writer.putRet()
        writer.flush()
        writer.dispose()
    }
    function hook_android_dlopen_ext() {
        Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
            onEnter: function (args) {
                var soPath = Memory.readCString(ptr(args[0]));
                console.log("android_dlopen_ext: " + soPath);
                if(soPath.indexOf("libmsaoaidsec.so") > -1){
                    locate_init()
                }
            },
            onLeave: function (retval) {
                if (retval.toInt32() != 0) {
                    console.log("android_dlopen_ext: loaded");
                }
            }

        });
    }


    hook_android_dlopen_ext()
}

// setImmediate(anti)
setImmediate(bypass)

setTimeout(
    main, 5000
)