当前位置: 首页 > news >正文

使用 Frida 运行时检测 Android 应用的真实权限状态 (App Ops)

使用 Frida 运行时检测 Android 应用的真实权限状态 (App Ops)

在 Android 安全分析和应用逆向工程中,了解一个应用拥有哪些权限至关重要。我们通常会首先查看 AndroidManifest.xml 文件来确定应用请求了哪些权限。然而,这仅仅是“静态”的声明。在现代 Android 系统中,用户可以在运行时授予、拒绝或限制权限,这使得静态分析的结果并不可靠。

应用可能请求了录音权限,但用户可能已经通过系统设置禁用了它。在这种情况下,应用虽然“声明”了该权限,但在运行时调用相关 API 时会失败。如何才能准确地知道应用在运行时某一时刻,是否真的被允许执行某个敏感操作呢?

答案是使用 Frida 结合 Android 的 AppOpsManager。本文将介绍 AppOpsManager 机制,并详细解析一个 Frida 脚本,该脚本可以实时检测目标应用是否真正获得了如录-音、访问位置等敏感操作的许可。

什么是 App Ops (App Operations)?

AppOpsManager 是 Android 4.3 (API 18) 引入的一套精细化的权限管理框架。它在标准的权限模型之上,提供了一个更细粒度的控制层。即使用户授予了某个权限(例如 android.permission.RECORD_AUDIO),系统或用户仍然可以通过 App Ops 来禁止或“忽略”这个操作。

简单来说:

  • 标准权限 (Standard Permissions):在 AndroidManifest.xml 中声明,用户在安装或运行时通过弹窗授予。决定了应用有没有资格执行某个操作。
  • 应用操作 (App Ops):在系统层面进行跟踪和控制。决定了应用在执行这个操作时是否被真正允许

很多国产手机的“权限管家”功能,比如返回空数据、拒绝后不再提醒等,底层就是基于 App Ops 机制实现的。因此,通过监控 App Ops,我们可以得到比标准权限检查更真实的结果。

Frida 脚本解析

我们的目标是编写一个 Frida 脚本,在附加到目标应用进程后,立即检查一系列我们关心的敏感操作权限的当前状态。

完整的 Frida 脚本 (check_op.js)
var Context;
var AppOpsManager;
var AndroidProcess;
var ActivityThread;
var context;
var app;
var appOps;var op_check_list;function use_classes() {AppOpsManager = Java.use("android.app.AppOpsManager");Context = Java.use("android.content.Context");AndroidProcess = Java.use("android.os.Process");ActivityThread = Java.use("android.app.ActivityThread");app = ActivityThread.currentApplication();context = app.getApplicationContext();appOps = context.getSystemService(Context.APP_OPS_SERVICE.value);appOps = Java.cast(appOps, AppOpsManager);op_check_list = [AppOpsManager.OPSTR_RECORD_AUDIO.value,          // 录音AppOpsManager.OPSTR_QUERY_ALL_PACKAGES.value,    // 查询所有包AppOpsManager.OPSTR_ACCESS_MEDIA_LOCATION.value, // 访问媒体文件位置// 你可以在这里添加更多关心的权限// AppOpsManager.OPSTR_COARSE_LOCATION.value,    // 访问粗略位置// AppOpsManager.OPSTR_FINE_LOCATION.value,      // 访问精确位置];}function checkOp() {for (let opstr of op_check_list){var mode = appOps.checkOp(opstr,AndroidProcess.myUid(),context.getPackageName());console.log("OPSTR: " + opstr + "\tmode = " + mode + " (" + mode_to_string(mode) + ")");}
};// 辅助函数,用于将 mode 转换为可读字符串
function mode_to_string(mode) {if (mode == AppOpsManager.MODE_ALLOWED) {return "ALLOWED";}if (mode == AppOpsManager.MODE_IGNORED) {return "IGNORED/DENIED";}if (mode == AppOpsManager.MODE_ERRORED) {return "ERRORED";}if (mode == AppOpsManager.MODE_DEFAULT) {return "DEFAULT";}return "UNKNOWN";
}Java.perform(() => {console.log("--- Starting AppOps Check ---");use_classes();checkOp();console.log("--- AppOps Check Finished ---");}
)
脚本逻辑分解
  1. use_classes() 函数:初始化与准备

    • Java.use(...): 这是 Frida 的核心功能,用于获取 Java 类的引用,以便我们可以在 JavaScript 中调用它们的静态方法或实例化它们。
    • ActivityThread.currentApplication(): 一个非常实用的技巧,用于获取当前应用的 Application 对象。
    • app.getApplicationContext(): 通过 Application 对象获取全局的 Context(上下文)。Context 是访问 Android 系统服务的入口。
    • context.getSystemService(...): 通过 Context 获取 AppOpsManager 系统服务。
    • Java.cast(...): 因为 getSystemService 返回的是一个通用的 Object,我们需要使用 Java.cast 将它转换为具体的 AppOpsManager 类型,这样 Frida 才能识别它拥有的方法(如 checkOp)。
    • op_check_list: 我们定义一个数组,存放所有我们想要检查的权限操作字符串。这些字符串都定义在 AppOpsManager 的常量中,例如 OPSTR_RECORD_AUDIO 对应的值是 “android:record_audio”。
  2. checkOp() 函数:核心检测逻辑

    • 该函数遍历 op_check_list 中的每一个权限字符串。
    • 关键调用是 appOps.checkOp(opstr, uid, packageName) 方法。
      • 第一个参数 opstr: 要检查的操作字符串,如 "android:record_audio"
      • 第二个参数 uid: 操作发起方的 User ID。我们使用 android.os.Process.myUid() 来获取当前进程的 UID。
      • 第三个参数 packageName: 操作发起方的包名。我们使用 context.getPackageName() 来获取当前应用的包名。
    • 该方法会返回一个整数 mode,这个 mode 值代表了该操作的真实权限状态。
  3. mode 值的含义
    checkOp 的返回值是理解脚本输出的关键。它主要有以下几种:

    • AppOpsManager.MODE_ALLOWED (值为 0): 允许。应用可以执行此操作。
    • AppOpsManager.MODE_IGNORED (值为 1): 忽略/拒绝。应用不允许执行此操作,并且调用相关 API 时系统会静默失败或返回空数据,不会导致应用崩溃。这是最常见的“拒绝”状态。
    • AppOpsManager.MODE_ERRORED (值为 2): 错误。应用不允许执行此操作,并且调用会直接抛出 SecurityException 异常。
    • AppOpsManager.MODE_DEFAULT (值为 3): 默认。系统将根据默认规则来决定是否允许。通常会最终解析为 ALLOWEDIGNORED

    为了方便阅读,我在脚本中增加了一个 mode_to_string 的辅助函数,可以将这些整数值转换成可读的字符串。

如何使用该脚本

使用这个脚本非常简单,只需要两个文件和一个正在运行的目标应用。

1. 附加脚本 (run.sh)

为了方便,我们可以创建一个简单的 shell 脚本来启动 Frida 并附加到目标进程。

#!/bin/bash# 检查是否提供了参数
if [ -z "$1" ]; thenecho "用法: $0 <应用的包名或PID>"echo "例如: $0 com.example.app"echo "或: $0 12345"exit 1
fi# 附加到进程并加载JS脚本
# -U: 连接到USB设备
# -f: 启动并附加到指定的包名 (如果提供的是包名)
# -p: 附加到指定的PID (如果提供的是数字)
# -l: 加载脚本文件# 判断输入是包名还是PID
if [[ $1 =~ ^[0-9]+$ ]]; thenecho "正在附加到 PID: $1..."frida -U -p $1 -l check_op.js
elseecho "正在启动并附加到包名: $1..."frida -U -f $1 -l check_op.js --no-pause
fi
  • 将上面的 Frida 代码保存为 check_op.js
  • 将上面的 shell 脚本保存为 run.sh
  • 赋予执行权限: chmod +x run.sh
2. 执行检测

现在,假设你想检测 com.google.android.apps.messaging 这个应用的权限:

  1. 在你的电脑上打开一个终端。
  2. 运行脚本: ./run.sh com.google.android.apps.messaging

Frida 会自动启动该应用(如果尚未运行),注入脚本,然后你将看到类似下面的输出:

--- Starting AppOps Check ---
OPSTR: android:record_audio	mode = 1 (IGNORED/DENIED)
OPSTR: android:query_all_packages	mode = 0 (ALLOWED)
OPSTR: android:access_media_location	mode = 1 (IGNORED/DENIED)
--- AppOps Check Finished ---

这个输出清晰地告诉我们:

  • 该应用在当前运行时不被允许录音。
  • 被允许查询设备上安装的所有应用包。
  • 不被允许访问媒体文件的地理位置信息。

拓展与思考

这个脚本是一个“一次性”的快照,它展示了脚本运行时那一刻的权限状态。我们可以基于此进行更多有趣的探索:

  • 持续监控:通过 Hook (钩子) 关键的 API (例如 android.media.MediaRecorder.start),在 API 被调用前执行 checkOp,可以动态地观察权限状态的变化。
  • 绕过检测:在更高级的攻防场景中,可以 Hook AppOpsManager.checkOp 方法本身,修改其返回值,从而欺骗应用让它以为自己拥有某项权限。
  • 权限审计:将 op_check_list 扩展到包含所有敏感的 OPSTR_ 常量,可以对一个应用进行全面的运行时权限审计,检查它是否有多余的、未被使用的授权。

总结

通过 Frida 与 AppOpsManager 的结合,我们能够穿透 Android 标准权限模型的表象,洞悉应用在运行时的真实行为许可。这不仅为安全研究人员和逆向工程师提供了一个强大的分析工具,也让我们对 Android 权限系统的复杂性和精妙性有了更深的理解。下次当你分析一个行为可疑的应用时,不妨用这个脚本来验证一下它的“真实面目”。

http://www.xdnf.cn/news/1360909.html

相关文章:

  • 强逆光干扰漏检率↓78%!陌讯多模态融合算法在光伏巡检的实战优化
  • Java全栈开发面试实战:从基础到高并发场景的深度解析
  • Python性能优化实战(二):让循环跑得比博尔特还快
  • 27.编程思想
  • 【golang长途旅行第30站】channel管道------解决线程竞争的好手
  • Teams Bot机器人实时语音识别的多引擎的处理
  • TCP--执行Linux命令(虚拟xshell)
  • 数据建模怎么做?一文讲清数据建模全流程
  • 一、基因组选择(GS)与基因组预测(GP)
  • 网络安全转型书籍清单
  • 【Java开发日记】我们来讲一讲 Channel 和 FileChannel
  • 深度学习之第一课深度学习的入门
  • VirtualBox安装openEuler24.03
  • daily notes[5]
  • 前端 vs 后端请求:核心差异与实战对比
  • 05 线性代数【动手学深度学习v2】
  • 中介者模式与几个C++应用实例
  • imx6ull-驱动开发篇39——Linux INPUT 子系统实验
  • 【基础算法】初识搜索:递归型枚举与回溯剪枝
  • 【ElasticSearch】springboot整合es案例
  • Smooze Pro for mac 鼠标手势增强软件
  • 【C语言练习】青蛙跳台阶
  • Vue状态管理工具pinia的使用以及Vue组件通讯
  • 强光干扰下检出率↑93%!陌讯多模态融合算法在充电桩车位占用检测的实战解析
  • 力扣【1277. 统计全为1的正方形子矩阵】——从暴力到最优的思考过程
  • 【网络运维】Shell脚本编程:函数
  • 深度学习之第二课PyTorch与CUDA的安装
  • AOSP构建指南:从零开始的Android源码之旅
  • Docker 容器(一)
  • 【Docker基础】Docker-compose常用命令实践(三):镜像与配置管理