长亭百川云 - 文章详情

APP渗透|安服仔们又稳了!过爱/梆企业版初级反调的Frida检测(含脚本)

老呆

579

2024-07-31

此文章只为学习而生,请勿干违法违禁之事,本公众号只在技术的学习上做以分享,通过现实的几个案例来实战锻炼水平,有个大佬公众号私聊我讨论了一下看了看,遇到所以试过,所有行为与本公众号无关。

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抓包。

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2