37.安卓逆向2-frida hook技术-过firda检测(二)(过D-Bus检测和搭配maps检测进行使用)
免责声明:内容仅供学习参考,请合法利用知识,禁止进行违法犯罪活动!
内容参考于:图灵Python学院
工具下载:
链接:https://pan.baidu.com/s/1bb8NhJc9eTuLzQr39lF55Q?pwd=zy89
提取码:zy89
复制这段内容后打开百度网盘手机App,操作更方便哦
上一个内容:36.安卓逆向2-frida hook技术-过firda检测(一)(端口检测和maps检测)
D-Bus是一种进程间通信(IPC)和远程过程调用(RPC)机制,最初是为Linux系统开发的,目的是用一个统一的协议替代现有的和竞争的ipc解决方案
说法1
app的D-Bus检测原理,app会向所有端口都发送一个D-Bus认证消息,如果是给frida服务端发送的认证消息,firda在返回内容中会有frida这样的单词(frida的特征),检测原理就是判断D-Bus返回的数据里有没有frida相关的文字(frida特征),如果有说明存在firda,如果没有说明不存在frida,这种消息会是字符串类型,字符串类型必然会用到strcmp或者strstr
strcmp函数 功能:
比较两个字符串的大小
原型:
int strcmp(const char *s1, const char *s2);
返回值:
若s1 > s2,返回正整数
若s1 == s2,返回 0
若s1 < s2,返回负整数
strstr函数 功能:
在一个字符串中查找另一个字符串的首次出现位置
原型:
char *strstr(const char *haystack, const char *needle);
返回值:
若找到,返回指向第一次出现needle的位置的指针,也就是非0的值
若未找到,返回NULL,也就是0
若needle是空字符串,返回haystack
所以我们把strcmp和strstr这俩函数进行hook,然后把frida服务端会返回的内容全拦截下来,然后返回不存在frida,过D-Bus之前,如下图
使用Frida脚本hook strcmp和strstr函数,过D-Bus检测
function replace_str() { // 1. 获取libc.so中字符串处理函数的地址 // strstr: 在字符串中查找子串(返回子串首次出现的位置,未找到返回null) var pt_strstr = Module.findExportByName(\"libc.so\", \'strstr\'); // strcmp: 比较两个字符串(返回0表示相等,非0表示不等) var pt_strcmp = Module.findExportByName(\"libc.so\", \'strcmp\'); // 2. 挂钩strstr函数,拦截子串查找操作 Interceptor.attach(pt_strstr, { // 当strstr被调用时执行(进入函数时) onEnter: function (args) { // args[0]:被查找的母字符串(const char* haystack) // args[1]:要查找的子字符串(const char* needle) var str1 = args[0].readCString(); // 读取母字符串 var str2 = args[1].readCString(); // 读取要查找的子串 // 检查要查找的子串是否包含敏感特征(Frida相关或检测关键词) // 这些特征是目标程序可能用来检测Frida的关键字符串 if (str2.indexOf(\"SigBlk\") !== -1 || // 可能与进程信号状态检测相关 str2.indexOf(\"tmp\") !== -1 || // 临时目录(Frida常使用/data/local/tmp) str2.indexOf(\"frida\") !== -1 || // Frida核心特征字符串 str2.indexOf(\"gum-js-loop\") !== -1 || // Frida JS引擎循环线程特征 str2.indexOf(\"gmain\") !== -1 || // Frida依赖的GLib主循环特征 str2.indexOf(\"gdbus\") !== -1 || // Frida与系统通信的D-Bus特征 str2.indexOf(\"pool-frida\") !== -1 || // Frida线程池特征 str2.indexOf(\"linjector\") !== -1) { // 常见注入工具特征 // 标记需要拦截处理(后续修改返回值) this.hook = true; } }, // 当strstr执行结束时执行(离开函数时) onLeave: function (retval) { // 如果是需要拦截的情况,修改返回值 if (this.hook) { // 将返回值替换为0x0(即NULL) // 作用:让目标程序认为\"未找到敏感子串\",从而绕过检测 retval.replace(0x0); } } }); // 3. 挂钩strcmp函数,拦截字符串比较操作 Interceptor.attach(pt_strcmp, { // 当strcmp被调用时执行(进入函数时) onEnter: function (args) { // args[0]:第一个待比较的字符串 // args[1]:第二个待比较的字符串 var str1 = args[0].readCString(); var str2 = args[1].readCString(); // 检查被比较的字符串中是否包含敏感特征(同strstr的检测逻辑) 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) { // 标记需要拦截处理(后续修改返回值) this.hook = true; } }, // 当strcmp执行结束时执行(离开函数时) onLeave: function (retval) { // 如果是需要拦截的情况,修改返回值 if (this.hook) { // 将返回值替换为0x0(表示两个字符串\"相等\") // 作用:让目标程序认为敏感字符串比较结果符合预期,避免触发检测逻辑 retval.replace(0x0); } } })}
配合上一个内容中过maps检测的代码
function mapsRedirect() { // 定义伪造的maps文件路径 var FakeMaps = \"/data/data/com.yimian.envcheck/maps\"; // 获取libc.so库中\'open\'函数的地址 const openPtr = Module.getExportByName(\'libc.so\', \'open\'); // 根据地址创建一个新的NativeFunction对象,表示原生的\'open\'函数 const open = new NativeFunction(openPtr, \'int\', [\'pointer\', \'int\']); // 查找并获取libc.so库中\'read\'函数的地址 var readPtr = Module.findExportByName(\"libc.so\", \"read\"); // 创建新的NativeFunction对象表示原生的\'read\'函数 var read = new NativeFunction(readPtr, \'int\', [\'int\', \'pointer\', \"int\"]); // 分配512字节的内存空间,用于临时存储从maps文件读取的内容 var MapsBuffer = Memory.alloc(512); // 创建一个伪造的maps文件,用于写入修改后的内容,模式为\"w\"(写入) var MapsFile = new File(FakeMaps, \"w\"); // 使用Interceptor替换原有的\'open\'函数,注入自定义逻辑 Interceptor.replace(openPtr, new NativeCallback(function(pathname, flag) { // 调用原始的\'open\'函数,并获取文件描述符(FD) var FD = open(pathname, flag); // 读取并打印尝试打开的文件路径 var ch = pathname.readCString(); if (ch.indexOf(\"/proc/\") >= 0 && ch.indexOf(\"maps\") >= 0) { console.log(\"open : \", pathname.readCString()); // 循环读取maps内容,并写入伪造的maps文件中,同时进行字符串替换以隐藏特定信息 while (parseInt(read(FD, MapsBuffer, 512)) !== 0) { var MBuffer = MapsBuffer.readCString(); MBuffer = MBuffer.replaceAll(\"/data/local/tmp/re.frida.server/frida-agent-64.so\", \"FakingMaps\"); MBuffer = MBuffer.replaceAll(\"re.frida.server\", \"FakingMaps\"); MBuffer = MBuffer.replaceAll(\"frida-agent-64.so\", \"FakingMaps\"); MBuffer = MBuffer.replaceAll(\"frida-agent-32.so\", \"FakingMaps\"); MBuffer = MBuffer.replaceAll(\"frida\", \"FakingMaps\"); MBuffer = MBuffer.replaceAll(\"/data/local/tmp\", \"/data\"); // 将修改后的内容写入伪造的maps文件 MapsFile.write(MBuffer); } // 为返回伪造maps文件的打开操作,分配UTF8编码的文件名字符串 var filename = Memory.allocUtf8String(FakeMaps); // 返回打开伪造maps文件的文件描述符 return open(filename, flag); } // 如果不是目标maps文件,则直接返回原open调用的结果 return FD; }, \'int\', [\'pointer\', \'int\']));}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(\"SigBlk\") !== -1 || 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(0x0); } } }); 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(0x0); } } })}mapsRedirect()replace_str()
效果图:上方的脚本有点不稳定(app会闪退),但是原理就这个事,后面会用魔改的frida不会采用脚本,这里的脚本只是为了知道怎样检测的frida,然后魔改的frida为什么可以过检测(这里使用frida通过hook某些函数来避免出现frida的特征,而魔改的frida直接在frida源码中把这些特征修改了)
frida常规检测点就结束了,但还有其它手段过检测,在下一节中