过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文件中

有两种方式,一种是正面对抗,一种是绕过。
正面对抗
直接将创建线程的操作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:传递给线程函数的参数指针

拿到了三个偏移地址,但要想获取真实地址进行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时机。
这里做了虚假控制流混淆,但是还是不难判断哪个外部函数最先被调用

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

所以当该函数被调用时,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()
}

真实地址=基地址+偏移
接下来进行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这个函数。

因为这个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
)