
1. 从一次逆向分析的需求说起最近在做一个竞品分析目标应用是一个基于 Flutter 开发的金融类 APP。我的任务不仅仅是看它的功能更想了解它底层使用的技术栈特别是 Flutter 的版本。为什么关心这个因为 Flutter 的版本直接决定了它可能使用的特性、存在的已知漏洞、以及我们后续进行一些兼容性测试或技术调研的边界。比如Flutter 3.0 引入了对 macOS 和 Linux 桌面应用的稳定支持而 3.10 在渲染引擎和性能上有重大更新。知道版本就能大致推断出这个应用的“技术年龄”和可能的技术债务。然而事情并没有想象中那么简单。你不可能直接跑过去问对方的开发团队“嘿你们用的 Flutter 是哪个版本” 对于已经发布上架的应用我们只能从应用包体本身去寻找蛛丝马迹。这就像侦探工作需要从打包后的产物中寻找线索。我尝试了网上常见的几种方法有的有效有的则完全走不通甚至有些教程里写的方法已经过时。这篇文章就是把我这段时间摸索出来的、真正有效且适用于不同场景的几种“侦探”手法以及背后的原理和踩过的坑系统地梳理一遍。无论你是做安全研究、竞品分析还是单纯对某个 Flutter 应用感到好奇这些方法都能给你提供一个清晰的路径。2. 初级侦探从应用包体结构入手寻找明文线索最直接的想法就是看看应用安装包APK 或 IPA里有没有写着版本号的文件。对于 Flutter 应用这确实是可能的但线索往往藏得比较深而且不同版本的 Flutter 打包方式也有差异。2.1 Android APK 的解包与探查对于 Android 平台我们可以直接分析 APK 文件。APK 本质上是一个 ZIP 压缩包解压后就能看到其内部结构。第一步获取并解压 APK你可以从官方应用商店下载 APK或者使用adb pull命令从已安装应用的设备中提取。拿到 APK 后将其后缀名改为.zip然后直接解压或者使用命令行工具如unzip。第二步关键目录与文件搜索解压后你会看到assets,lib,META-INF等目录。对于 Flutter 应用核心线索通常在assets目录下。查找flutter_assets目录这是 Flutter 资源的标准存放位置。进入assets/flutter_assets/查找一个名为version或flutter_version的文件。在 Flutter 早期的一些版本或特定构建模式下开发者可能会手动放置这样一个文件但这不是官方强制行为所以找到的概率不高。探查kernel_blob.bin或vm_snapshot_data在assets/flutter_assets下你大概率会看到kernel_blob.binDart 代码的 Kernel 格式快照或vm_snapshot_data/isolate_snapshot_dataAOT 编译模式的快照等文件。这些文件本身是二进制的但它们的文件名和存在性就是线索。不过直接从这些二进制文件里读出版本号对普通人来说几乎不可能。检查AndroidManifest.xml用AXMLPrinter2或apktool反编译 APK查看AndroidManifest.xml。虽然这里不会直接写明 Flutter 版本但有时元数据meta-data或android:versionName可能包含一些内部版本约定需要结合其他信息推断。注意直接搜索“Flutter”字符串可能一无所获因为版本信息通常不会以明文形式存储在资源文件中。这个阶段的目标是熟悉包体结构并确认这确实是一个 Flutter 应用通过存在flutter_assets和lib/*/libflutter.so等文件来确认。2.2 iOS IPA 的探查思路iOS 的 IPA 文件同样是一个 ZIP 包但结构略有不同且由于苹果的签名和加密机制分析起来更复杂一些。获取 IPA对于未上架 App Store 或通过企业证书分发的应用可以直接拿到 IPA。对于 App Store 应用获取解密后的 IPA 需要越狱设备或使用一些特定工具这超出了普通技术探查的范围且涉及法律风险此处不展开。解压与查找将.ipa改为.zip并解压。你会得到一个Payload文件夹里面是.app包。右键显示包内容进入.app内部。类似于 Android查找Frameworks/Flutter.framework目录的存在可以确认是 Flutter 应用。但版本信息同样不会直接写在明文中。有时在AppFrameworkInfo.plist或Info.plist里可能会有一些编译相关的信息但极少包含清晰的 Flutter 版本号。初级方法小结通过解包直接查找明文版本号成功率极低。它主要用来确认应用是否为 Flutter 开发并为后续更深入的分析做准备。如果你在这一步就找到了一个写着“3.13.9”的version.txt那恭喜你中奖了。但绝大多数情况下我们需要更高级的工具。3. 中级侦探逆向工程与二进制分析当明文搜索无效时我们就需要深入二进制世界从应用的代码和依赖库中寻找编译时留下的痕迹。3.1 分析 Android 原生库 (libflutter.so)这是目前最可靠的方法之一。Flutter 引擎的核心是一个名为libflutter.so的本地共享库它会被打包进 APK 的lib/目录下例如lib/arm64-v8a/libflutter.so。这个库文件里包含了许多字符串信息其中就有编译时嵌入的 Flutter 引擎版本。操作步骤提取 libflutter.so从解压后的 APK 的lib/abi/目录下找到libflutter.so文件。abi可能是armeabi-v7a,arm64-v8a,x86_64等。使用字符串查看工具在 Linux/macOS 上可以直接使用strings命令。在 Windows 上可以使用Sysinternals Suite中的strings.exe或者用 Notepad、Hex Editor 等编辑器以二进制打开后搜索。# 在终端中执行 strings libflutter.so | grep -i flutter筛选关键信息strings命令会输出该二进制文件中所有可打印的字符串。你需要仔细查看输出寻找包含版本号的字符串。通常它会类似于Flutter 3.13.9 • channel stable • https://github.com/flutter/flutter.git Engine • revision a6ef4c6003 Tools • Dart 3.1.5 • DevTools 2.25.0或者更简洁的“Flutter 3.10.0”。这一串信息是 Flutter 引擎在编译时硬编码进去的它明确指出了 Flutter 框架版本、引擎版本、Dart 版本等。找到以 “Flutter” 开头后接数字版本号的字符串就是你要的答案。为什么这个方法可靠因为这个字符串是 Flutter 引擎本身的一部分只要应用使用了 Flutter这个库就必须被包含并且这个版本信息字符串在官方构建的引擎中总是存在的。它不依赖于开发者的任何额外配置。3.2 分析 iOS 框架 (Flutter.framework)对于 iOS思路类似但分析的对象是Flutter.framework。定位框架在解压后的.app/Frameworks/目录下找到Flutter.framework。分析可执行文件Flutter.framework本质上是一个文件夹其中的Flutter文件无后缀是实际的动态库。对这个二进制文件使用strings命令。strings Flutter.framework/Flutter | grep -i -A2 -B2 flutter同样在输出中寻找包含完整版本信息的字符串行。格式与 Android 的libflutter.so中的类似。3.3 反编译 Dart 代码辅助手段有时我们可能想通过 Dart 代码本身来找线索。Flutter 发布版应用中的 Dart 代码通常被编译为 AOT 快照libapp.so等或 Kernel 快照kernel_blob.bin这些是高度优化和压缩的二进制格式直接反编译可读性极差。不过我们可以尝试一个“笨办法”用strings命令扫描这些 Dart 二进制文件有时能发现一些开发环境残留的路径信息例如/Users/SomeDeveloper/flutter/3.7.12/...这种路径中可能包含 Flutter SDK 的版本号。但这完全取决于开发者的构建环境配置不是一种可靠的方法只能作为在毫无头绪时的补充尝试。中级方法小结逆向二进制是获取 Flutter 版本的黄金标准。strings命令配合grep是核心工具链。它的优势是准确、直接、不依赖于应用逻辑。你需要克服的是对命令行工具的一点点恐惧实际上操作非常简单。我遇到的大约 95% 的 Flutter 应用都可以通过分析libflutter.so或Flutter二进制文件成功获取版本。4. 高级侦探动态分析与运行时探查如果应用包体被加固了导致无法轻易解包或分析二进制文件或者你想在不接触安装包的情况下进行探测那么动态分析运行时分析就是另一条路。这条路更“黑盒”但也更有趣。4.1 抓包分析网络请求许多 Flutter 应用在启动或运行过程中会与后端服务器通信。有时为了统计或诊断开发者会在 HTTP 请求头User-Agent或特定的初始化请求体中附带客户端的环境信息其中就可能包含 Flutter 版本。操作步骤配置抓包环境在电脑上设置代理如 Charles 或 Fiddler并在手机 Wi-Fi 设置中配置代理指向电脑。安装 CA 证书在手机上安装抓包工具的根证书以便解密 HTTPS 流量对于金融类等强校验 APP 可能失败它们会启用证书绑定。启动应用并抓包清空抓包工具记录然后启动目标 Flutter 应用。观察所有发出的网络请求。筛选请求重点关注应用启动后最早的几个请求或者看起来像是“初始化”、“上报”、“配置获取”之类的 API。检查请求头和请求体仔细查看这些请求的Headers特别是User-Agent字段。有时它会包含类似Flutter/3.10.0 (iOS; 16.6)的字符串。同时查看Request Body如果是 JSON 格式寻找clientInfo,sdkVersion,flutterVersion等字段。踩坑实录这个方法成功率不高完全取决于开发者的实现习惯。而且现在越来越多的应用为了防止中间人攻击会启用 SSL Pinning证书绑定导致常规的抓包工具无法解密其 HTTPS 流量你会看到一堆Tunnel to ...或乱码。这就是为什么“app抓包失败”会成为一个热门搜索词。对于这类应用此路基本不通。4.2 日志输出分析 (Logcat/Console)Flutter 引擎和应用本身在运行时可能会向系统日志输出一些信息。在 Android 上我们可以通过adb logcat来捕获这些日志。操作步骤连接设备并清空日志adb logcat -c启动应用并过滤日志启动目标应用同时运行adb logcat | grep -i flutter或者更精确地过滤 Flutter 引擎的标签tagadb logcat -s flutter寻找版本信息在应用启动的瞬间Flutter 引擎初始化时很可能会打印出类似下面的日志I/flutter (12345): Flutter run key commands. I/flutter (12345): Flutter 3.13.9 • channel stable • ...这和在libflutter.so中找到的字符串是一致的。这是动态方法中最有可能成功的一种因为它打印的是引擎自身的版本信息不依赖于应用代码。iOS 的类似操作在 macOS 上如果设备通过 Xcode 连接可以在Console.app中查看设备日志并过滤flutter关键词。对于未越狱的设备获取完整系统日志比较麻烦通常需要开发证书配置。高级方法小结动态分析是一种非侵入式的探查方式。其中分析 Android 的logcat日志是除二进制分析外最有效的方法。抓包分析则不确定性很大受应用网络库实现和安全策略影响深。这些方法适合当你只有应用安装权限而没有其安装包文件时使用。5. 综合案例与疑难杂症处理理论讲完了我们结合几个典型的“热搜词”场景来看看如何具体应用上述方法并处理一些常见问题。5.1 案例一面对“flutter怎么防止http抓包”的应用当你搜索“如何获取版本”时很可能目标应用已经采取了防抓包措施。这时二进制分析和日志分析就成了主力。首选libflutter.so分析无论应用如何加固网络层只要它是 Flutter 应用libflutter.so或Flutter.framework就必须存在。从 APK/IPA 中提取这个库进行分析是绕开网络防护的直击核心的方法。加固可能会混淆应用自身的 Dart 代码但通常不会也很难去抹掉引擎库中的版本信息字符串。次选logcat日志即使应用做了证书绑定它启动时向系统日志打印信息的行为通常不会受到影响。通过adb logcat抓取启动日志依然是有效的动态方案。放弃抓包对于这类应用不要再在抓包上浪费时间。看到“Tunnel to”或证书错误就可以果断放弃这条路径。5.2 案例二处理“initializing the flutter sdk. this could take a few minutes. 一直卡着”这个热搜词描述的是开发环境问题但给我们一个启示版本信息可能在“初始化”过程中出现。对应到已发布的应用就是应用启动的初始阶段。无论是分析libflutter.so的字符串还是抓取logcat都要特别关注应用冷启动时产生的信息。有时候多重启几次应用在启动瞬间抓取日志成功率更高。5.3 疑难版本字符串格式不匹配或找不到有时strings命令找到的信息可能不标准或者你找到了多个疑似版本号的字符串。怎么办识别官方格式Flutter 官方版本的字符串格式相对固定Flutter x.y.z • channel channel • ...。认准以“Flutter ”注意有空格开头的行。引擎版本与框架版本你可能会看到Engine • revision abcdef。这是引擎的提交哈希不是框架版本号。我们需要的是Flutter后面的数字版本。框架版本如 3.13.9才是我们通常所说的“Flutter 版本”。多渠道构建如果字符串中包含channel dev或channel master说明这是开发版或主分支构建版本号可能是一个较新的“预览”版本你需要结合日期和提交哈希去 Flutter 仓库大致推断。真的找不到极其罕见的情况下如果应用使用了高度定制或裁剪的 Flutter 引擎可能会移除非必要的字符串以减小体积。这时可以尝试搜索 “Dart SDK version”因为 Dart 版本与 Flutter 版本有较强的关联性例如 Dart 3.1.5 通常对应 Flutter 3.13.x。你可以根据 Dart 版本去 Flutter 的发布历史中反推一个近似的 Flutter 版本范围。5.4 工具链自动化思路如果你需要频繁进行此类分析可以写一个简单的脚本自动化这个过程。以下是一个 Bash 脚本的示例思路#!/bin/bash # 假设 APK 路径作为第一个参数传入 APK_PATH$1 # 1. 解压 APK 到临时目录 TEMP_DIR$(mktemp -d) unzip -q $APK_PATH -d $TEMP_DIR # 2. 寻找 libflutter.so假设为 arm64-v8a LIB_PATH$(find $TEMP_DIR -name libflutter.so -path */arm64-v8a/* | head -1) if [ -f $LIB_PATH ]; then echo 找到 libflutter.so: $LIB_PATH # 3. 提取版本信息 VERSION_INFO$(strings $LIB_PATH | grep -E ^Flutter [0-9]\.[0-9]\.[0-9]) if [ -n $VERSION_INFO ]; then echo Flutter 版本信息: echo $VERSION_INFO else echo 未在 libflutter.so 中找到标准版本字符串。 # 可以尝试更宽泛的搜索 strings $LIB_PATH | grep -i -A5 -B5 flutter | head -20 fi else echo 未找到 libflutter.so可能不是 Flutter 应用或架构路径不同。 # 尝试其他 ABI 目录或查找 Flutter.framework (iOS) fi # 4. 清理临时目录 rm -rf $TEMP_DIR这个脚本只是一个起点你可以根据需要扩展比如自动处理 IPA、支持多种 ABI、解析更复杂的版本行等。6. 总结与最佳实践选择走过了初级、中级、高级的侦探之路我们来梳理一下在不同场景下的最佳选择。如果你有 APK/IPA 文件首选且最推荐的方法直接使用strings命令分析libflutter.soAndroid或Flutter.framework/FlutteriOS二进制文件。这是最快、最准确、最可靠的方法成功率接近 100%。备用方案解包后快速浏览assets目录看是否有显式的版本文件概率极低但操作简单可顺手为之。如果你只有安装好的应用无安装包对于 Android 设备使用adb logcat在应用启动时捕获日志并过滤 Flutter 标签。这是动态分析中最有效的手段。对于 iOS 设备通过 Xcode Console.app 查看设备日志过滤 Flutter 关键词。这需要一定的开发环境配置。网络抓包仅作为最后尝试且对未启用强证书绑定的应用可能有效。优先检查启动时的初始化请求和 User-Agent。需要避开的坑不要依赖应用内 UI 显示应用设置里的“关于”页面显示的版本号通常是应用自身的业务版本如1.2.3而不是 Flutter SDK 的版本。不要轻信过时的教程有些老文章会提到分析pubspec.lock文件这个文件在开发阶段存在但不会被打包到发布版的应用中。谨慎使用第三方在线分析工具有些网站声称可以上传 APK 分析组件。出于安全考虑不要将敏感的竞品或商业应用 APK 上传到不明网站。理解版本的局限性知道 Flutter 版本号能帮你判断大致的特性范围和潜在问题但它不能告诉你应用具体用了哪些插件、如何定制的引擎。版本号只是一个起点。最后我个人在实际操作中的体会是“strings grep” 这套组合拳是每一位需要分析 Flutter 应用的技术人员都应该掌握的基本功。它简单、暴力、有效不依赖于任何高级的逆向技巧。下次当你再好奇某个炫酷的 APP 背后是哪个版本的 Flutter 在驱动时不妨用这个方法试一试你会发现技术世界的面纱往往一揭就开。