当前位置: 首页 > news >正文

最新版某货app bypassFrida检测

文章中所有内容仅供学习交流使用,不用于其他任何目的,抓包内容、敏感网址、数据接口均已做脱敏处理。严正声明禁止用于商业和非法用途,否则由此产生的一切后果与作者本人无关。


  • appName:某货(v8.65.0
  • 签名状态V1 + V2
  • 加固状态:未加固(伪几维安全)

废话

本篇文章并不会对该app的各项参数进行逆向分析,只是记录下学习Frida工具时遇到的检测,以及绕过检测的过程。

简单说下使用到的工具:

  • 手机:谷歌Pxiel 6(安卓13系统)
  • 系统环境Linux-kali
  • Frida16.5.9版本
  • Python3.11.14版本
  • IDA Pro:9.3版本

正题

  1. 开启手机,连接到kali虚拟机,打开我们要测试的app程序。正常的流程我们可以先抓包,找到要逆向分析的参数,然后使用Jadx或者JEB去反编译app程序,在JAVA层先进行追踪需要分析的参数,不过这不是本篇文章的重点,本篇文章目前只研究如果绕过appFrida检测。ok那么我们直接使用Fridaapp进行Hook
  2. 俺习惯使用微软大战代码,来进行hook,如图:

  1. 使用adb shell进入设备的shell环境。

  1. 启动好Frida-server后,我们对app进行Hook

  1. 这里可以看到Frida给我们的报错。进程直接被中止了。(这里我用的是-f Spawn的方式进行的注入,其实用Attach也是一样的结果,有兴趣的小伙伴可以自行尝试。)
  2. 遇到这种情况,一般就是有Frida检测。(Frida的检测点,网上文章巨多,我这里就不在赘述了。)
  3. 最容易过的就是检测Frida服务端的进程命是否有frida字样,如果存在则直接关闭app。其二是端口检测,Frida的默认通信端口是27042,有些app会检测是否开启该端口,如果开启则直接关闭app
  4. 第一点,我们在给手机传入Frida-server时,就直接修改了名字,当前案例app未检测端口,所以我就没有修改端口号。
  5. 其他的检测点,我们后边遇到再说。
  6. Frida的检测一般是Native配合Java层,所以我们可以Hook dlopen函数来看,是加载到哪个so文件,app被强制关闭的,Frida检测代码,一般就在最后加载的so文件中。

  1. 可以看到最后加载的so文件是libmsaoaidsec.so文件,我们把该文件拖到桌面上,稍后会用到。
  2. 这时候基本可以确认,检测代码是在这个文件中,可是我们后边该怎么做呢?我们该报着什么思路去做呢?
  3. 有个前置知识点,so文件的加载流程,网上有很多的大佬文章,我也是个小白,这里简单提几句,详细的内容可以看看其他大佬写的。从System.loadLibrary开始,加载so文件,从Java层到底层linker(链接器),核心就是将二进制文件映射到内存、解析符合,并初始化代码。而在linker链接器时,有DT_INITDT_INIT_ARRAY俩个函数,用来初始化。他们的执行逻辑是:INIT --> INIT_ARRAY --> JNI_OnLoad --> dlopen
  4. 小白可以这么浅的理解,实际情况是有所差距的,正是调用了dlopen函数,才会执行前面的一系列初始化操作。上面说的执行逻辑,可以理解成hook时机的先后。邪修可以先按我这个逻辑死记硬背。
  5. 说完这个前置知识,我们回到正题,确定了检测so文件后,我们来hook pthread_create看看这个so文件创建了那些线程。为什么要看创建的线程呢?很简单的道理,进程是有自己的工作的,它们负责保证app正常运行,提供正常服务,而创建出来的线程就负责检测,有没有Frida注入进来干坏事。就像公司之间分工明确,老板负责赚钱,牛马负责给老板赚钱一样。牛马就是线程,老板就是进程。
function hook_dlopen(so_name) { let dlopen = Module.findExportByName(null, so_name); if (dlopen) { Interceptor.attach(dlopen, { onEnter: function (args) { let pathPtr = args[0]; if (pathPtr != undefined && pathPtr != null) { let path = pathPtr.readCString(); if (path.indexOf("libmsaoaidsec.so") >= 0) { console.log("load so::> ", path) this.hookTarget = true; hook_pthread_creat() } } }, onLeave: function (retval) { if (this.hookTarget) { console.log("over!!") } } }) } }; function hook_pthread_creat() { let pthread_create = Module.findExportByName("libc.so", "pthread_create"); let libmsaoaidsec = Process.findModuleByName("libmsaoaidsec.so"); if (!libmsaoaidsec) { console.log("libmsaoaidsec.so is not found!!") return; } if (pthread_create) { Interceptor.attach(pthread_create, { onEnter: function (args) { let pthreadAddr = args[2]; if (pthreadAddr.compare(libmsaoaidsec.base) >= 0 && pthreadAddr.compare(pthreadAddr.add(libmsaoaidsec.size)) <= 0) { console.log("libmsaoaidsec.so addr: " + pthreadAddr, "offset: ", pthreadAddr.sub(libmsaoaidsec.base)) } }, onLeave: function (retval) { } }) } }; function main() { hook_dlopen("android_dlopen_ext") }; setImmediate(main)
  1. 再次执行这个代码,如图:

  1. 这里报错文件未找到,说明没Hook到。为什么?这就用到了前置知识的内容。
  2. 就是因为此时的Hook时机不对,虽然Hook到了该so文件的路径,其实这个时候刚刚加载到so文件,还没有把符号表映射到内存,所以我们hook显示找不到,那就要寻找其他的hook时机才行,就是更早的init阶段。
  3. 这时候我们使用ida反编译libmsaoaidsec.so文件。

  1. 这里我们站在巨人的肩膀,在字符串窗口直接搜索“sdk”,这个方法是用来抓取手机的sdk版本的。方法内部如图:

  1. 看下图,这里可以看到,这个方法实际在init_proc阶段有被调用过,所以也符合我们的hook时机。

  1. 接下来修改我们的hook代码,重新再试一下。
var hookPtrheadCreat = false; function hook_dlopen(so_name) { let dlopen = Module.findExportByName(null, so_name); if (dlopen) { Interceptor.attach(dlopen, { onEnter: function (args) { let pathPtr = args[0]; if (pathPtr != undefined && pathPtr != null) { let path = pathPtr.readCString(); if (path.indexOf("libmsaoaidsec.so") >= 0) { console.log("load so::> ", path) this.hookTarget = true; hook_system_property_get(); // hook_pthread_creat() } } }, onLeave: function (retval) { if (this.hookTarget) { console.log("over!!") } } }) } }; function hook_pthread_creat() { let pthread_create = Module.findExportByName("libc.so", "pthread_create"); let libmsaoaidsec = Process.findModuleByName("libmsaoaidsec.so"); if (!libmsaoaidsec) { console.log("libmsaoaidsec.so is not found!!") return; } if (pthread_create) { Interceptor.attach(pthread_create, { onEnter: function (args) { let pthreadAddr = args[2]; if (pthreadAddr.compare(libmsaoaidsec.base) >= 0 && pthreadAddr.compare(libmsaoaidsec.base.add(libmsaoaidsec.size)) < 0) { console.log("libmsaoaidsec.so addr: " + pthreadAddr, "offset: ", pthreadAddr.sub(libmsaoaidsec.base)); } }, onLeave: function (retval) { } }) } }; // _system_property_get("ro.build.version.sdk", v1); function hook_system_property_get() { let _system_property_get = Module.findExportByName("libc.so", "__system_property_get"); if (_system_property_get) { Interceptor.attach(_system_property_get, { onEnter: function (args) { let argName = args[0].readCString(); if (argName === "ro.build.version.sdk") { let callerAddr = this.returnAddress; let module = Process.findModuleByAddress(callerAddr); if (module && module.name.indexOf("libmsaoaidsec.so") >= 0) { if (!hookPtrheadCreat) { hook_pthread_creat(); hookPtrheadCreat = true; } } } }, onLeave: function (retval) { } }) } }; function main() { hook_dlopen("android_dlopen_ext") }; setImmediate(main)
  1. 这个offset就是检测函数的偏移,偏移地址一般是不会变的,so文件加载进内存的地址是不固定的,所以我们每次都需要重新获取,获取到so文件的基址加上这个偏移地址,就是检测函数的位置。
  2. 可以用ida来简单看下检测函数。(0x1c544)

  1. 这是其中的一个检测位置,fd检测,通俗的讲,就是检查frida进程的句柄数量与进程的软链接指向。再看(0x1b8d4)

  1. 这个是在检测线程ID相关的内容,检测线程ID句柄中有没有与Frida相关的。比如:frida、gmain、gum-js-loop、pool-frida等。再看最后一个(0x26e5c)

  1. 读取proc/self/maps进行检测,如果不是魔改的frida,依旧会在该目录中留下敏感的字眼,比如:frida、gum-js-loop等。
  2. 那么这些监测点该怎么绕过呢?可以针对每一处检测位置做hook,给他伪装成无frida痕迹的样子。也可以直接把这三个偏移函数置空。
  3. 建议是优先置空操作,如果置空后app无法正常运行,那么就针对每一处监测点做hook。本篇文章直接做了置空,已验证,可以绕过。
var hookPtrheadCreat = false; function hook_dlopen(so_name) { let dlopen = Module.findExportByName(null, so_name); if (dlopen) { Interceptor.attach(dlopen, { onEnter: function (args) { let pathPtr = args[0]; if (pathPtr != undefined && pathPtr != null) { let path = pathPtr.readCString(); if (path.indexOf("libmsaoaidsec.so") >= 0) { console.log("load so::> ", path) this.hookTarget = true; hook_system_property_get(); // hook_pthread_creat() } } }, onLeave: function (retval) { if (this.hookTarget) { console.log("over!!") } } }) } }; function hook_pthread_creat() { let pthread_create = Module.findExportByName("libc.so", "pthread_create"); let libmsaoaidsec = Process.findModuleByName("libmsaoaidsec.so"); if (!libmsaoaidsec) { console.log("libmsaoaidsec.so is not found!!") return; } let base = libmsaoaidsec.base; let size = libmsaoaidsec.size; if (pthread_create) { Interceptor.attach(pthread_create, { onEnter: function (args) { let pthreadAddr = args[2]; let offset = pthreadAddr.sub(base); if (pthreadAddr.compare(base) >= 0 && pthreadAddr.compare(base.add(size)) < 0) { console.log("libmsaoaidsec.so addr: " + pthreadAddr, "offset: ", offset); replace_func(pthreadAddr, offset); } }, onLeave: function (retval) { } }) } }; // _system_property_get("ro.build.version.sdk", v1); function hook_system_property_get() { let _system_property_get = Module.findExportByName("libc.so", "__system_property_get"); if (_system_property_get) { Interceptor.attach(_system_property_get, { onEnter: function (args) { let argName = args[0].readCString(); if (argName === "ro.build.version.sdk") { let callerAddr = this.returnAddress; let module = Process.findModuleByAddress(callerAddr); if (module && module.name.indexOf("libmsaoaidsec.so") >= 0) { if (!hookPtrheadCreat) { hook_pthread_creat(); hookPtrheadCreat = true; } } } }, onLeave: function (retval) { } }) } }; function replace_func(base, offset) { let module = Process.findModuleByAddress(base); if (module) { Interceptor.replace(module.base.add(offset), bypass()) } }; function bypass() { return new NativeCallback(function () { console.log("replace is ok!!!") }, 'void', []) }; function main() { hook_dlopen("android_dlopen_ext") }; setImmediate(main)
  1. 这样就可以绕过Frida检测,可以愉快的逆向分析加密参数啦!
http://www.cnnetsun.cn/news/1803166.html

相关文章:

  • 从零开始掌握数据同化与集合卡尔曼滤波:9个交互式教程带你快速入门
  • 三分钟拯救你的B站缓存:零转码无损转换技术完全指南
  • 5分钟搞定Realtek 8192FU无线网卡:Linux驱动终极安装指南
  • 【数据结构】环形队列(循环队列)实战:从原理到C语言高效实现
  • lerobot so101机械臂锁死紧急救援!删除校准文件三步重生术
  • Arduino MQTT客户端终极指南:3步实现物联网设备快速联网
  • Multisim 12仿真避坑指南:搭建直流稳压电源时这5个参数千万别设错
  • 别让AI代码,变成明天的技术债吹
  • PyTorch 2.8模型部署实战:使用TorchServe构建高可用推理服务
  • GESPC++三级考试语法知识---位运算历年真题
  • 百考通:AI精准赋能,打破传统学术写作的壁垒
  • 【AI原生研发工具链2026权威选型白皮书】:覆盖LLM编排、智能测试、语义CI/CD与Agent化运维的7大维度实测对比
  • 国产操作系统SSD的工程权衡:四款热门型号横向测评(含湖南天硕技术拆解)
  • AI专著生成新玩法!借助工具,短时间打造专业学术专著
  • 比迪丽AI绘画模型.NET集成开发:企业级应用方案
  • 基于uA741的波形变换电路设计与Multisim仿真优化
  • C++移动语义与完美转发在资源管理类设计中的最佳实践模式
  • BMS硬件版与软件版:从电路板到代码,如何选择你的电池守护者?
  • 别等延毕才后悔!大论文送审前一定要自查的几点
  • 高精度生菜生长阶段识别系统:YOLO26s在5类周期检测中的实现与评估(免费获取模型)
  • 别再花钱买企业邮箱了!手把手教你用Cloudflare邮件路由免费搞定个性化域名邮箱(支持Gmail/Outlook发信)
  • 解决iOS下小程序createInnerAudioContext无声问题的3种实用方案
  • Windows内核驱动手动映射终极指南:KDMapper完全攻略
  • 保姆级教程:用华为eNSP模拟器搞定USG6000V防火墙多区域实验(含完整拓扑与配置)
  • day03统计师考试(初级)变量与数据
  • BepInEx:Unity游戏模组开发的终极框架指南
  • 使用DuMate帮助创建 Python 3.9 环境并部署 Kotti CMS
  • 如何选择最适合你的macOS开源RSS阅读器:从入门到精通的完整指南
  • ComfyUI TensorRT加速:告别漫长等待,解锁NVIDIA显卡的终极AI创作性能
  • 007、微服务架构设计与服务拆分策略