此文章只为学习而生,请勿干违法违禁之事,本公众号只在技术的学习上做以分享,通过现实的几个案例来实战锻炼水平,有个大佬公众号私聊我讨论了一下看了看,遇到所以试过,所有行为与本公众号无关。
sdfd
01
日常截图
漏洞盒子众测的某金融APP用了爱马氏加密啊,检测Frida难受啊。
然后又测试了一下梆某人的加密啊,又检测Frida难受啊,还不能通用,烦死了。
02
反调思路来源
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
03
起手式
第一个肯定先看的金融APP的爱马仕加密。
在启动后就Process terminated。
01
Hook android_dlopen_ext
大佬们说检测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使其退出。
02
HOOK pthread_create
现在这步是确认是否由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创建的检测线程。
03
Patch pthread_create
知道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()`
发现应用跑无响应了,但是经过了很长的时间没有退出说明有点戏,试试另一个方法。
04
Patch 所有调用pthread_create 函数的caller
`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()`
还是不行,直到看到一位大神评论:
04
初级脚本最终版
01
爱马仕加密最终方案:自建pthread_create
`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里即可无脑过。
02
梆某人最终脚本
`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)
05
小叙
最近和前启明师傅聊了一下过抓包检测的问题,佬给我提供了一个很好的思路,过几天再更新通杀APP抓包。