基于Python与Frida的Android隐私合规动态检测工具实战 📅 发布时间:2026/9/14 13:57:25 👁 浏览次数: 简介Python基于Frida的Android App隐私合规检测辅助工具源码面向移动安全测试、隐私合规审计与App自检场景帮助开发者与安全人员系统化排查敏感数据采集、传输和存储过程中的违规风险适合希望用自动化手段替代人工逆向分析的初、中级安全从业者。项目以Frida动态插桩为核心通过Python管理会话、加载脚本并汇总分析结果可实现对敏感API调用的监听、HTTP/HTTPS请求的拦截、本地数据库及SharedPreferences等存储内容的检查进而跟踪数据流走向。压缩包仅1.16MB共12个文件包含Python主脚本、Frida插桩脚本、依赖清单requirements.txt、说明文档和7张运行效果图结构清晰便于对照源码与文档同步学习。已有894人学习/下载。通过camille-master源码可以掌握Python与Frida联动时的会话建立、脚本注入与结果采集方法理解hook函数、拦截网络包、解析存储数据等实现细节也可根据自身业务定制检测规则快速搭建一套轻量的Android隐私合规检测工具。1. 为什么需要一套基于Python和Frida的Android隐私合规检测工具多数应用在上架前都要过一遍隐私合规测试但静态扫描只能看到权限声明和有没有调用特定APIApp真正在哪个业务场景读取了IMEI、什么时候把定位信息传给第三方静态扫不出来。动态检测才能还原这些行为而Frida正好能提供稳定的运行时注入能力Python又是写自动化、整理报告效率最高的语言。这个组合意味着你可以用几十行代码搭一个能抓取敏感API调用、记录调用栈、输出证据链的检测框架不需要逆向精通也能上手。本文围绕这套技术选型从Frida的原理、环境搭建到具体实现给出可以直接落地的方案。适合用这套工具的是移动开发工程师、自动化测试和合规安全团队。用Python做控制器用Frida注入JavaScript脚本再配合adb管理多台设备就能批量对目标App做动态行为采集。下面直接拆解怎么做。2. Frida的工作机制与隐私合规检测的技术路径2.1 Frida的注入模型与Python绑定方式Frida是一个动态插桩框架核心流程是在Android设备或模拟器上运行frida-server它负责与目标进程建立连接并注入一个Agent运行环境Python客户端通过USB或网络连接到frida-server发送JavaScript代码让Agent执行。JavaScript代码可以Hook任意函数在函数调用前后修改参数、返回值或者打印调用栈。在Python代码里操作Frida通常只有三个步骤import frida import sys def on_message(message, data): print(message.get(payload, message)) # 连接设备并附加到目标进程 device frida.get_usb_device() session device.attach(com.example.app) # 注入JavaScript脚本 script session.create_script( Interceptor.attach(Module.findExportByName(null, open), { onEnter: function(args) { console.log(open called); } }); ) script.on(message, on_message) script.load() sys.stdin.read()这段代码的逻辑是先通过USB获取设备再用包名附加进程。attach返回一个session之后在这个session上创建脚本。脚本里用Interceptor.attach挂到指定导出函数onEnter在函数进入时执行。Python端的on_message回调接收JavaScript里console.log推送的数据。如果附加失败先确认frida-server版本是否与Python的frida版本匹配以及设备上是否跑着root权限的frida-server。2.2 隐私合规检测需要关注的Android API与权限维度隐私合规通常关注App是否未经用户同意收集个人信息、是否超出业务必要性获取权限、是否有高频读取敏感数据的行为。下面是常见的检测对象它们都有明确的API入口。隐私数据关键系统API关联权限设备标识TelephonyManager.getDeviceId、getImei、getSubscriberIdREAD_PHONE_STATE定位LocationManager.getLastKnownLocation、requestLocationUpdatesACCESS_FINE_LOCATION通讯录ContentResolver.query(ContactsContract.Contacts...)READ_CONTACTS短信/通话记录ContentResolver.query(Telephony.Sms...)READ_SMS应用列表PackageManager.getInstalledApplicationsQUERY_ALL_PACKAGES剪贴板ClipboardManager.getPrimaryClip无默认权限但Android 12有标识检测思路分两层第一层静态检查AndroidManifest里声明了哪些权限第二层动态Hook上述API确认它们在哪个线程、哪个业务栈里被触发是否在App启动时就调用以及调用的频率和调用前后参数是否被加工后传到了网络。只有把动态行为与静态声明对照起来才能判断是否属于超范围收集。2.3 为什么选择Python作为编排层而不是纯JS或Java纯JavaScript脚本能做Hook但很难处理设备管理、结果聚合、报告生成和批量执行。Python的优势在于生态可以用pandas处理数据用Jinja2生成报表用pytest做回归测试。而Frida官方维护了frida-python绑定API设计成熟与Node.js版本功能等价。对于想长期维护合规检测平台的团队Python下搭服务的成本比纯JS方案低得多。3. 搭建PythonFrida检测环境的最小可复现步骤3.1 安装Python侧frida库与匹配的frida-server版本常见做法是先装Python包再找对应的server。注意Python侧的frida库和Android侧的frida-server版本必须一致否则连接时经常报“unable to enumerate processes”或“device not found”。pip install frida16.0.0 pip install frida-tools12.0.0随后下载frida-server-16.0.0-android-arm64.xz解压后推入设备adb push frida-server /data/local/tmp/ adb shell chmod 755 /data/local/tmp/frida-server adb shell /data/local/tmp/frida-server 这里把版本固定下来是为了避免今天能用、明天升级后脚本全挂的问题。frida-tools提供命令行工具比如frida-ps -U可以列出设备进程用来验证连接是否成功。如果设备是模拟器需要根据CPU架构选择arm64或x86_64的server真机一般用arm64。这一步里最容易踩的坑是应用商店里的frida-server版本较旧而pip装的是最新版。建议在Python代码里打印frida.version再对照server启动时的版本提示先把这个唯一强约束满足。3.2 第一个Hook拦截IMEI读取并打印调用栈做一个能实际抓数据的检测从拦截getDeviceId开始。先准备一个纯Python执行脚本里面嵌入JS代码import frida import sys import time js_code const hooks { android.telephony.TelephonyManager: [ getDeviceId, getDeviceId(int), // Android 8以上重载 getImei, getMeid ] }; function readStringForUtf(args, index) { var pointer args[index]; if (pointer.isNull()) return null; try { return pointer.readCString(); } catch (e) { return [[not readable]]; } } Java.perform(function () { for (var className in hooks) { var methods hooks[className]; Java.use(className).$init.overload.implementation function() { return this.$init(); }; methods.forEach(function (methodName) { var overloads hooks[className] // place // 这里需要用 Java.use类.methodName.overloads 枚举重载 }); } }); def main(): device frida.get_usb_device(timeout10) session device.attach(com.example.capture) script session.create_script(js_code) script.on(message, on_message) script.load() print([*] Hook loaded. Waiting for calls...) time.sleep(60) if __name__ __main__: main()代码还没完整逻辑说明和最终版见下一节。先记住这个流程定义一个目标类和方法列表进入Java.perform回调用Java.use拿到类引用遍历该方法的所有重载逐个设置implementation在调用时把参数转成JavaScript对象并通过send函数把数据推给Python端。send一次推一个JSON结构Python端用on_message接收写入本地队列供后续分析。3.3 设备连接失败的排错清单连接Frida失败时按顺序排查以下项目检查项命令/操作错误特征adb设备可见adb devices无设备或offlinefrida-server进程adb shell ps -A | grep frida无输出版本匹配frida-ps -UFrida: ERROR: unable to connect to remote frida-server进程名是否正确frida-ps -U | grep 包名找不到应用模拟器多用户adb -e shell idroot权限不足其中“进程名是否正确”尤其容易踩App的进程名可能不是包名而是带后缀的比如com.example.capture:push。遇到这种情况attach时要填进程名不能用包名。还有一部分App会检查进程是否被调试一旦检测到Frida就崩溃这是后面要单独处理的对抗场景。4. 实现隐私合规检测工具的核心模块4.1 模块规划规则引擎、Hook调度、仓库与报告分离一个可维护的检测工具代码上至少应分成四块privacy_checker/ ├── main.py # 入口解析参数控制设备 ├── rules/ │ └── sensitive_api.json # 敏感API规则定义 ├── injector/ │ └── agent.js # 注入到目标进程的Frida脚本 ├── collector/ │ ├── store.py # 数据内存聚合和去重 │ └── model.py # 数据模型App、行为、权限 └── report/ ├── json_reporter.py # 输出JSON证据 └── html_reporter.py # 输出HTML阅读报告模块职责清晰之后扩展新检测目标不需要改主流程。下面重点讲两个核心文件规则定义和注入脚本。4.2 定义敏感API规则表把需要检测的类、方法和对应的隐私类型放到JSON文件里格式如下[ { class: android.telephony.TelephonyManager, method: getDeviceId, privacy_type: device_id, permission: READ_PHONE_STATE }, { class: android.location.LocationManager, method: requestLocationUpdates, privacy_type: location }, { class: android.media.AudioRecord, method: read, privacy_type: microphone } ]规则表要格外注意Android版本差异。像getDeviceId在API 26以上需要传入slot索引参数实际重载是getDeviceId(int)和getDeviceId()两个版本。规则表中同时列一条不带参数的方法JS脚本在遍历overloads时自动匹配所有重载不需要在规则里写全但需要在脚本里处理重载名否则只Hook默认无参数版结果会漏。4.3 注入脚本拦截、采集与去重这里给出可用的agent.js核心片段逻辑覆盖枚举重载、读取参数和栈信息Java.perform(function () { var RuleList []; // 从Python端注入的规则会放到全局变量 window.__rules里 FileAccess Java.use(java.io.File); var rules global.__rules; rules.forEach(function (rule) { var hookClass Java.use(rule[class]); var methodName rule[method]; var overloads hookClass[methodName].overloads; overloads.forEach(function (overload) { overload.implementation function () { // 收集调用栈 var JavaStack Java.use(java.lang.Thread).currentThread().getStackTrace(); var stackArr []; for (var i 2; i JavaStack.length; i) { stackArr.push(JavaStack[i].toString()); } // 构造消息对象 var msg { type: rule.privacy_type, method: rule[class] . rule[method], args: argsToArray(arguments), stack: stackArr, time: Date.now() }; send(JSON.stringify(msg)); // 保留原逻辑 return overload.apply(this, arguments); }; }); }); });这里常用的参数转换方式面对基本类型和String对象时可以直接把arguments转成普通数组如果参数是自定义对象就要调用对象的toString()或者反射字段。上面的argsToArray在完整代码里还需要对Java字符串做readCString处理。重点在于不要篡改返回值否则会破坏被检测应用的正常功能导致它接着崩溃或跳过业务流程。Python端接收之后进一步做聚合去重。同一方法在短时间内被疯狂调用如果每条都记录报告会膨胀且没有阅读价值。我一般用“方法名调用栈第一层隐私类型”作为唯一键第一次出现保留完整栈信息后续调用只累加计数。class CallAggregator: def __init__(self): self.events {} self.counter {} def add(self, raw): key (raw[method], raw[stack][0], raw[type]) if key not in self.events: self.events[key] raw self.counter[key] 1 else: self.counter[key] 14.4 生成合规检测报告报告需要回答三个问题哪些行为发生了、触发频率多高、对应的权限是否声明。因此报告主体是一个行为表附带原始调用栈。用一个简单的Python字典生成JSONreport { app: app_package, device: device_id, start_time: start_time, end_time: time.time(), behaviors: [] } for key, event in aggregator.events.items(): entry { privacy_type: event[type], api: event[method], count: aggregator.counter[key], first_stack: event[stack][:5] } report[behaviors].append(entry)合规判断规则可以追加一个简单逻辑如果行为的调用栈顶层不是当前App的主Activity或用户点击触发的回调而是处于Application.onCreate、内容观察者注册的回调或者在启动后的2秒内就标记为“潜在违规”。监控时留意时间戳App刚启动就读取iPhone般精准的IMEI这本身就值得怀疑。5. 让检测工具更可靠的三个实战进阶技巧5.1 绕过针对Frida的常见反调试检测合规检测过程中会遇到App反调试检测到Frida文件、默认端口27042或Java层被注入痕迹。跑检测时用Frida的gadget方式替代frida-server可以降低痕迹具体做法是把libgadget.so重命名为libapp_so_name.so依赖App加载该共享库。但更常用的方案是先Hook反调试函数例如针对检查端口连接的函数使用Interceptor.attach截断connect调用Interceptor.attach(Module.findExportByName(null, connect), { onEnter: function(args) { var port (args[1].readU16() 0xFFFF); if (port 27042) { args[1].writeU16(0); // 修改端口值 } } });注意这会让连接端口写错需要配合端口重定向。实际项目中建议使用非默认端口启动frida-serverfrida-server -l 0.0.0.0:27043同时让Python端用device frida.get_device_manager().add_remote_device(127.0.0.1:27043)连接。这样能避开大多数按默认端口探测的检测。5.2 用Stalker监控动态加载的JNI调用有一部分Native层代码无需Java调用直接从so层读取隐私数据此时Java函数Hook看不到。可以用Frida的Stalker跟踪Native函数但性能开销大不适合长时间采集。折中的做法是HookJNIEnv-GetStringUTFChars和NewStringUTF从字符串参数中识别包含telephony、imei、location等关键字的内容。把它们记录下来再通过回溯返回地址得到so库名称和偏移量Interceptor.attach(Module.findExportByName(libart.so, _ZN3art3JNI16GetStringUTFCharsEP7_JNIEnv_P8_jstringPPh), { onEnter: function(args) { var jniEnv args[0]; var jstring args[1]; var env Java.vm.getEnv().handle; // 通过JNIEnv调用GetStringUTFChars获取内容 } });这个方法在隐私检测中比较好用能抓到Java层转给Native层的字符串方向是从Java到Native取反向的话要Hook字符串构造器。5.3 生成合规验证的证据包并绑定时间线给检测工具的最后一个能力是证据固定。把采集到的事件按时间顺序排列同时记录前台Activity和当前网络请求的URL形成一条“数据流转链”。采集网络请求可以Hookokhttp3.Request.url()或HttpURLConnection.getInputStream但这样会影响运行性能所以只在事件发生时记录最近一次的网络访问。最终报告里每一条行为都附上时间戳和当时的界面Activity。这个时间线能直观看出用户是否在点击按钮后被动发起数据读取还是App在后台悄悄采集。将报告JSON和抓到的调用栈细节存成一个zip包即成为上报给监管或第三方检测的平台的基础材料。最后在批量检测多个App时连续长时间采集容易造成设备内存膨胀。一个合理配置是每次只跑20分钟每5分钟重启一次App再把多个session的数据合并。把脚本挂到CI上每天定时执行输出报告到统一目录合规检测就不再是一次性补丁而能成为持续在跑的流程。本文还有配套的精品资源点击获取