做安卓逆向的人,应该都有过这种体验:需要抓包的时候先开电脑,需要看 APK 信息的时候先开电脑,需要跑脚本的时候还是先开电脑。可很多现场排查场景,手边根本不会有电脑,只有一台手机。那能不能把常见的逆向工具链直接塞进手机里,做成一款能在安卓设备上直接跑的 App?这次我们就来拆解一个基于 Flutter 构建的安卓逆向工具箱方案——它不依赖 PC,不强制 root,核心能力包括 APK 信息解析、抓包入口管理、Hook 脚本管理、批量任务队列,以及可选的本地 HTTP 调试接口。本文会围绕“移动端逆向工具箱”的架构设计、功能模块、代码骨架、测试流程和常见问题展开,适合正在做安卓逆向、Flutter 开发,或者想把自己的 PC 工具链移植到手机上的开发者参考。
这类工具过去更常见的是 PC 整合包形式,比如大家熟悉的“图吧工具箱”那一类桌面工具集合。但移动端场景一直在增长,把 Flutter 当作 UI 层,把系统 API、ADB 命令、抓包代理、脚本引擎整合到同一个 App 里,就能形成一套“口袋工具箱”。和 PC 端相比,它最大的优势是便携:手机可以直接连测试机、连 Wi-Fi 热点、读目标 App 的包名和权限,所有动作都在本地完成,不依赖台式机环境。
先给结论:这个方案适合中等复杂度逆向场景,它能跑通的典型任务包括查看 APK 的包名、版本、权限、四大组件信息;快速配置代理地址做抓包准备;把 Frida 脚本或自定义脚本推送到目标设备执行;对一批 APK 做信息收集,并把结果导出成 JSON。如果只是想做静态分析、信息收集、签名查看这类轻量任务,手机完全够用。如果需要高强度动态调试、反混淆、堆栈追踪,还是建议配合 PC 上的 IDA、Frida、Jadx 使用。整篇会按照“核心能力速览 — 功能边界 — 环境准备 — 项目搭建 — 功能实现 — 功能测试 — 接口与批量任务 — 资源占用 — 排查方法 — 最佳实践”的顺序展开,可以直接照着搭。
1. Flutter 逆向工具箱核心能力速览
把工具做成 App,第一个要回答的问题就是“哪些功能适合放进手机”。下面是这套 Flutter 逆向工具箱的核心能力清单:
| 能力项 | 说明 |
|---|---|
| 项目类型 | 基于 Flutter 的安卓端逆向辅助工具箱 |
| 主要功能 | APK 信息解析、抓包代理配置、Hook 脚本管理、批量任务队列、本地调试接口 |
| 运行平台 | 安卓手机 / 安卓模拟器,开发阶段可通过 flutter run 运行 |
| 启动方式 | Debug 模式flutter run或 Release 打包 APK 安装启动 |
| 是否需要 root | 不强制;部分 Hook 与抓包场景需要配合授权测试环境 |
| 是否支持批量任务 | 支持;通过 Isolate 和任务队列批量处理 APK 信息收集 |
| 是否支持接口 API | 支持;可启动本地 HTTP 服务,供其他工具调用 |
| 显存需求 | 不涉及显存;主要关注手机内存和磁盘空间 |
| 适用场景 | 现场排查、目标 App 信息收集、脚本管理、自动抓包准备、课程测试演示 |
| 不适合场景 | 高强度动态调试、反混淆、大型函数级分析 |
从这张表能看出来,手机端工具箱的重点在于“够用、便携、可批处理”。Flutter 在这里承担的是 UI 和逻辑编排层,真正干活的还是安卓系统 API、ADB、代理设置和脚本文本管理。批量任务和本地 API 这两个能力,让这个 App 不只是单机工具,还可以被外部脚本或 PC 端驱动。
2. 功能边界与合规使用提醒
在动手搭建之前,最好先想清楚:这个工具箱到底解决什么问题,不解决什么问题。
移动端逆向工具箱适合做的任务分成四类。静态信息收集类:读取已安装 App 的包名、版本号、权限列表、Activity、Service、Receiver、Provider 等信息,这部分最稳定,也是最适合做成 App 的模块。网络调试辅助类:在手机上快速配置 Wi-Fi 代理,或者生成代理配置二维码,让目标设备扫码后进入抓包环境,这比在 PC 上手动填代理快得多。脚本管理类:把 Frida 或其他 Hook 脚本按目标 App 分类保存,点击即可把脚本文件复制到授权测试设备,或者记录启动命令,减少敲键盘的时间。批量任务类:对一组 APK 文件逐个做静态签名校验、恶意权限扫描、基本信息提取,最终输出 JSON 报告。
不适合做的场景也要说清楚。完整的反编译分析依赖 JADX、GDA 这类桌面工具,手机端预览可以,但做深度调用链分析不现实。动态调试和高强度 Hook 受系统权限限制,普通 App 内嵌的调试器能力和 IDA Pro、Frida Server 全功能差很多。合法性边界更要注意:抓包和 Hook 只允许在你自己开发的 App、公司授权的测试 App、或者已获得明确授权的目标上使用,不能拿它去破解别人的服务端签名、绕过付费验证、爬取未授权数据。文章后面所有脚本和接口示例,都默认在授权测试环境中执行。
3. 环境准备与前置条件
构建这套 Flutter 逆向工具箱,开发机建议先准备好以下环境。
3.1 开发环境检查清单
- 操作系统:Windows / macOS / Linux 均可,建议 64 位 - Flutter SDK:稳定版,建议在 3.x 以上(具体版本以官方最新稳定版为准) - Dart SDK:随 Flutter 安装,不需要单独配置 - Android Studio:用于创建安卓工程、安装 SDK、查看设备日志 - Android SDK:API 21 以上即可,推荐 API 30 左右 - JDK:Android Studio 自带 JBR,或单独安装 JDK 11/17 - 测试设备:安卓 7.0 以上真机或模拟器,建议真机测试代理和抓包模块 - ADB 工具:用于连接设备、查看进程、截图3.2 Flutter 安装与验证
Flutter 安装的流程是:从官网下载对应操作系统的 SDK 压缩包,解压后把 bin 目录加入 PATH,然后运行flutter doctor检查依赖。安装完成后,打开终端执行:
flutter doctor能看到 Android toolchain 这一项通过,说明环境基本可用。如果没有通过,通常是三方面问题:没有安装 Android Studio;没有接受 SDK License;没有配置 JDK。分别用下面的命令处理:
flutter doctor --android-licenses flutter config --jdk-dir=/path/to/jdk3.3 项目依赖规划
在主工程的pubspec.yaml里,建议根据功能按需引入依赖,但不要一次性全装。常用的几类包括:
dependencies: flutter: sdk: flutter # 选择适合当前 Flutter 版本的网络库 dio: ^5.0.0 # 本地存储,用于保存脚本和任务配置 shared_preferences: ^2.0.0 # 文件选择,方便导入 APK 文件 file_picker: ^5.0.0依赖版本不能照抄,要以你本机flutter --version对应的兼容版本为准。加依赖后执行:
flutter pub get这里有个容易踩的坑:网络库、文件选择库更新很快,不同 Flutter 版本的要求不一样。安装报错时,优先看pubspec.lock的冲突提示,不要盲目 upgrade。
4. 项目搭建与启动方式
项目结构上,我建议按功能模块拆分,而不是把所有代码堆进 main.dart。
lib/ ├── main.dart # 入口,负责初始化 ├── pages/ │ ├── home_page.dart # 工具箱首页 │ ├── apk_info_page.dart # APK 信息查看 │ ├── proxy_page.dart # 代理配置页 │ ├── script_page.dart # Hook 脚本管理页 │ └── batch_task_page.dart # 批量任务页 ├── services/ │ ├── apk_parser.dart # APK 解析服务 │ ├── proxy_config.dart # 代理配置服务 │ ├── script_manager.dart # 脚本管理服务 │ ├── task_queue.dart # 批量任务队列 │ └── http_server.dart # 本地 HTTP 接口服务 ├── models/ │ ├── apk_info.dart # APK 信息模型 │ └── task_result.dart # 任务结果模型 └── utils/ └── network.dart # 网络请求工具创建项目只需要一行命令:
flutter create reverse_toolbox创建完成后,进入目录启动调试:
cd reverse_toolbox flutter run启动后可以在 Android Studio Logcat 或终端里查看运行日志。如果是真机调试,需要开启开发者选项和 USB 调试。这个阶段不要急着写复杂业务逻辑,先把底部导航和四个核心页面搭出来,确保 App 能在设备上跑起来。
5. 核心功能模块实现
5.1 APK 信息解析模块
APK 信息解析是整个工具箱里最稳定、最值得做的模块。Flutter 本身拿不到系统安装包的详细信息,需要借助 MethodChannel 调用安卓原生代码。在 MainActivity.kt 中注册 Channel:
class MainActivity : FlutterActivity() { private val CHANNEL = "com.example.reverse_toolbox/apk" override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL) .setMethodCallHandler { call, result -> when (call.method) { "getInstalledApks" -> { val packageManager = packageManager val intent = Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_LAUNCHER) val resolveInfos = packageManager.queryIntentActivities(intent, 0) val appList = resolveInfos.map { info -> mapOf( "packageName" to info.activityInfo.packageName, "appName" to info.loadLabel(packageManager).toString(), "versionName" to packageManager.getPackageInfo(info.activityInfo.packageName, 0).versionName ) } result.success(appList) } else -> result.notImplemented() } } } }在 Flutter 侧调用时,封装一个服务类:
import 'package:flutter/services.dart'; class ApkParser { static const _channel = MethodChannel('com.example.reverse_toolbox/apk'); Future<List<Map<String, dynamic>>> getInstalledApks() async { final result = await _channel.invokeMethod('getInstalledApks'); return (result as List).cast<Map<String, dynamic>>(); } }页面里显示列表时,只需要从 Future 中取数据,配合 ListView 渲染即可。判断成功的标准是:能看到设备上所有可启动 App 的包名、应用名和版本号。如果列表为空,先检查设备是否连接、权限是否被系统限制。
5.2 代理配置与抓包入口
抓包是逆向里的高频动作,但在手机上手动填 Wi-Fi 代理很浪费时间。这个模块的思路是做两个功能:一键支取当前设备的 Wi-Fi 代理;另一个是生成代理配置二维码,供另一台设备扫码配置。需要注意的是,安卓系统从高版本开始对 Wi-Fi 代理的写入权限有收紧,如果直接写入失败,可以引导用户进入系统设置页手动填写。Dart 侧代码只负责 UI 展示和存储代理地址:
class ProxyConfig { String host = '127.0.0.1'; int port = 8888; String get proxyAddress => '$host:$port'; }实际设置代理时,需要调用原生 Wi-Fi 管理接口。建议先让用户手动输入代理地址,再通过一个“打开设置”按钮跳到系统 Wi-Fi 设置,用户确认后回到 App 继续操作。这样稳定性最高,不至于被不同厂商的安卓 ROM 拦截。
5.3 Hook 脚本管理模块
脚本管理模块本质上是一个文本容器。它不直接执行 Hook 逻辑,而是按“目标 App 包名”分类保存 Frida 代码片段,并提供复制、导出、生成启动命令的功能。这样在现场调试时,不用再去找脚本文件路径,打开 App 直接复制,或者通过本地 API 推送。数据模型可以这样设计:
class HookScript { final String name; final String targetPackage; final String content; final DateTime updatedAt; HookScript({ required this.name, required this.targetPackage, required this.content, required this.updatedAt, }); }保存方式用 SharedPreferences 或本地文件都行。如果脚本较多,建议直接用文件目录管理:
/storage/emulated/0/ReverseToolbox/scripts/ ├── com.example.target_a/ │ ├── hook_login.js │ └── hook_sign.js └── com.example.target_b/ └── trace_network.js这个模块做起来不复杂,但实用价值很高。配合后面讲的本地 HTTP 接口,PC 端可以直接向手机端脚本库发起请求,实现“PC 编辑脚本 -> 手机端保存 -> 目标设备执行”的完整链路。
5.4 批量任务队列
批量任务适合做 APK 信息收集和签名校验。它的难点在于不要阻塞 UI 线程,同时要处理失败重试和结果收集。Flutter 里用 Isolate 做后台处理,主 Isolate 只接收进度消息。一个简单的任务管理器可以这样设计:
import 'dart:isolate'; class TaskQueue { final List<Map<String, dynamic>> tasks = []; Future<void> addTask(Map<String, dynamic> task) async { tasks.add(task); // 此处将任务发送到后台 Isolate 处理 } }在实际工程里,可以用Isolate.spawn创建一个 worker,通过 ReceivePort 接收任务和返回结果。批量任务的输入可以是一个目录下的多个 APK 文件,也可以是一个包名列表。输出统一为 JSON,方便 PC 端导入分析。
6. 功能测试与效果验证
功能写完只是第一步,下面给出一套可执行的验证流程。每个功能都按“测试目的、输入素材、操作步骤、预期结果、失败排查”来验证。
6.1 APK 信息解析测试
- 测试目的:确认 App 能正确读取设备上的应用信息。
- 输入素材:一台安装有微信、淘宝等常见应用的安卓真机。
- 操作步骤:打开 APK 信息页,点击刷新。
- 预期结果:页面展示包名、应用名、版本号,搜索框能按包名过滤。
- 判断成功标准:微信的包名显示为
com.tencent.mm,版本号和系统设置里一致。 - 常见失败:列表空白时,检查 MethodChannel 名称是否一致、MainActivity 是否重新编译过、设备是否授予了查询应用列表的权限。
6.2 代理配置测试
- 测试目的:确认代理地址可以被正确保存、展示、并复制到剪贴板。
- 输入素材:本机内网地址
192.168.1.100:8888。 - 操作步骤:在代理配置页输入地址,点击保存,再点击复制。
- 预期结果:保存后重新进入页面,地址仍然存在;粘贴板内容为完整代理地址。
- 判断成功标准:复制出的地址能被 PC 端代理工具识别。
- 常见失败:地址格式错误、端口写错、SharedPreferences 未提交。
6.3 脚本管理测试
- 测试目的:验证脚本保存、按包名分类、复制内容三个核心动作。
- 输入素材:一段 Frida 网络监控脚本。
- 操作步骤:新建脚本,输入目标包名和内容,保存后再次进入。
- 预期结果:脚本列表出现目标包名分类,点击复制后内容完整。
- 判断成功标准:复制出的脚本能粘贴到 PC 端的命令行执行。
- 常见失败:内容包含特殊字符串导致页面渲染异常,建议在编辑时用普通 TextField 而不是带高亮逻辑的编辑器组件。
6.4 批量任务测试
- 测试目的:验证批量解析 APK 信息并导出 JSON。
- 输入素材:在手机存储目录放入 5 个 APK 文件。
- 操作步骤:进入批量任务页,选择目录,点击开始。
- 预期结果:进度条逐个推进,完成后生成 report.json。
- 判断成功标准:报告包含每个 APK 的包名、版本、权限数量、签名信息。
- 常见失败:文件读取权限不足,需要在安卓 11 以上适配分区存储;任务卡住时检查 Isolate 是否正常销毁。
6.5 本地接口服务测试
- 测试目的:确认其他设备能访问手机端 HTTP 接口。
- 输入素材:手机和 PC 连接同一局域网。
- 操作步骤:在 App 中启动本地服务,浏览器访问
http://手机IP:8765/health。 - 预期结果:接口返回
{"status":"ok"}。 - 判断成功标准:PC 能访问,且响应时间在几秒以内。
- 常见失败:手机防火墙拦截、端口被占用、服务绑定了 127.0.0.1 而不是 0.0.0.0。
7. 接口 API 与批量任务设计
工具箱如果只用 UI 操作,实用性会打折。加上本地 HTTP 接口后,手机就变成一个可被 PC 调用的调试节点。这里采用 Dart 内置的HttpServer来实现,不引入重量级服务框架。
7.1 启动本地 HTTP 服务
import 'dart:io'; class LocalApiServer { HttpServer? _server; Future<void> start({int port = 8765}) async { _server = await HttpServer.bind(InternetAddress.anyIPv4, port); _server!.listen((request) async { if (request.uri.path == '/health') { request.response ..statusCode = HttpStatus.ok ..write('{"status":"ok"}'); await request.response.close(); } else if (request.uri.path == '/apk/list') { final apks = await ApkParser().getInstalledApks(); request.response ..statusCode = HttpStatus.ok ..headers.contentType = ContentType.json ..write(apks.toString()); await request.response.close(); } else { request.response.statusCode = HttpStatus.notFound; await request.response.close(); } }); } Future<void> stop() async { await _server?.close(); } }7.2 批量任务 JSON 配置
批量任务建议通过一个 JSON 清单描述,方便 PC 端生成任务后提交到手机。
{ "taskName": "apk_scan_demo", "inputDir": "/storage/emulated/0/ReverseToolbox/apks", "outputFile": "/storage/emulated/0/ReverseToolbox/output/report.json", "actions": ["parseInfo", "checkSignature", "highRiskPermission"], "retry": 2 }7.3 curl 调用示例
PC 端可以通过 curl 触发手机端的批量任务:
curl -X POST http://192.168.1.100:8765/task/run \ -H "Content-Type: application/json" \ -d '{ "taskName": "apk_scan_demo", "inputDir": "/storage/emulated/0/ReverseToolbox/apks", "actions": ["parseInfo", "checkSignature"], "retry": 2 }'如果使用 Python 做批量调用,可以参考以下模板:
import requests import json phone_api = "http://192.168.1.100:8765" payload = { "taskName": "batch_1", "inputDir": "/storage/emulated/0/ReverseToolbox/apks", "actions": ["parseInfo"], "retry": 2 } resp = requests.post( f"{phone_api}/task/run", json=payload, timeout=120 ) print(resp.status_code) print(resp.json())7.4 失败重试建议
移动端执行批量任务时,经常遇到文件占用、读取超时、设备休眠导致服务中断等问题。建议在任务队列中加三层机制:超时控制,单个 APK 解析超过 30 秒直接标记失败;重试计数,每个任务最多重试 2 次,失败原因记入日志;断点续跑,每完成一个任务就更新进度文件,重启后从上次位置继续。把这些逻辑放进TaskQueue里,不要散落在页面代码中。
8. 资源占用与性能观察
这套工具箱属于轻量级 App,不涉及 GPU 显存,但手机内存和耗电依然需要关注。
在 Flutter 开发阶段,建议通过 Android Studio 的 Profiler 观察 App 内存。核心指标有三个:Dart VM 内存、原生内存、线程数。做批量任务时,原来在 PC 端跑 Frida 脚本的 CPU 占用会转移到手机端脚本执行,App 自身的批量任务如果不当心,也可能导致手机发热。降低占用可以从三个方向入手:批量任务启动时把屏幕常亮关闭,让系统正常息屏后进入后台调度;使用 Isolate 时每次任务结束都调用Isolate.kill或发送退出消息,避免 worker 泄漏;大列表使用ListView.builder而不是一次性构建全部 item。
如果只是做 UI 扫描和脚本管理,App 长期后台驻留的额外开销很小。但本地 HTTP 服务一旦开启,建议在界面明确显示“服务运行中”,并提供一键关闭,避免用户忘记关闭导致耗电和端口开放。
9. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| flutter run 启动失败 | Flutter 版本和依赖库不兼容 | 查看终端报错中的版本冲突项 | 调整 pubspec.yaml 依赖版本或升级 Flutter |
| MethodChannel 调用无响应 | Channel 名称不一致或未重新构建原生代码 | 检查 Kotlin 代码和 Dart 代码中的 channel 名称 | 统一 channel 名称并 hot restart |
| APK 列表为空 | 缺少查询应用列表权限 | 查看 Logcat 中的 SecurityException | 在 AndroidManifest 中添加权限声明并动态申请 |
| 代理设置保存后重启丢失 | SharedPreferences 未异步提交 | 检查写入是否使用了 await | 改用 setString 后调用 commit 或使用 await |
| 批量任务解析超时 | APK 文件过大或目录中有损坏文件 | 查看任务日志中的单文件耗时 | 增加超时时间并跳过损坏文件 |
| 本地接口无法访问 | 服务绑定了 127.0.0.1 或手机对端口有限制 | 检查代码中的 bind 地址 | 改用 InternetAddress.anyIPv4,关闭防火墙限制 |
| 脚本内容显示乱码 | 编码不一致 | 确认文件保存格式为 UTF-8 | 在保存时统一编码 |
| 打包后 MethodChannel 闪退 | 混淆规则缺失 | 查看 release 包崩溃日志 | 在 proguard-rules.pro 中保留 FlutterChannel 相关类 |
遇到问题先看两类日志:Flutter 侧日志用debugPrint输出,原生侧日志用Logcat查看。大部分通道问题都是名称不一致导致,优先排除。
10. 最佳实践与使用建议
10.1 先做减法
第一版不要把抓包、Hook、反编译全部塞进去。建议先实现 APK 信息解析和脚本管理两个模块,跑通整个开发、打包、真机安装流程后,再加入批量任务和 HTTP 接口。工具类 App 最怕功能堆砌但每个模块都不稳定。
10.2 目录与产物分离
建议在 App 内固定一套目录结构:
/storage/emulated/0/ReverseToolbox/ ├── apks/ # 待分析的 APK 文件 ├── scripts/ # Hook 脚本库 ├── output/ # 批量任务报告 └── logs/ # 运行日志这样即使任务中断,PC 端也能通过文件管理器快速找到中间产物。
10.3 接口服务要加访问控制
本地 HTTP 服务虽然方便,但也意味着同一局域网内其他设备可以请求你的接口。不要在局域网内无条件开放。建议加一个简单 Token 验证,请求头中携带固定 Key,服务端校验通过才处理。生产工具不要使用硬编码 Token,至少做到可配置。
10.4 合规边界必须清楚
再次强调,抓包、Hook、脚本注入只允许用于授权测试环境。以下行为明确禁止:破解付费 App 的 VIP 功能;绕过服务端签名校验;爬取他人未授权的数据;在未经测试的线上环境使用 Frida 脚本。工具箱只是工具,用途要由使用者负责。
11. 总结与下一步
这套基于 Flutter 的安卓逆向工具箱,解决的是“没有 PC 时也能快速完成 APK 信息收集、脚本管理、代理配置和批量任务”的问题。最容易上手验证的是 APK 信息解析模块,跑通它就能建立起“Flutter UI + MethodChannel + 原生能力”的完整链路。最容易踩坑的是通道名称不一致和安卓高版本分区存储权限,这两点建议在项目初期就处理掉。本地 HTTP 接口接入后,可以继续扩展远程任务下发、PC 端批量驱动手机端分析。如果愿意继续迭代,还可以加入报文比对、签名查看、脱壳辅助等模块,让这套工具箱从一个演示项目逐步变成真正每天都会打开的效率工具。