尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

实战分享:用Frida绕过App反调试的5种常见检测(附完整Hook脚本)

实战分享:用Frida绕过App反调试的5种常见检测(附完整Hook脚本) 实战指南Frida对抗App反调试的深度策略与脚本实现在移动安全研究领域动态分析工具Frida已成为逆向工程师的标配武器。然而随着安全意识的提升越来越多的应用开始部署反调试机制专门针对Frida等工具进行检测和阻断。本文将深入剖析五种典型的反调试检测点并提供可直接投入实战的Hook脚本解决方案。1. 文件特征检测的绕过策略应用通常会扫描/data/local/tmp路径下的可疑文件如frida-server或相关组件。这种检测方式简单直接但同样容易绕过。核心思路是通过重命名和路径伪装来消除特征标识。以下是完整的Hook脚本实现function bypass_file_checks() { const fopen Module.findExportByName(null, fopen); Interceptor.attach(fopen, { onEnter: function(args) { this.path args[0].readCString(); if (this.path.includes(/data/local/tmp)) { console.log([] Detected file access: ${this.path}); // 替换路径中的敏感关键词 const fakePath this.path.replace(frida, xyz) .replace(re.frida.server, random.data); args[0] Memory.allocUtf8String(fakePath); } } }); }关键改进点不仅处理frida关键词还覆盖re.frida.server等常见变体动态修改路径参数而非简单返回错误保留原始功能的同时消除检测特征2. 端口检测的隐蔽通信方案默认的Frida服务端口(27042)是明显的检测目标。更隐蔽的做法是使用随机高端口号启动服务./frida-server -l 0.0.0.0:58923配套的ADB转发和连接命令adb forward tcp:58923 tcp:58923 frida -H 127.0.0.1:58923 包名 -l hook.jsHook脚本增强版function anti_port_detection() { const getpeername Module.findExportByName(null, getpeername); Interceptor.attach(getpeername, { onLeave: function(retval) { if (!this.returnAddress.isNull()) { const port this.returnAddress.add(0x12).readU16(); if (port 58923) { // 我们的自定义端口 this.returnAddress.add(0x12).writeU16(443); // 伪装成HTTPS端口 } } } }); }进阶技巧定期更换端口号使用SSL包装通信流量结合iptables进行本地端口重定向3. 内存映射(maps)的深度伪装/proc/self/maps文件泄露了大量运行时信息。以下是两种对抗检测的方案方案A字符串函数Hookfunction hook_maps_checks() { const strstr Module.findExportByName(libc.so, strstr); Interceptor.attach(strstr, { onEnter: function(args) { const needle args[1].readCString(); const sensitiveKeywords [frida, gum-js, linjector]; if (sensitiveKeywords.some(kw needle.includes(kw))) { this.shouldHook true; } }, onLeave: function(retval) { if (this.shouldHook) { retval.replace(ptr(0)); // 返回NULL表示未找到 } } }); }方案B文件内容重定向function redirect_maps() { const open Module.findExportByName(null, open); const read Module.findExportByName(null, read); Interceptor.replace(open, new NativeCallback((pathname, flags) { const fd new NativeFunction(open, int, [pointer, int])(pathname, flags); const path pathname.readCString(); if (path path.includes(/proc/self/maps)) { // 创建临时文件写入伪造内容 const fakeMaps /data/data/fake.maps; const fakeFd new NativeFunction(open, int, [pointer, int])( Memory.allocUtf8String(fakeMaps), O_CREAT | O_RDWR ); // 读取真实maps并过滤敏感内容 const buf Memory.alloc(4096); let bytesRead; while ((bytesRead new NativeFunction(read, int, [int, pointer, int])( fd, buf, 4096)) 0) { let content buf.readCString(bytesRead); content content.replace(/frida/g, libart.so) .replace(/re\.frida\.server/g, system.lib); // 写入过滤后的内容 Memory.writeUtf8String(buf, content); Process.write(fakeFd, buf, bytesRead); } return fakeFd; } return fd; }, int, [pointer, int])); }对比分析方案优点缺点适用场景字符串Hook实现简单性能影响小可能遗漏非字符串检测初级防护应用文件重定向彻底隐藏痕迹实现复杂可能影响性能高级安全检测环境4. 线程状态(status)的完美伪装Frida创建的线程具有特征名称(gmain, gum-js-loop等)检测脚本如下function disguise_threads() { const keywords [gmain, gdbus, gum-js-loop, pool-frida]; const pthread_create Module.findExportByName(null, pthread_create); Interceptor.attach(pthread_create, { onEnter: function(args) { const namePtr args[3]; // 线程名参数 if (!namePtr.isNull()) { const name namePtr.readCString(); if (keywords.some(kw name.includes(kw))) { // 替换为无害的线程名 const newName system_thr_ Math.floor(Math.random() * 1000); args[3] Memory.allocUtf8String(newName); } } } }); // 处理已存在的线程 const prctl Module.findExportByName(null, prctl); Interceptor.attach(prctl, { onEnter: function(args) { if (args[0] 15) { // PR_SET_NAME const name args[1].readCString(); if (keywords.some(kw name.includes(kw))) { const newName worker_ Math.floor(Math.random() * 1000); args[1] Memory.allocUtf8String(newName); } } } }); }关键点拦截线程创建和重命名操作动态生成随机的无害线程名同时处理新线程和已有线程的改名5. Inline Hook检测的对抗之道高级反调试会检查关键函数是否被Hook。对抗检测需要保存原始字节码function backup_original_code(target) { const funcPtr Module.findExportByName(null, target); const original Memory.readByteArray(funcPtr, 8); return {ptr: funcPtr, code: original}; }临时恢复原始执行流function restore_for_check(target, backup) { Memory.protect(backup.ptr, 8, rwx); Memory.writeByteArray(backup.ptr, backup.code); // 执行关键检查 const result check_target_function(target); // 重新应用Hook apply_hook(target); return result; }完整对抗脚本function anti_hook_detection() { const mprotect Module.findExportByName(null, mprotect); const memcmp Module.findExportByName(null, memcmp); // 备份关键函数 const targets [strstr, fopen, ptrace]; const backups {}; targets.forEach(t backups[t] backup_original_code(t)); // Hook mprotect防止内存保护检查 Interceptor.attach(mprotect, { onLeave: function(retval) { const addr this.context.x0; targets.forEach(t { if (addr.equals(backups[t].ptr)) { retval.replace(-1); // 假装保护失败 } }); } }); // Hook memcmp防止字节码比对 Interceptor.attach(memcmp, { onEnter: function(args) { const addr1 args[0]; const addr2 args[1]; targets.forEach(t { if (addr1.equals(backups[t].ptr) || addr2.equals(backups[t].ptr)) { this.shouldFake true; } }); }, onLeave: function(retval) { if (this.shouldFake) { retval.replace(0); // 返回相同 } } }); }实施建议优先Hook检测函数而非全部函数保持原始函数行为模式定期轮换Hook点避免模式固定6. 定制化Frida服务的构建与部署终极解决方案是修改Frida源码并重新编译关键修改点默认路径和文件名通信协议特征线程命名规则内存映射标识编译步骤git clone https://github.com/frida/frida-core cd frida-core # 修改相关源码文件 make make install部署优化# 使用随机名称部署 mv frida-server /data/local/tmp/.system_daemon # 设置隐藏属性 chmod 600 /data/local/tmp/.system_daemon # 启动时伪装成系统服务 /system/bin/sh -c LD_LIBRARY_PATH/system/lib64 /data/local/tmp/.system_daemon 定制建议修改frida-core/libc/process.c中的线程命名逻辑调整frida-core/glue/linux/frida-helper-backend-glue.c中的路径处理混淆frida-gum/gum/arch-x86/gumx86relocator.c中的代码特征在实际对抗中建议组合使用多种技术。例如先部署定制版Frida服务再配合运行时Hook脚本进行动态伪装。不同应用的反调试策略差异很大需要根据具体情况调整方案。
返回列表