news 2026/6/23 20:35:05

【Frida Android】 实战篇15:Frida检测与绕过——基于/proc/self/maps的攻防实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Frida Android】 实战篇15:Frida检测与绕过——基于/proc/self/maps的攻防实战

文章目录

    • 1. 前言:为什么企业要检测Frida
      • 1.1 Frida的安全威胁:企业检测的核心动因
      • 1.2 Frida检测的典型应用场景
      • 1.3 学习Frida绕过的意义
    • 2. 检测原理:基于/proc/self/maps的Frida特征识别
      • 2.1 proc/self/maps的核心作用
      • 2.2 基于/proc/self/maps的Frida检测逻辑
    • 3. APK的C++检测实现:代码逻辑与人工验证
      • 3.1 C++核心检测代码(JNI实现)
        • 代码逻辑梳理
      • 3.2 人工验证Frida注入与检测效果
    • 4. Hook基础分析:通过JADX反编译定位检测逻辑
      • 4.1 步骤1:JADX反编译APK,定位JNI调用方法
      • 4.2 步骤2:梳理检测结果的展示流程
      • 4.3 核心结论:绕过的关键切入点
    • 5. 两种Hook绕过思路详解
      • 5.1 思路1:Hook libc.so的fopen函数,重定向文件读取
        • 5.1.1 原理:C++文件操作的底层依赖
        • 5.1.2 实现:replace与attach两种语法的绕过脚本
          • 方式1:Interceptor.replace(替换函数实现)
          • 方式2:Interceptor.attach(拦截函数调用)
        • 5.1.3 两种语法的核心区别
      • 5.2 思路2:Hook libc.so的memchr函数,过滤特征字符
        • 5.2.1 原理:IDA反编译后的特征匹配逻辑
        • 5.2.2 实现:replace与attach两种语法的绕过脚本
          • 方式1:Interceptor.replace(替换函数实现)
          • 方式2:Interceptor.attach(拦截函数调用)
        • 5.2.3 两种语法的核心区别
      • 5.3 关键问题:为什么Hook libc.so对C++代码有效?
      • 5.4 绕过的核心步骤
    • 6. 章节总结
      • 6.1 核心知识点梳理
      • 6.2 扩展思考

⚠️本博文所涉安全渗透测试技术、方法及案例,仅用于网络安全技术研究与合规性交流,旨在提升读者的安全防护意识与技术能力。任何个人或组织在使用相关内容前,必须获得目标网络 / 系统所有者的明确且书面授权,严禁用于未经授权的网络探测、漏洞利用、数据获取等非法行为。

1. 前言:为什么企业要检测Frida

1.1 Frida的安全威胁:企业检测的核心动因

Frida是一款跨平台的动态插桩工具,能够在不修改目标程序源码、不重新编译的情况下,动态注入脚本拦截函数调用、修改内存数据、篡改业务逻辑。对于企业而言,Frida的滥用会带来以下风险:

  • 核心业务逻辑被破解:如金融APP的支付校验、加密算法被逆向,付费软件的授权验证被绕过;
  • 敏感数据被窃取:如APP中的用户令牌、加密密钥、隐私数据被Hook获取;
  • 业务流程被篡改:如电商APP的订单金额、风控校验被恶意修改;
  • 企业内部应用被渗透:企业私有化部署的应用被逆向,导致内部数据泄露。

因此,检测并阻断Frida的注入行为,是企业保障应用安全的重要防线。

1.2 Frida检测的典型应用场景

Frida检测逻辑通常被嵌入在对安全性要求高的应用中,常见场景包括:

  • 金融类应用:银行APP、支付APP、证券APP等,涉及用户资金安全;
  • 付费/版权类应用:会员制软件、付费内容APP、游戏等,防止破解与盗版;
  • 企业级应用:企业内部管理系统、私有化部署的业务应用,防止内部数据泄露;
  • 安全防护类应用:杀毒软件、安全加固工具,防止被逆向分析。

1.3 学习Frida绕过的意义

对于安全研究者、渗透测试工程师、逆向分析人员而言,学习Frida绕过并非为了恶意攻击,而是:

  • 合规的安全评估:在获得企业授权后,对应用进行安全测试,验证防护机制的有效性;
  • 理解攻防对抗本质:通过分析检测逻辑与绕过方法,掌握移动安全的核心攻防思路;
  • 提升逆向工程能力:从JNI调用、SO层逻辑、系统函数调用等维度,深化对Android底层的理解。

2. 检测原理:基于/proc/self/maps的Frida特征识别

2.1 proc/self/maps的核心作用

/proc/self/maps是Linux/Android系统中的虚拟文件,用于记录当前进程的内存映射信息,包括:

  • 内存区域的地址范围、访问权限;
  • 映射的文件路径(如加载的SO库、配置文件);
  • 内存区域的所属进程与权限属性。

该文件是系统提供的进程内存快照,任何进程都可以读取自身的/proc/self/maps文件。

2.2 基于/proc/self/maps的Frida检测逻辑

当Frida注入目标进程时,可能会在进程中加载frida-agent.solibfrida-core.solibfrida.so等相关模块,这些模块的路径与名称会被写入/proc/self/maps文件中。

企业应用的检测逻辑正是利用这一特征:读取/proc/self/maps文件,逐行查找是否包含“frida”相关关键词,若存在则判定为Frida注入

3. APK的C++检测实现:代码逻辑与人工验证

本章节使用的示例 APK、相关源码如下:
链接: https://pan.baidu.com/s/1Gr0RSnS2DAQ3YeB_FE-DPQ?pwd=vdn7
提取码: vdn7

3.1 C++核心检测代码(JNI实现)

示例APK将检测逻辑下沉到C++层(SO文件),通过JNI供Java层调用,核心代码如下:

#include <jni.h> #include <string> #include <fstream> #include <sstream> static bool checkFrida() { // 打开当前进程的/proc/self/maps文件 std::ifstream mapsFile("/proc/self/maps"); if (!mapsFile.is_open()) { return false; } std::string line; // 逐行读取文件内容 while (std::getline(mapsFile, line)) { // 检测行内容中是否包含Frida相关特征 if (line.find("frida") != std::string::npos || line.find("libfrida") != std::string::npos || line.find("frida-agent") != std::string::npos) { mapsFile.close(); return true; // 检测到Frida,返回true } } mapsFile.close(); return false; } // JNI接口方法:供Java层调用,返回检测结果 extern "C" JNIEXPORT jstring JNICALL Java_com_example_securitycheck_MainActivity_checkSecurity(JNIEnv *env, jobject thiz) { bool isDetected = checkFrida(); if (isDetected) { return env->NewStringUTF("检测到使用frida"); } else { return env->NewStringUTF("未检测到frida"); } }
代码逻辑梳理

上述代码分为两个核心部分:

  1. checkFrida函数:通过C++的std::ifstream读取/proc/self/maps,逐行匹配fridalibfridafrida-agent特征字符串;
  2. JNI接口方法:将checkFrida的布尔结果转换为字符串,返回给Java层展示。

3.2 人工验证Frida注入与检测效果

通过ADB命令可手动验证/proc/self/maps中的Frida特征,步骤如下:

  1. 注入 Frida 脚本后,启动目标应用,获取进程ID
    打开终端执行以下命令,通过pidof获取应用进程的PID(以示例应用com.example.securitycheck为例):

    adb shellsupidof com.example.securitycheck# 输出进程ID,例如4081
  2. 查看进程的内存映射文件,筛选Frida特征
    执行以下命令,读取/proc/[PID]/maps并过滤包含“frida”的行:

    cat/proc/4081/maps|grepfrida
  3. 验证结果
    若命令输出包含“frida”等关键词,说明Frida已成功注入,此时点击应用中的检测按钮,会显示“检测到使用frida”。


测试时可以先如下图操作(示例应用的包名是com.example.securitycheck),先注释掉绕过检测的方法。

4. Hook基础分析:通过JADX反编译定位检测逻辑

要实现绕过,首先需要通过逆向工具定位检测逻辑的调用链,这里使用JADX反编译APK进行分析。

4.1 步骤1:JADX反编译APK,定位JNI调用方法

将APK文件拖入JADX后,在com.example.securitycheck.MainActivity类中,发现checkSecurity方法被声明为native方法(如图所示),说明该方法通过JNI调用C++层的检测逻辑。

4.2 步骤2:梳理检测结果的展示流程

MainLayout的布局逻辑中,按钮的点击事件会触发checkSecurity方法的调用,并将返回的字符串结果显示在文本控件中。这意味着:只要篡改checkSecurity的底层依赖逻辑,使其返回“未检测到frida”,即可完成绕过

4.3 核心结论:绕过的关键切入点

C++层的检测逻辑依赖两个核心步骤:读取/proc/self/maps文件、匹配特征字符串。因此,我们可以通过Hook这两个步骤的底层函数,阻断检测逻辑的执行。

5. 两种Hook绕过思路详解

本节将详细讲解Hook fopen(重定向文件读取)和Hook memchr(过滤特征字符)两种绕过思路,并对比Interceptor.replaceInterceptor.attach两种语法的区别与适用场景。

5.1 思路1:Hook libc.so的fopen函数,重定向文件读取

5.1.1 原理:C++文件操作的底层依赖

C++的std::ifstream是高层的文件流操作类,其底层最终会调用libc.so(C标准库)的fopen函数完成文件打开操作,调用链如下:

上层C++代码:std::ifstream("/proc/self/maps")↓ libc++的std::filebuf::open()(C++文件缓冲区的核心方法) ↓ libc的fopen()(Android的C库fopen实现) ↓ Linux系统调用open()(最终触发内核打开文件)

因此,Hook libc.so的fopen函数,可拦截应用对/proc/self/maps的读取请求,将其重定向到空文件(如/dev/null),使检测逻辑读取不到任何内容,从而绕过检测。

5.1.2 实现:replace与attach两种语法的绕过脚本
方式1:Interceptor.replace(替换函数实现)
importJavafrom"frida-java-bridge";functionbypassFopen(){try{constlibc=Module.load('libc.so');constfopenPtr=libc.getExportByName('fopen');if(!fopenPtr){console.log('[!] 未找到fopen符号');returnfalse;}constfopen=newNativeFunction(fopenPtr,'pointer',['pointer','pointer']);Interceptor.replace(fopen,newNativeCallback((pathPtr,modePtr)=>{constpath=pathPtr.readCString();if(path?.includes('/proc/self/maps')){console.log('Redirecting /proc/self/maps to /dev/null');returnfopen(Memory.allocUtf8String("/dev/null"),modePtr);}returnfopen(pathPtr,modePtr);},'pointer',['pointer','pointer']));console.log('Frida detection bypass applied.');returntrue;}catch(error){console.error("Bypass执行出错:",error.message);returnfalse;}}Java.perform(()=>{try{bypassFopen()console.log(111);}catch(error){console.error("Hook执行出错:",error.message);}});
方式2:Interceptor.attach(拦截函数调用)
importJavafrom"frida-java-bridge";functionbypassFopen2(){try{constlibc=Module.load('libc.so');constfopenPtr=libc.getExportByName('fopen');if(!fopenPtr){console.log('[!] 未找到fopen符号');returnfalse;}Interceptor.attach(fopenPtr,{onEnter:function(args){this.path=args[0].readCString();this.shouldRedirect=this.path?.includes('/proc/self/maps');if(this.shouldRedirect){console.log('Redirecting /proc/self/maps to /dev/null');// 修改参数指向 /dev/nullargs[0]=Memory.allocUtf8String("/dev/null");}}});console.log('Frida detection bypass with attach applied.');returntrue;}catch(error){console.error("Bypass执行出错:",error.message);returnfalse;}}Java.perform(()=>{try{bypassFopen2()console.log(111);}catch(error){console.error("Hook执行出错:",error.message);}});
5.1.3 两种语法的核心区别
语法类型核心逻辑优势适用场景
Interceptor.replace完全替换原函数的实现,需手动调用原始函数处理非目标场景可完全接管函数逻辑,自定义程度高需要修改函数返回值或逻辑的场景
Interceptor.attach拦截函数的进入/退出阶段,仅修改参数或返回值,保留原函数的完整逻辑逻辑更简洁,副作用小(不破坏原函数)仅需修改参数或返回值的简单场景

为什么两种方式都能绕过?
无论是替换函数实现(replace)还是拦截调用修改参数(attach),最终都达成了同一个目标:让应用读取不到/proc/self/maps的真实内容,效果如图所示。

5.2 思路2:Hook libc.so的memchr函数,过滤特征字符

5.2.1 原理:IDA反编译后的特征匹配逻辑

通过IDA反编译目标SO文件(libsecuritycheck.so),发现C++层的特征匹配逻辑最终依赖libc.so的memchr函数(如图所示)。memchr的作用是在指定内存缓冲区中查找指定字符,原型为:

void*memchr(constvoid*s,intc,size_tn);// s:缓冲区;c:目标字符;n:缓冲区长度

检测逻辑通过memchr查找'f'(ASCII码102),再验证后续字符是否为“rida”,从而匹配“frida”特征。因此,Hookmemchr函数,使其在找到Frida相关特征时返回NULL,即可阻断匹配逻辑。


5.2.2 实现:replace与attach两种语法的绕过脚本
方式1:Interceptor.replace(替换函数实现)
importJavafrom"frida-java-bridge";functionbypassMemchr(){try{constlibc=Module.load('libc.so');constmemchrPtr=libc.getExportByName('memchr');if(!memchrPtr){console.log('[!] 未找到memchr符号');returnfalse;}Interceptor.replace(memchrPtr,newNativeCallback((s,c,n)=>{constoriginalMemchr=newNativeFunction(memchrPtr,'pointer',['pointer','int','int']);constresult=originalMemchr(s,c,n);if(result!==NULL&&n>4){constcontent=s.readCString(n);if(content&&(content.includes('frida')||content.includes('libfrida')||content.includes('frida-agent'))){// 拦截并返回NULLreturnNULL;}}returnresult;},'pointer',['pointer','int','int']));console.log('memchr bypass applied.');returntrue;}catch(error){console.error("memchr bypass error: ",error.message);returnfalse;}}Java.perform(()=>{try{bypassMemchr()console.log(111);}catch(error){console.error("Hook执行出错:",error.message);}});
方式2:Interceptor.attach(拦截函数调用)
importJavafrom"frida-java-bridge";functionbypassMemchr2(){try{constlibc=Module.load('libc.so');constmemchrPtr=libc.getExportByName('memchr');if(!memchrPtr){console.log('[!] 未找到memchr符号');returnfalse;}Interceptor.attach(memchrPtr,{onEnter:function(args){// 原始参数this.buffer=args[0];// 搜索缓冲区this.searchChar=args[1];// 搜索字符this.bufferSize=args[2];// 缓冲区大小},onLeave:function(retval){if(retval!==NULL){// 从缓冲区起始位置读取内容进行检查constcontent=this.buffer.readCString(this.bufferSize.toInt32());if(content?.includes('frida')){// 隐藏frida相关结果retval.replace(NULL);}}}});console.log('memchr attach bypass applied.');returntrue;}catch(error){console.error("memchr attach bypass error: ",error.message);returnfalse;}}Java.perform(()=>{try{bypassMemchr2()console.log(111);}catch(error){console.error("Hook执行出错:",error.message);}});
5.2.3 两种语法的核心区别

与Hook fopen的逻辑一致,两种语法最终都达成了memchr无法返回Frida特征的查找结果的目标,因此都能成功绕过检测,效果如图所示。

5.3 关键问题:为什么Hook libc.so对C++代码有效?

很多读者会疑惑:检测代码是C++编写的,为什么Hook C标准库(libc.so)的函数能生效?核心原因有两点:

  1. C++标准库的底层依赖:C++的高层封装(如std::ifstreamstd::string::find)并非完全独立实现,而是依赖libc.so提供的底层系统调用(如fopenmemchrread)。也就是说,C++是“上层封装”,libc.so是“底层支撑”。
  2. IDA中的函数归属:通过IDA看到的memchrfopen等函数,本质上是libc.so导出的函数,C++代码只是调用了这些函数,因此Hook libc.so的函数能直接拦截到C++层的调用。

这也是为什么即使检测逻辑是纯C++编写,Hook libc.so的核心函数依然是最有效的绕过手段

5.4 绕过的核心步骤

无论采用哪种Hook思路,核心步骤都可总结为:

  1. 分析检测逻辑的底层依赖:确定检测代码依赖的核心函数(如fopenmemchr);
  2. 定位函数的所属库:找到函数所在的库(如libc.so)及导出符号;
  3. 选择Hook语法:根据需求选择replace(自定义逻辑)或attach(简单修改);
  4. 篡改函数的输入/输出:要么修改参数(如重定向文件路径),要么修改返回值(如返回NULL),阻断检测逻辑。

6. 章节总结

6.1 核心知识点梳理

维度具体内容
检测方法读取/proc/self/maps文件,匹配“frida”“libfrida”等特征字符串,判断是否存在Frida注入
分析方法1. 用JADX反编译APK,定位JNI调用的native方法;
2. 用IDA反编译SO文件,还原底层检测逻辑(依赖的核心函数)
绕过方法1. Hookfopen函数,重定向/proc/self/maps的读取请求;
2. Hookmemchr函数,过滤Frida特征的查找结果

6.2 扩展思考

实际应用中的Frida检测逻辑可能更加多样化,例如:

  • 检测代码为纯C编写:直接调用fopenfgetsstrstrstrcmp等函数,而非C++的流操作;
  • 特征匹配更隐蔽:使用十六进制特征匹配、多字符组合校验,而非直接匹配字符串。

面对这类型场景,核心原则不变:先通过逆向工具找到检测逻辑的核心依赖点(如strstropenread等函数),再根据依赖点选择对应的Hook目标,通过修改输入/输出实现绕过。学会分析思路,而非死记脚本,才能应对各种复杂的检测场景。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/23 15:07:14

Claude Skills 深度解析:从 What、Why、How 构建领域专用 AI 能力

一、What&#xff1a;Claude Skills 是什么&#xff1f;Claude Skills 是 Anthropic 推出的领域专用知识包&#xff0c;本质是包含 SKILL.md 文件的目录结构&#xff0c;用于将程序性知识&#xff08;如操作流程、行业规范、工具使用方法&#xff09;打包成 AI 可复用的资源。它…

作者头像 李华
网站建设 2026/6/23 13:03:59

网站被黑后的紧急处理恢复正常步骤是什么?

随着时代的发展网站使用的频率是逐步增加。然而&#xff0c;随着互联网技术的不断进步&#xff0c;网站安全问题也引起了广泛关注。其中&#xff0c;最严重的问题是网站被黑客攻击。那么&#xff0c;何谓网站被黑&#xff1f;它可能会给企业和用户带来哪些风险&#xff1f;一、…

作者头像 李华
网站建设 2026/6/23 8:24:01

30、Linux 打印系统全解析

Linux 打印系统全解析 1. 设备管理基础 在 PC 上可连接的设备数量众多,要全面涵盖所有设备的相关知识几乎是不可能的。不过,掌握 SCSI、USB、PCMCIA 和 IEEE 1394 等设备的相关知识,再加上对打印机、以太网接口等的了解,就足以应对大多数情况。例如,识别 USB 扫描仪后,…

作者头像 李华
网站建设 2026/6/23 19:06:22

MYSQL的学习

mysql的分类mysql在其内部根据功能的不同分为不同的语言一、数据查询语言&#xff08;DQL, Data Query Language&#xff09;核心作用&#xff1a;从数据库中查询 / 检索数据&#xff08;仅读取&#xff0c;不修改数据&#xff09;&#xff0c;是最常用的 SQL 类别。核心关键字…

作者头像 李华
网站建设 2026/6/23 16:24:59

8、延迟执行与虚拟零调制解调器驱动解析

延迟执行与虚拟零调制解调器驱动解析 在系统编程中,延迟执行是一个常见的需求,它可以帮助我们更好地管理资源和处理事件。本文将介绍几种延迟执行的方法,并通过虚拟零调制解调器驱动 nmdm(4) 的案例来深入理解这些方法的应用。 1. 延迟执行的基本概念与相关函数 在某些…

作者头像 李华
网站建设 2026/6/12 9:07:43

7、Linux 网络安全与防火墙配置全解析

Linux 网络安全与防火墙配置全解析 1. 网络安全威胁概述 在网络环境中,不同的 OSI 层面临着各种各样的安全威胁。以下是各层常见的攻击类型: - 第 1 层攻击 :主要是电缆切断,Linux 对此类攻击无法有效应对。 - 第 2 层攻击 :这类攻击具有很强的破坏性,因为它们会…

作者头像 李华