当前位置: 首页 > 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.dtcms.com/a/348035.html

相关文章:

  • 第4章栈和队列:顺序队——基本结构
  • Java 基础学习总结(211)—— Apache Commons ValidationUtils:让参数校验从 “体力活“ 变 “优雅事“
  • Vue状态管理工具pinia的使用以及Vue组件通讯
  • 一个byte表示多个bool属性的功能
  • 高并发AI服务部署方案:vLLM、TGI、FastChat性能压测报告
  • CSS 进阶用法
  • Read View是实现MVCC的三大前提之一,那么它是在什么时候建立的
  • frida安装配置及其使用方法
  • 一个奇怪的问题-Python会替代Java吗?技术语言之争的真相-优雅草卓伊凡
  • Java 大视界 -- Java 大数据在智能交通智能公交系统中的乘客流量预测与车辆调度优化
  • 程序里的依赖和中间件的依赖冲突,怎么解决
  • Docling:一个基于AI驱动的免费文档解析工具
  • Python性能优化实战(二):让循环跑得比博尔特还快
  • [身份验证脚手架] 应用布局如何构建
  • 初学python的我开始Leetcode题-16
  • CTFshow系列——命令执行web49-52
  • assert使用方法
  • Redis 高可用开发指南
  • 力扣594:最和谐子序列
  • 客流特征识别误报率↓76%!陌讯多模态时序融合算法在智慧零售的实战解析
  • Tesla智能座舱域控制器(MCU)的系统化梳理
  • 【网络运维】Shell 脚本编程:if 条件语句
  • 【40页PPT】数字工厂一体化运营管控平台解决方案(附下载方式)
  • Spark04-MLib library01-机器学习的介绍
  • SNMP 协议的总结
  • 每日算法题【链表】:相交链表、环形链表、环形链表II
  • 鸿蒙分布式计算实战:用 ArkTS+Worker 池落地可运行任务管理 Demo,从单设备到跨设备全方案
  • [二维前缀和]1277. 统计全为 1 的正方形子矩阵
  • HarmonyOS实战(DevEco AI篇)—深度体验DevEco CodeGenie智能编程助手
  • Function + 枚举 + Map:轻量路由器的最佳实践