漏洞盒子众测的某金融APP用了爱马氏加密啊,检测Frida难受啊。
然后又测试了一下梆某人的加密啊,又检测Frida难受啊,还不能通用,烦死了。
1. 绕过bilibili frida反调试:https://bbs.kanxue.com/thread-277034.htm
2. frida常用检测点及其原理-一把梭方案:https://bbs.kanxue.com/thread-278145.htm
3. 绕过最新版bilibili app反frida机制:https://bbs.kanxue.com/thread-281584.htm
4. 某车联网APPx梆反调试分析:https://bbs.kanxue.com/thread-277692.htm
5. 记一次爱加密反调试分析及绕过思路:https://bbs.kanxue.com/thread-259619.htm
第一个肯定先看的金融APP的爱马仕加密。
在启动后就Process terminated。
大佬们说检测Frida一般都在Native层实现,会用线程轮询的方式来检测,那hook线程创建函数android_dlopen_ext,来看看由哪个so实现或者在加载到哪个so时候触发反调试进程终止。
// 检测SO代码然后将hook到的so文件进行过检测
function hook_dlopen() {
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);
}
}
}
);
}
// 调用查看检测的
hook_dlopen()
发现了libexecmain.so执行后进程退出。
我们可以猜测libexecmain.so中创建了一个线程检测到了Frida使其退出。
现在这步是确认是否由libexecmain创建的检测线程,用以下脚本:
function hook_pthread_create(){
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))
}
},
}
)
}
hook_pthread_create()
发现只有libexec.so一直在创建进程,而libexecmain.so从未出现,而libexec.so是上一个调用的,可能就是由libexec.so创建的检测线程。
知道App通过哪些SO进行pthrea_create自己创建的线程了,我们可以尝试进行Patch HOOK过掉看看能不能过掉检测。
// 1. patch 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"])
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("pthread_create")
return 0;
}
return org_pthread_create(a, b, c, d)
}, "int", ["pointer", "pointer", "pointer", "pointer"])
Interceptor.replace(pthread_create, my_pthread_create)
}
patchPthreadCreate()
发现应用跑无响应了,但是经过了很长的时间没有退出说明有点戏,试试另一个方法。
function patchPthreadCreateCaller(){
let pthread_create = Module.findExportByName(null, "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) {
//start_rtn地址 start_rtn偏移 caller地址
console.log(args[2], args[2].sub(base), this.context.lr.sub(m.base))
}
let addr = args[2].sub(base)
console.log(addr)
}
})
}
patchPthreadCreateCaller()
单独列出来的addr就是创建线程的偏移量,所以再改进一下。
function patchPthreadCreateCaller(){
let pthread_create = Module.findExportByName(null, "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) {
//start_rtn地址 start_rtn偏移 caller地址
console.log(args[2], args[2].sub(base), this.context.lr.sub(m.base))
if (args[2] >= base && args[2] < base.add(m.size)) {
Memory.patchCode(args[2], 4, code => {
console.log("Patching thread function at " + args[2]);
const cw = new Arm64Writer(code, {pc: args[2]});
cw.putLdrRegU64('x0', 0);
cw.flush();
});
}
}
}
})
}
patchPthreadCreateCaller()
还是不行,直到看到一位大神评论:
function hook_pthread() {
var pthread_create_addr = Module.findExportByName(null, '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 (parg0, parg1, parg2, parg3) {
var so_name = Process.findModuleByAddress(parg2).name;
var so_path = Process.findModuleByAddress(parg2).path;
var so_base = Module.getBaseAddress(so_name);
var offset = parg2 - so_base;
console.log("so_name", so_name, "offset", offset, "path", so_path, "parg2", parg2);
var PC = 0;
if ((so_name.indexOf("libexec.so") > -1) || (so_name.indexOf("xxxx") > -1)) {
console.log("find thread func offset", so_name, offset);
if ((207076 === offset)) {
console.log("anti bypass");
} else if (207308 === offset) {
console.log("anti bypass");
} else if (283820 === offset) {
console.log("anti bypass");
} else if (286488 === offset) {
console.log("anti bypass");
} else if (292416 === offset) {
console.log("anti bypass");
} else if (293768 === offset) {
console.log("anti bypass");
} else if (107264 === offset) {
console.log("anti bypass");
} else {
PC = pthread_create(parg0, parg1, parg2, parg3);
console.log("ordinary sequence", PC)
}
} else {
PC = pthread_create(parg0, parg1, parg2, parg3);
// console.log("ordinary sequence", PC)
}
return PC;
}, "int", ["pointer", "pointer", "pointer", "pointer"]))
}
hook_pthread();
爱马仕加密的初步反调可以用此脚本无脑过。
我们直接看if elseif我改为1使其错误,得到offset偏移量:
然后一个个填进if elseif里即可无脑过。
function replace_str() {
var pt_strstr = Module.findExportByName("libc.so", 'strstr');
var pt_strcmp = Module.findExportByName("libc.so", 'strcmp');
Interceptor.attach(pt_strstr, {
onEnter: function (args) {
var str1 = args[0].readCString();
var str2 = args[1].readCString();
if (str2.indexOf("tmp") !== -1 ||
str2.indexOf("frida") !== -1 ||
str2.indexOf("gum-js-loop") !== -1 ||
str2.indexOf("gmain") !== -1 ||
str2.indexOf("gdbus") !== -1 ||
str2.indexOf("pool-frida") !== -1||
str2.indexOf("linjector") !== -1) {
// console.log("strstr-->", str1, str2);
this.hook = true;
}
}, onLeave: function (retval) {
if (this.hook) {
retval.replace(0);
}
}
});
Interceptor.attach(pt_strcmp, {
onEnter: function (args) {
var str1 = args[0].readCString();
var str2 = args[1].readCString();
if (str2.indexOf("tmp") !== -1 ||
str2.indexOf("frida") !== -1 ||
str2.indexOf("gum-js-loop") !== -1 ||
str2.indexOf("gmain") !== -1 ||
str2.indexOf("gdbus") !== -1 ||
str2.indexOf("pool-frida") !== -1||
str2.indexOf("linjector") !== -1) {
console.log("strcmp-->", str1, str2);
this.hook = true;
}
}, onLeave: function (retval) {
if (this.hook) {
retval.replace(0);
}
}
})
}
replace_str()
so的加载流程,那么就会知道linker会先对so进行加载与链接,然后调用so的.init_proc函数,接着调用.init_array中的函数,最后才是JNI_OnLoad函数,但是啥也不管一把梭是最方便的,可能会有点卡,多起几次。(https://bbs.kanxue.com/thread-278145.htm)
最近和前启明师傅聊了一下过抓包检测的问题,佬给我提供了一个很好的思路,过几天再更新通杀APP抓包。
原文始发于微信公众号(进击的HACK):APP渗透|安服仔们又稳了!过爱/梆企业版初级反调的Frida检测(含脚本)
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论