news 2026/4/16 14:33:19

【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/4/16 14:04:43

好险!泰国最窄处仅13公里,“腰子”差点被缅甸嘎了!

在地图上泰国的版图&#xff0c;会发现一处颇为有趣的地理特征&#xff1a;其南部延伸出一条狭长地带&#xff0c;最窄处仅 13 公里。 而就在这“腰子”&#xff08;暂且称之为“腰子”&#xff0c;也有人说是“象鼻”&#xff09;之上&#xff0c;缅甸的领土如一把尖刀直插而…

作者头像 李华
网站建设 2026/4/2 3:02:26

DM8数据库与MySQL语法兼容性解析与实践指南

本文旨在系统性地整合与验证DM8在数据类型、SQL语法、内置函数及存储程序语言等多个层面与MySQL的兼容性&#xff0c;并提供清晰的迁移转换策略与实践指南。一、 兼容性总体策略与核心机制DM8实现MySQL兼容性的核心设计哲学是“语义等价&#xff0c;形式适配”。其并非完全照搬…

作者头像 李华
网站建设 2026/4/14 21:44:07

25、Linux 软件编译、安装与问题排查全解析

Linux 软件编译、安装与问题排查全解析 1. 安装实践 在 Linux 系统中,了解如何构建和安装软件固然重要,但知道何时以及在何处安装自己的软件包才是真正实用的技能。Linux 发行版在安装时通常会尽可能多地预装软件,但我们需要思考是否应该自行安装某些软件包。 1.1 自行安…

作者头像 李华
网站建设 2026/4/8 19:12:43

27、Linux内核安装、配置与测试全解析

Linux内核安装、配置与测试全解析 1. 引导加载程序与内核安装基础 引导加载程序的主要作用是将内核镜像加载到内存中,并把CPU的控制权交给新加载的内核。要让新内核正常工作,就必须让引导加载程序知晓内核的相关信息。 在了解Linux系统的引导加载程序之前,先看看普通基于…

作者头像 李华
网站建设 2026/4/12 18:27:53

29、Linux 外设设备配置与使用指南

Linux 外设设备配置与使用指南 在 Linux 系统中,对外设设备的支持和管理是系统功能的重要组成部分。本文将详细介绍 USB、IEEE 1394/FireWire、PC 卡(PCMCIA)等设备在 Linux 系统中的使用和配置方法。 1. USB 设备介绍 Linux 支持各种各样的 USB(通用串行总线)设备。PC…

作者头像 李华
网站建设 2026/4/16 12:29:55

详解XSS漏洞及反射型XSS漏洞

1. XSS漏洞简介 跨站脚本攻击&#xff08;Cross-Site Scripting&#xff0c;简称XSS&#xff09;是一种常见的Web安全漏洞&#xff0c;攻击者通过注入恶意脚本代码&#xff0c;将其注入到Web页面中&#xff0c;并在访问该页面的用户浏览器中执行该脚本代码&#xff0c;从而窃取…

作者头像 李华