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

文章目录

    • [1. 前言:为什么企业要检测Frida](#1. 前言:为什么企业要检测Frida)
      • [1.1 Frida的安全威胁:企业检测的核心动因](#1.1 Frida的安全威胁:企业检测的核心动因)
      • [1.2 Frida检测的典型应用场景](#1.2 Frida检测的典型应用场景)
      • [1.3 学习Frida绕过的意义](#1.3 学习Frida绕过的意义)
    • [2. 检测原理:基于/proc/self/maps的Frida特征识别](#2. 检测原理:基于/proc/self/maps的Frida特征识别)
      • [2.1 proc/self/maps的核心作用](#2.1 proc/self/maps的核心作用)
      • [2.2 基于/proc/self/maps的Frida检测逻辑](#2.2 基于/proc/self/maps的Frida检测逻辑)
    • [3. APK的C++检测实现:代码逻辑与人工验证](#3. APK的C++检测实现:代码逻辑与人工验证)
      • [3.1 C++核心检测代码(JNI实现)](#3.1 C++核心检测代码(JNI实现))
      • [3.2 人工验证Frida注入与检测效果](#3.2 人工验证Frida注入与检测效果)
    • [4. Hook基础分析:通过JADX反编译定位检测逻辑](#4. Hook基础分析:通过JADX反编译定位检测逻辑)
      • [4.1 步骤1:JADX反编译APK,定位JNI调用方法](#4.1 步骤1:JADX反编译APK,定位JNI调用方法)
      • [4.2 步骤2:梳理检测结果的展示流程](#4.2 步骤2:梳理检测结果的展示流程)
      • [4.3 核心结论:绕过的关键切入点](#4.3 核心结论:绕过的关键切入点)
    • [5. 两种Hook绕过思路详解](#5. 两种Hook绕过思路详解)
      • [5.1 思路1:Hook libc.so的fopen函数,重定向文件读取](#5.1 思路1:Hook libc.so的fopen函数,重定向文件读取)
      • [5.2 思路2:Hook libc.so的memchr函数,过滤特征字符](#5.2 思路2:Hook libc.so的memchr函数,过滤特征字符)
      • [5.3 关键问题:为什么Hook libc.so对C++代码有效?](#5.3 关键问题:为什么Hook libc.so对C++代码有效?)
      • [5.4 绕过的核心步骤](#5.4 绕过的核心步骤)
    • [6. 章节总结](#6. 章节总结)
      • [6.1 核心知识点梳理](#6.1 核心知识点梳理)
      • [6.2 扩展思考](#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层调用,核心代码如下:

c++ 复制代码
#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为例):

    shell 复制代码
    adb shell
    su
    pidof com.example.securitycheck # 输出进程ID,例如4081
  2. 查看进程的内存映射文件,筛选Frida特征

    执行以下命令,读取/proc/[PID]/maps并过滤包含"frida"的行:

    shell 复制代码
    cat /proc/4081/maps | grep frida
  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函数完成文件打开操作,调用链如下:

shell 复制代码
上层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(替换函数实现)
javascript 复制代码
import Java from "frida-java-bridge";

function bypassFopen() {
  try {
    const libc = Module.load('libc.so');
    const fopenPtr = libc.getExportByName('fopen');

    if (!fopenPtr) {
      console.log('[!] 未找到fopen符号');
      return false;
    }

    const fopen = new NativeFunction(fopenPtr, 'pointer', ['pointer', 'pointer']);
    Interceptor.replace(fopen, new NativeCallback((pathPtr, modePtr) => {
      const path = pathPtr.readCString();

      if (path?.includes('/proc/self/maps')) {
        console.log('Redirecting /proc/self/maps to /dev/null');
        return fopen(Memory.allocUtf8String("/dev/null"), modePtr);
      }

      return fopen(pathPtr, modePtr);
    }, 'pointer', ['pointer', 'pointer']));

    console.log('Frida detection bypass applied.');
    return true;
  } catch (error) {
    console.error("Bypass执行出错:", error.message);
    return false;
  }
}


Java.perform(() => {
  try {
    bypassFopen()
    console.log(111);
  } catch (error) {
    console.error("Hook执行出错:", error.message);
  }
});
方式2:Interceptor.attach(拦截函数调用)
javascript 复制代码
import Java from "frida-java-bridge";

function bypassFopen2() {
  try {
    const libc = Module.load('libc.so');
    const fopenPtr = libc.getExportByName('fopen');

    if (!fopenPtr) {
      console.log('[!] 未找到fopen符号');
      return false;
    }

    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/null
          args[0] = Memory.allocUtf8String("/dev/null");
        }
      }
    });

    console.log('Frida detection bypass with attach applied.');
    return true;
  } catch (error) {
    console.error("Bypass执行出错:", error.message);
    return false;
  }
}



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的作用是在指定内存缓冲区中查找指定字符,原型为:

c 复制代码
void *memchr(const void *s, int c, size_t n); // s:缓冲区;c:目标字符;n:缓冲区长度

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


5.2.2 实现:replace与attach两种语法的绕过脚本
方式1:Interceptor.replace(替换函数实现)
javascript 复制代码
import Java from "frida-java-bridge";

function bypassMemchr() {
  try {
    const libc = Module.load('libc.so');
    const memchrPtr = libc.getExportByName('memchr');

    if (!memchrPtr) {
      console.log('[!] 未找到memchr符号');
      return false;
    }

    Interceptor.replace(memchrPtr, new NativeCallback((s, c, n) => {
      const originalMemchr = new NativeFunction(memchrPtr, 'pointer', ['pointer', 'int', 'int']);
      const result = originalMemchr(s, c, n);

      if (result !== NULL && n > 4) {
        const content = s.readCString(n);
        if (content &&
          (content.includes('frida') ||
            content.includes('libfrida') ||
            content.includes('frida-agent'))) {
          // 拦截并返回NULL
          return NULL;
        }
      }

      return result;
    }, 'pointer', ['pointer', 'int', 'int']));

    console.log('memchr bypass applied.');
    return true;
  } catch (error) {
    console.error("memchr bypass error: ", error.message);
    return false;
  }
}

Java.perform(() => {
  try {
    bypassMemchr()
    console.log(111);
  } catch (error) {
    console.error("Hook执行出错:", error.message);
  }
});
方式2:Interceptor.attach(拦截函数调用)
javascript 复制代码
import Java from "frida-java-bridge";

function bypassMemchr2() {
  try {
    const libc = Module.load('libc.so');
    const memchrPtr = libc.getExportByName('memchr');

    if (!memchrPtr) {
      console.log('[!] 未找到memchr符号');
      return false;
    }

    Interceptor.attach(memchrPtr, {
      onEnter: function (args) {
        // 原始参数
        this.buffer = args[0];     // 搜索缓冲区
        this.searchChar = args[1]; // 搜索字符
        this.bufferSize = args[2]; // 缓冲区大小
      },
      onLeave: function (retval) {
        if (retval !== NULL) {
          // 从缓冲区起始位置读取内容进行检查
          const content = this.buffer.readCString(this.bufferSize.toInt32());
          if (content?.includes('frida')) {
            // 隐藏frida相关结果
            retval.replace(NULL);
          }
        }
      }
    });

    console.log('memchr attach bypass applied.');
    return true;
  } catch (error) {
    console.error("memchr attach bypass error: ", error.message);
    return false;
  }
}

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目标,通过修改输入/输出实现绕过。学会分析思路,而非死记脚本,才能应对各种复杂的检测场景。

相关推荐
hhy_smile5 小时前
Android 与 java 设计笔记
android·java·笔记
laocooon5238578866 小时前
C#二次开发中简单块的定义与应用
android·数据库·c#
似霰6 小时前
传统 Hal 开发笔记5 —— 添加硬件访问服务
android·framework·hal
恋猫de小郭6 小时前
Android 宣布 Runtime 编译速度史诗级提升:在编译时间上优化了 18%
android·前端·flutter
小白勇闯网安圈6 小时前
upload、very_easy_sql、i-got-id-200
python·网络安全·web
csj507 小时前
安卓基础之《(4)—Activity组件》
android
logic_57 小时前
DHCP+DNS
网络安全·udp·信号处理
游戏开发爱好者87 小时前
H5 混合应用加密 Web 资源暴露到 IPA 层防护的完整技术方案
android·前端·ios·小程序·uni-app·iphone·webview
爱浦路 IPLOOK7 小时前
高校5G实验室助力人才培养的五种创新模式
计算机网络·5g·网络安全·可信计算技术