-
-
逆向已死2:AI直接操作电脑逆向,无差别替代所有真人
-
发表于: 22小时前 9
-
逆向已死2:AI直接操作电脑逆向,无差别替代所有真人
环境说明:本文全部操作基于 Windows 10 x64,命令默认在 Windows 原生 PowerShell 中执行;手机端使用 Pixel 2 XL / Android 10。下文的路径、安装步骤与桌面操作均按这一环境记录。
逆向这十多年 都是靠一次次人工手动操作 解码混淆 寻址,拿到 APK,先用 apktool 拆包、jadx 翻代码,从 Manifest 和入口类里找线索;碰到混淆,就追交叉引用、认控制流,给那些看不出意思的类名和函数名一点点补上含义。走到 JNI,打开 IDA、Ghidra,盯着 ARM 汇编寻址,顺着寄存器和内存里的数据追算法。静态分析卡住,再上 Frida 下钩子、用调试器打断点,把参数和返回值抓出来对照。最后还得写 Python 脚本还原逻辑,接上 ADB 和真机,一遍遍输入、运行、看结果。每走一段,都有一套技能要学、一堆窗口要切,大家真的不容易
而今天本文讲的主题是 MCP 已死,GUI 当立 当AI真的可以直接操作电脑逆向,无差别替代所有真人,以往的不可能正在真的走向现实
GUI 给 AI 自动化逆向打开了无限可能,软件摆在桌面上,模型可以直接动手操控鼠标点击键盘 更厉害的是,IDA 里追函数、终端里跑脚本、手机上验结果,几个窗口之间来回折腾的活,可以让同一个 Agent 接着干。碰到报错和弹窗,它还能看着现场继续判断、尝试处理,原来需要人死守屏幕的那些环节,几乎被强大的GUI通杀
自此再也不需要憋屈地用着专用 MCP 打转,以前的MCP时代 有时软件明明有这个功能,服务器没暴露,模型再想干也得等你补接口。好不容易接上,换个工具又得找插件、查版本,逆向还没开始,环境配置先把人折腾掉半条命。分析流程终于跑了,偏偏桌面弹出个确认窗口,接好的分析接口管不到这里,最后还是你伸手点鼠标——好家伙,说好的 AI 给人打工,自己倒成了 AI 的贴身保姆。
而比起MCP的笨拙繁杂,GUI就是冲着全自动化的王位来的,通过 CUA 读取画面、模拟鼠标键盘,模型可以完全模拟真人点击菜单、拖动窗口,字太小就缩放屏幕;进了 IDA,更是全自动输入地址跳转,沿交叉引用寻址,打开反编译结果继续分析。需要执行命令就切终端,需要验证就切手机,输入答案、点击确认,再看程序给出什么结果。你坐在电脑前怎么操作,它就沿着这套方式看、想、动手。完全模拟真人操作,把整段工作里的人工接力一起替下来,这才是 GUI 要拿下的东西。 古法手搓最磨人的那些来回切换,今天终于迎来了AI的接管。
新王来临 我才终于明白"模型是系统的主体,而不只是组件"的最终含义 ———— AI直接操作电脑逆向,无差别替代所有真人
各位先把咖啡端好,下面请欣赏新王第一次作战 以全自动的战果致敬古法逆向的黄昏绝唱。
下文把这次 AI 自动化逆向 的过程完整展开。
准备工作:ChatGPT 账号、Codex 安装登录与 r0env
先准备账号和环境,然后把桌面操作、分析工具和手机接上:
ChatGPT 账号准备 / Codex 安装与登录
↓
r0env2:检查 Windows 和网络、浏览器环境
↓
CUA:让 AI 读取窗口、点击和输入
↓
jadx / IDA / Ghidra:Java 与 native 分析
↓
QtScrcpy / ADB:连接真实手机
↓
给出 APK → AI 分析与排障 → 原始程序验证
0.1 ChatGPT 账号准备(含买号情况)与 Codex 安装、登录
本次使用已有的可登录账号。若通过购买方式取得账号,先完成登录与恢复方式设置,再进入 Codex。
Windows 原生 PowerShell 下的 Codex CLI 安装与启动步骤
第一步:下载并运行 Windows 安装脚本
从开始菜单打开 Windows PowerShell,在当前用户的临时目录下载官方安装脚本:
Set-Location $env:TEMP
Invoke-WebRequest -UseBasicParsing -Uri 'f5aK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6U0K9r3q4@1k6%4m8@1i4K6u0W2j5$3!0E0i4K6u0r3j5$3!0V1k6i4S2Q4x3V1k6A6L8Y4y4@1j5h3I4D9i4K6u0W2M7s2x3I4i4K6t1%4 -OutFile '.\codex-install.ps1'
powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\codex-install.ps1
第一行切换下载位置,第二行保存安装脚本,第三行启动安装。这里的 -ExecutionPolicy Bypass 仅作用于本次启动的 PowerShell 进程
安装步骤参考:OpenAI 官方 Windows 部署说明:7a7K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6D9k6h3q4J5L8W2)9J5k6h3y4Z5j5i4c8Y4M7s2c8Q4x3X3g2U0L8$3#2Q4x3V1k6V1L8$3y4K6i4K6u0r3k6h3&6@1k6i4u0H3M7X3W2K6k6g2)9J5c8Y4N6A6L8X3c8G2N6%4y4Q4x3X3c8V1k6i4m8D9L8%4W2E0k6h3&6@1。
第二步:确认命令可用
安装结束后,关闭当前 PowerShell 窗口,再打开一个新窗口,检查命令位置和版本:
Get-Command codex
codex --version
如果提示找不到 codex,先检查默认安装位置:
Test-Path "$env:LOCALAPPDATA\Programs\OpenAI\Codex\bin\codex.exe"
返回 True 时,可以先用完整路径检查版本:
& "$env:LOCALAPPDATA\Programs\OpenAI\Codex\bin\codex.exe" --version
如果完整路径可用而短命令不可用,检查 Windows 的“用户环境变量 → Path”是否包含 %LOCALAPPDATA%\Programs\OpenAI\Codex\bin,补齐后重新打开终端。返回 False 则需要回看安装输出,确认是否安装完成或使用了自定义路径。
第三步:登录 ChatGPT 账号
在 PowerShell 中执行:
codex login
按提示在浏览器中完成 ChatGPT 登录与授权,然后回到终端确认:
codex login status
首次写作时,本机记录的版本为 codex-cli 0.164.0,登录方式显示 ChatGPT。这个版本号用于说明当时的实验环境。
登录步骤参考:OpenAI 官方身份验证说明:747K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6D9k6h3q4J5L8W2)9J5k6h3y4Z5j5i4c8Y4M7s2c8Q4x3X3g2U0L8$3#2Q4x3V1k6V1L8$3y4K6i4K6u0r3j5i4g2@1K9l9`.`.。
第四步:进入分析目录并启动任务
为题目和分析文件建立固定目录。下面以用户目录中的 AndroidReverseLab 为例:
$workDir = Join-Path $env:USERPROFILE 'AndroidReverseLab'
New-Item -ItemType Directory -Path $workDir -Force | Out-Null
Set-Location $workDir
codex
进入 Codex 交互界面后,输入下面的任务描述:
检查这台 Windows 的 Python、Java 和已有逆向工具。
为 Android APK 分析补齐环境;每装好一个工具,就用实际样本验证。
现有软件能保留的就保留,把安装和排障结果记下来。
这里先完成 Codex 的安装、登录与任务启动,后续再按本文顺序接入 CUA、逆向工具和手机。CLI 的基本使用方式见 OpenAI 官方 Codex CLI 入门:118K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6D9k6h3q4J5L8W2)9J5k6h3y4Z5j5i4c8Y4M7s2c8Q4x3X3g2U0L8$3#2Q4x3V1k6V1L8$3y4K6i4K6u0r3j5$3I4A6。
0.2 r0env:Windows 版本的安装与配置
项目地址:
仓库首页主要说明 Linux 环境,Windows 版本位于 r0env2-windows/r0env2。对应安装说明:
下载仓库 ZIP,解压后把整个 r0env2 文件夹放到固定位置,例如 C:\Tools\r0env2。准备 Python 3.10+ 和 VC++ x64 运行库,然后运行:
cd C:\Tools\r0env2
.\install.cmd
安装器会准备虚拟环境和浏览器组件。接着填代理端口,先看预览结果:
$env:R0ENV_PROXY = 'http://127.0.0.1:18080'
.\r0env.cmd preview
这里的 18080 要换成代理软件的 HTTP 或混合端口。apply 会改系统设置,要在管理员终端运行。这次补写只检查了现有文件,没有重新执行 apply。
本机已经有 r0env2,放在桌面目录。启动器和 .venv\Scripts\python.exe 都在,r0env.cmd 直接调用这个 Python。
0.3 分析工具的安装顺序
接下来安装 CUA 并测试桌面操作。Java 层使用 jadx;我选择官方 IDA Free 后,又为 ARM 分析补充了 Ghidra。手机通过 QtScrcpy 连接,各工具的下载地址和安装过程见第四节。
一、实验结果

最终结果:原始 Android2.0.apk,Pixel 2 XL / Android 10,输入 flag{sosorryla},显示 Great。
| 做到哪一步 | 这次留下的实际结果 |
|---|---|
| CUA 桌面操作 | 浏览器搜索与滚动,分析工具的窗口操作、输入及截图 |
| IDA Free 9.4 SP1 | 安装排障完成,AMD64 PE 自动分析与 F5 云端反编译通过 |
| jadx 1.5.6 | 中文 GUI、自带 JRE、命令行入口可用,自建 class 与题目 APK 均完成反编译 |
| Ghidra 12.2 | 独立 JDK,ARM32 / ARM64 小样本通过,本题 ARM32 JNI 伪代码已导出 |
| QtScrcpy 4.2.1 | USB 授权、投屏、渲染问题处理、真实手机鼠标控制通过 |
| Android2.0.apk | 从 Java 入口追到 ARM 校验,恢复输入,原始程序实机验证通过 |
| 复现材料 | 15 张截图、源码导出、分析日志、常量提取脚本与 solver |
二、任务起点与环境约束
最初发的要求是“帮我安装 IDA 和 jadx,看看能不能用”。题目是后面才给的。
装完 IDA,我先让它试 F5,随后又问 ARM 的 so 能不能用。当前 Free 版做不了,这才转到 Ghidra。Java 版本和手机投屏也是边用边配的。
任务在对话中逐步补充,最初只要求安装 IDA 和 jadx。安装完成后,我继续询问 ARM 支持情况,AI 随后检查当前 IDA Free 的能力,再配置 Ghidra。
切换工具时,已有进程和测试文件继续保留。处理题目的 so 时使用此前通过 ARM32 / ARM64 测试的 Ghidra,jadx 也沿用自带 JRE 的安装。
三、桌面操作与分析工具的调用方式
本次用到的调用关系:
用户提出目标、补充现场信息
│
▼
AI 读取当前窗口、工具输出和已保存文件,判断下一步
│
┌───────┼─────────────┐
▼ ▼ ▼
CUA 桌面 Shell / Python jadx / Ghidra
看与点击 下载与数据处理 Java / ARM 分析
│ │ │
└───────┼─────────────┘
▼
QtScrcpy / ADB → 原始 APK 真机验证
│
失败:核对输入与调用链
成功:保留截图、代码和日志
通过 jadx 图形界面查看入口,同时用 CLI 批量导出源码。Ghidra 保留分析工程,另用 headless 脚本导出伪代码。手机端由 CUA 完成点击,特殊字符输入出现偏差后,用 ADB 修正文本。
桌面操作的状态检查
list_windows 能找到窗口,get_window_state 能读控件或截图,bring_to_front 可以恢复窗口。可点击、键盘输入返回成功后,仍要再看一次界面。尤其是 Java Swing 应用,有些后台消息被发出去了,窗口没有处理,必须切到前台重试。
一次点击返回 screenshot_context_missing,重新获取截图后才继续操作。窗口最小化时先恢复,布局变化后重新读取控件。有些 Swing 界面更新较慢,调用返回后,对话框才出现。
四、工具安装与配置记录
4.1 CUA:安装与窗口操作验证
CUA 仓库:
当时我提的要求是:
装不用登账号的 cua-driver 即可。
装好本机 driver 后,它开始通过窗口列表、控件树和截图操作桌面。使用过的接口包括 list_windows、get_window_state、bring_to_front、点击、热键、文本输入,以及把一串操作放在一起执行的 run_script。
本机实际装的是 cua-driver 0.37.0,可执行文件在:
%LOCALAPPDATA%\Programs\Cua\cua-driver\bin\cua-driver.exe
写帖时重新执行 --version,仍能返回版本号。后面操作桌面用的就是这套本机组件,没有为了调用它另登 CUA 账号。
浏览器是最先测试的应用。我让它搜索图片,随后检索 FinSectech 的公开资料并整理报告。展示过程又重复过几次,每页加载后稍作停留,再向下滚动,便于观察内容。
Driver 路径与命令行调用
本机组件在 %LOCALAPPDATA%\Programs\Cua\cua-driver\bin 下。目录里除了主程序,还有 UIA 相关可执行文件和 SDK DLL。AI 后面通过 Python 调用这个命令行入口,传入操作名与 JSON 参数,再读取返回的窗口信息。
PowerShell 里这样检查:
$driver = "$env:LOCALAPPDATA\Programs\Cua\cua-driver\bin\cua-driver.exe"
& $driver --version
& $driver call list_windows '{}'
补跑时,版本仍是 cua-driver 0.37.0。后续调用用进程 ID 和窗口句柄指定目标。
QtScrcpy 的按钮可以用控件名称定位;Swing 窗口读不到完整控件时,就看最新截图,再按位置操作。两种方式在同一台桌面里交替使用,遇到窗口状态变化再重新读。后面 Ghidra 的 JNI 入口截图,也是这样重新打开菜单、输入符号名定位出来的。
获取窗口状态并定位控件
QtScrcpy 的主界面能读到按钮名称。AI 当时没有去猜“启动服务”在屏幕上的位置,调用的核心片段是这样:
const app = await cua.getApp({
pid: 7188,
windowId: 5506102
});
await app.getState({
include_screenshot: false,
max_elements: 90
});
await app.click({role: 'button', name: '启动服务'});
代码中的 PID 和窗口句柄取自该轮窗口列表。先获取状态,再点击同一 QtScrcpy 窗口中的按钮;程序重启后重新读取这些标识。
控件树里还能读到设备序列号下拉框、设备列表,以及 startServerBtn、stopAllServerBtn 这类控件标识。服务启动后再列一次窗口,新出现的 Phone-... 才是手机画面。后面的输入和点击转到这个窗口,主配置界面仍然留着。
jadx 和 Ghidra 的情况不一样。有些 Swing 对话框没有出现在普通窗口列表里,它又按进程枚举可见窗口,找到打开文件的对话框,再取一张新截图。文件路径由键盘输入,点开以后再看标题是否换成了目标 APK 或工程名。
这期间出现过一个很具体的错误:screenshot_context_missing。当时一次读取没有带截图,紧接着却要求按坐标点击。Driver 拒绝了这个动作,返回信息要求先获取截图。它随后重新读取窗口,才继续点。
后台输入未生效后的前台重试
第一次向 jadx 发送 Ctrl+O,接口返回消息已投递,截图仍停留在测试类。改用前台输入后出现文件选择器。此前测试工程尚未关闭,先处理“保存项目”提示,再选择 APK 路径。
手机投屏也经历过类似情况。后台点击能让输入框获得焦点,后台文本输入却没进字段。切到前台后文字出现了,后来读控件树才发现括号不对。这几次操作没有一次返回值就结束,后面都跟着读窗口或截图。
4.2 IDA:安装、运行库与文件关联
IDA Free 下载入口:
接下来我让它安装 IDA 和 jadx 1.5.6,顺便试一下。
我起初给的 IDA 名称是 IDA94ProBable_CN。这个名字没找到可核验的来源,它问了版本选择。我选了官方 IDA Free,随后完成 Hex-Rays 账号登录,才继续从官方渠道下载。
最后装好的是 IDA Free 9.4 SP1。下载目录里还留着 ida-free-pc_94_x64win.exe,大小为 431,243,128 字节。
运行库与用户级文件关联
系统已有的 VC++ 运行库版本是 14.51.36247.0,比安装包附带版本新。运行库安装器返回 1638,拒绝降级。安装流程因此返回非零状态。
它继续检查运行库和程序本体,保留了系统较新的运行库。另一个问题是全局文件关联写入权限不足,随后补建当前用户的 .idb、.i64 关联和桌面快捷方式。
安装位置落在当前用户目录:
%LOCALAPPDATA%\Programs\IDA-Free-9.4
签名检查最初缺 Sectigo 中间证书。补齐后,Windows 缓存模式的 Authenticode 验证成功。
装完后,我又问:“你看看现在可以用了吗?”
它拿 Windows 自带 whoami.exe 的副本做静态测试,导入 AMD64 PE,等自动分析完成,再按 F5。测试副本没有被执行。

IDA 安装验证截图。左侧函数列表、下方自动分析输出和中间 C 伪代码,都是当时留下来的。
日志里能看到:
Detected file format: Portable executable for AMD64 (PE)
Hex-Rays Cloud Decompiler plugin has been loaded
The initial autoanalysis has been finished.
F5 得到了图中的伪代码。当前插件提示支持 x64 云端反编译,需要联网,禁用批量反编译。
程序、许可证与数据库路径
程序文件位于 %LOCALAPPDATA%\Programs\IDA-Free-9.4。免费许可证从官方获取后,放在 %APPDATA%\Hex-Rays\IDA Pro 对应位置;分析数据库另存到样本目录。
全局文件关联写入失败后,改在 HKCU\Software\Classes 下建立当前用户的关联。
补跑时还查了运行库注册项:
Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64' |
Select-Object Version, Installed
实际仍显示 v14.51.36247.00、Installed = 1。这与当时“已有较新运行库,安装器拒绝降级”的排查结果一致。
IDA 安装包签名也重新查了一次:
Get-AuthenticodeSignature .\ida-free-pc_94_x64win.exe
返回 Valid,签名主体为 Hex-Rays。原来的较新运行库继续保留。
使用 AMD64 PE 验证分析功能
我问“现在可以用了吗”时,它没有只报一个版本号。测试文件取的是 Windows 自带 whoami.exe 的副本,另存到测试目录后交给 IDA。
日志先确认文件格式为 AMD64 PE,再装入 metapc 处理器模块。后面完成导入、异常目录和类型信息处理,才出现自动分析结束的提示。这个阶段会花一点时间,刚看到 IDA 窗口时函数列表还没有稳定下来。
F5 之后,图里显示了入口函数的伪代码。样本目录中也出现了 IDA 写出的 .id0、.id1、.nam、.til 等数据库文件。安装、分析数据库和云端反编译分成了几步,日志都留在各自的阶段。
当时安装器的异常也就是这样追的。运行库安装步骤报 1638,它去看系统已经装了什么;全局关联写入失败,则转去处理用户级关联。主程序后来能打开样本,F5 也能工作,这两处安装问题才算处理完。
用户需要动手的地方主要是 Hex-Rays 登录和版本选择。我选官方 Free 以后,AI 才继续下载和安装。它最初拿到的那个 IDA94ProBable_CN 名称,没有被写成“已安装的 IDA Pro 中文版”。
4.3 jadx:独立 JRE 与反编译测试
jadx 1.5.6 发布页:
jadx 用的是 1.5.6 自带 JRE 的 Windows 包:
jadx-gui-1.5.6-with-jre-win.zip
99,122,202 字节
电脑原本装着 Java 8。jadx 直接用包里的 JRE 25.0.3,建立桌面快捷方式后,把 GUI 切成中文。
命令行包装的核心是:
"%~dp0jre\bin\java.exe" --enable-native-access=ALL-UNNAMED ^
-cp "%~dp0lib\*" jadx.cli.JadxCLI %*
相对路径从 jadx 自己的目录找 Java,机器上别的软件仍可以使用原来的 Java 8。
验收也做了一次。它创建一个简单的 Java 类,放进字符串返回和整数相加两个方法,再分别看命令行输出和 GUI 反编译结果。

这张图分析的是自建 JadxSmokeTest,后面的 APK 还没有登场。
我接着问:
ARM 的 so 可以吗?
Java 运行时版本检查
补跑时分别执行三个 Java 的 -version,输出如下:
系统原有 Java:
java version "1.8.0_202"
jadx 自带 Java:
java version "25.0.3"
Ghidra 独立 Java:
openjdk version "25.0.4.1"
jadx 的命令行包装直接调用 jre\bin\java.exe,GUI 也从自己的目录启动。
当时用来验收的测试类也很小,源码就是:
public class JadxSmokeTest {
public static String greeting(String name) {
return "Hello, " + name;
}
public static int add(int a, int b) {
return a + b;
}
}
反编译后,两个方法都能恢复。命令行和中文 GUI 也分别试过。
下载文件与分析目录
下载完成后,压缩包留在 Downloads。jadx 解压到用户的 Programs 目录,反编译输出另放在题目目录中。后面查文件时,可以直接区分原包、自带运行时和 Java 导出结果。
本次几份包的文件名都保留了下来:
ida-free-pc_94_x64win.exe
jadx-gui-1.5.6-with-jre-win.zip
ghidra_12.2_PUBLIC_20261016.zip
ghidra-jdk25.zip
QtScrcpy-win-x64-v4.2.1.zip
AI 下载 QtScrcpy 时用 Python 处理网络请求,最初的下载循环每次读 1MB。后来补跑摘要检查,改用 hashlib.file_digest 逐块读取。Ghidra 的包有五百多 MB,也没有一次把整个压缩包读进内存。
摘要计算的部分很短:
from pathlib import Path
import hashlib
archive = Path('QtScrcpy-win-x64-v4.2.1.zip')
with archive.open('rb') as stream:
digest = hashlib.file_digest(stream, 'sha256').hexdigest()
print(digest)
这是按补跑脚本整理的写法,使用本机的 Python 3.12。得到的值记录到 environment-rerun.json,包大小和版本检查结果也在同一份文件里。Ghidra、JDK、QtScrcpy 的摘要重新算过,与安装时留下的官方发布记录一致。
4.4 Ghidra:独立 JDK 与 ARM 分析验证
当前这份 IDA Free 不支持本题的 ARM 库,需要另配工具。
我又问装其他版本会不会冲突,随后同意:
ghidra 也行,不冲突就可以安装。
Ghidra 和 JDK 的仓库:
它为 Ghidra 单独准备了 JDK,装好后几套运行时是这样放的:
IDA Free 9.4 SP1
jadx 1.5.6 → 使用自带 JRE 25.0.3
Ghidra 12.2 → 使用独立 Temurin JDK 25.0.4.1+1
系统 Java 8 → 保留
下载目录里保留着两个包:Ghidra 压缩包 569,712,708 字节,JDK 压缩包 141,167,264 字节。安装时两者的 SHA-256 都核对了官方发布记录,完整摘要附在环境记录里。
机器只有 2GB 内存,浏览器和分析工具同时运行时资源较紧张。AI 为 Ghidra 编写独立启动器,将最大 Java 堆设为 512MB,并限制 CPU 并发参数。
启动器结构如下,路径在这里改成了环境变量写法:
@echo off
setlocal
set "JAVA_HOME=%LOCALAPPDATA%\Programs\Ghidra-JDK25\jdk-25.0.4.1+1"
set "GHIDRA_MAXMEM=512M"
set "GHIDRA_GUI_JAVA_OPTIONS=-Dcpu.core.limit=2"
set "PATH=%JAVA_HOME%\bin;%PATH%"
call "%~dp0ghidraRun.bat" %*
Ghidra 从这里指定 JDK,setlocal 把环境变量限制在这次启动中。桌面快捷方式外面又包了一层隐藏控制台的启动入口。
ARM32 / ARM64 测试库
为了确认 ARM 分析能力,它生成了最小 ARM32、ARM64 ELF 共享对象,各导出一个 add_numbers 函数,然后导入 Ghidra,自动分析并导出 C 伪代码。

左边是 add、ret,右边恢复成了 return param_1 + param_2;。
两个架构的小样本都通过了。附件里保留了测试库、生成器、验证脚本与反编译结果。它们用于静态分析验收,没有被拿到手机上运行。
项目选择器曾出现异常:

快速操作项目选择器时遇到的 Swing Update Manager 异常。
关闭提示后,项目正常打开,已有分析结果保留。
安装包摘要与启动参数
Ghidra 与 JDK 原来的压缩包还在。补跑时重新流式计算 SHA-256,与安装记录中已核对的官方摘要再次比较,二者都一致:
Ghidra 12.2
2c2cd776db3c1f6a097ba418922378ac3649cadd5e844a6f5f088ae628a7be67
Temurin JDK 25
00c847d804f4a78e9f04f2683faf14fed898535b177b7fc704486cb0284e9283
图形界面启动器用 512MB 堆;后来的这次 headless 复跑则指定 384MB,并将分析并发限制为 2。jadx 的重跑先完成,再启动新的 Ghidra 分析项目,减少两套 Java 分析任务一起占内存的压力。
系统的 JAVA_HOME 没有永久改动,Ghidra 只在自己的启动器中选 JDK。
测试库的导入与反编译结果
前面的 IDA 测的是 Windows PE。到了 Ghidra,这次特意准备 ELF 共享对象,分别采用 ARM32 和 ARM64,导出函数名都叫 add_numbers。
ARM64 测试图里可以看到加法指令与 ret,反编译结果是两个参数相加。ARM32 的输出也恢复了相同运算。生成这两个文件的代码、测试库本身和导出脚本都在环境附件里。
这一步由 AI 生成样本并执行分析。我没有另外给它一个已知答案的 Android so。等到题目中出现 lib/armeabi-v7a/libNative.so 时,安装过的工具已经有对应架构的测试结果。
独立复测工程与伪代码导出
复测时,CodeBrowser 中仍打开着 NativeProject。AI 另建 ReplayNative 工程,导入新导出的 so,原窗口保持打开。
实际 headless 参数包括:
analyzeHeadless.bat <复跑目录> ReplayNative
-import <jadx 导出的 libNative.so>
-scriptPath <导出脚本目录>
-postScript ExportNative.java <新的伪代码目录>
-max-cpu 2
-analysisTimeoutPerFile 90
-import 指向新导出的库,-postScript 指定分析完成后要跑的 Java 脚本。这一轮使用 384MB 最大堆,jadx 的重跑先结束,再开始 Ghidra,原先打开的 GUI 则继续保留。
导出脚本调用的是 Ghidra 自带反编译器。核心调用如下,完整文件在附件 ExportNative.java:
DecompInterface decompiler = new DecompInterface();
decompiler.openProgram(currentProgram);
DecompileResults results =
decompiler.decompileFunction(function, 30, monitor);
String code = results.getDecompiledFunction().getC();
脚本遍历函数并写出 C 文件。新工程完成后,AI 又把 JNI、Init、First、Second、Third 的输出与首次文件比较,五个结果一致。新日志没有覆盖旧日志,后来整理文章时还能分别找到。
4.5 QtScrcpy:设备连接与渲染排障
QtScrcpy 4.2.1 发布页:
接着装 QtScrcpy。
使用的是官方 4.2.1 Windows x64 便携包,大小 32,450,642 字节。安装时核对了 SHA-256:
130f5c6f5d2dde1afb58775daf65060e9be007e7825f70c26c7121414f175c82
软件自带 ADB、Qt 和视频相关依赖。解压到当前用户的程序目录,建立快捷方式,再把用户配置中的语言从自动改成 zh_CN。自带 ADB 的版本是 1.0.41 / 33.0.2-8557947。

最初这里没有手机。程序打开了,但投屏和鼠标控制还没法验。
我在手机上打开 USB 调试,回了一句:“现在开启usb调试了。”
接下来的检查认出了 Pixel 2 XL。设备状态从 unauthorized 变成 device,QtScrcpy 的列表里也出现了手机。点启动服务后,日志显示服务端文件已经推送,设备信息也正确:
[server] INFO: Device: [Google] google Pixel 2 XL (Android 10)
server start finish in 1.504s
电脑端随后出现渲染错误:
QOpenGLShader::compile(Fragment)
No precision specified for (float)
shader program is not linked
此时设备连接和服务启动均已完成,故障位于电脑端渲染。
它接着去读 QtScrcpy 的 main.cpp,找到选择 OpenGL 后端的分支:配置值为 0 时设置 Qt::AA_UseSoftwareOpenGL。于是先停止服务、备份配置,把用户目录下 config.ini 的这一项改掉:
UseDesktopOpenGL=0
重启软件并启动服务后,手机设置页面正常显示。
另一次启动停在服务端文件推送之后。关闭自动刷新并重新启动服务后恢复连接,这一处没有继续单独定位。
手机控制验证
它打开手机设置,通过投屏点击“网络和互联网”,再读一次前台 Activity。页面已经切过去了。

设置页面真的切换了,鼠标控制验证通过。
完成手机控制测试后,我提供了题目链接:
778K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6J5x3s2W2K6N6h3g2Q4x3V1k6y4L8$3u0A6L8r3g2o6g2p5k6Q4x3V1k6T1L8r3!0T1i4K6u0r3L8h3q4A6L8W2)9J5c8V1q4F1k6s2u0G2K9h3c8m8L8r3N6G2M7X3W2@1K9r3#2Q4x3V1k6d9k6h3N6A6M7%4c8W2M7V1#2S2j5$3S2A6L8X3g2Q4x3V1k6m8L8X3c8J5L8$3W2V1x3W2)9J5k6e0m8Q4x3X3g2S2M7r3D9`.
要求是用 CUA 操作已经打开的手机、jadx、IDA 等工具,把题解出来。
用户配置文件与渲染选项
便携包里有 adb.exe 和 scrcpy-server,也带了 Qt DLL。软件渲染用的 opengl32sw.dll 同样在包内。
包内有默认 config/config.ini,实际修改的是用户目录里的:
%APPDATA%\QtScrcpy\QtScrcpy\config\config.ini
相关设置是:
Language=zh_CN
UseDesktopOpenGL=0
它先备份配置,再停止服务并退出软件。改好文件后重新启动,渲染选项随进程一起生效。
这次补跑一开始,ADB 又看见了设备,但状态为 offline。执行 adb reconnect offline 后,状态恢复到 device,再重启 QtScrcpy 服务,手机画面重新出现。已有的软件渲染配置仍然有效,这一轮没有再去改图形设置。
ADB 设备状态与投屏服务检查
手机刚出现时状态是 unauthorized。确认调试授权后,设备变成 device,日志随后才打印出 Pixel 2 XL 和 Android 10。
当时检查用的 ADB 是 QtScrcpy 自带版本。命令按下面的方式定位到它,省得终端意外调用另一份 ADB:
$qtDir = Join-Path $env:LOCALAPPDATA 'Programs\QtScrcpy-4.2.1\QtScrcpy-win-x64-v4.2.1'
$adbExe = Join-Path $qtDir 'adb.exe'
& $adbExe version
& $adbExe devices -l
服务启动卡住时,它又查看手机进程和 ADB reverse 列表,区分服务端有没有真正起来。等日志出现设备信息和视频编码器名称,电脑端仍然报 shader 错误,排查才转到 OpenGL。
main.cpp 中的分支给出了配置值和 Qt 属性的对应关系。软件渲染对应 Qt::AA_UseSoftwareOpenGL,所以最后落到 UseDesktopOpenGL=0。修改用户配置后重启进程,手机设置页面出来了。窗口里再点“网络和互联网”,前台 Activity 也发生变化。
补跑时又遇到过离线状态。这一轮没有重新安装驱动,而是先执行:
& $adbExe reconnect offline
& $adbExe devices -l
设备重新显示 device。随后停止 QtScrcpy 里旧的服务,再启动,出现一个新的手机投屏窗口。因为窗口句柄已经变化,接下来的 CUA 操作也改用新句柄。
五、任务描述与人工补充
这些需求是聊天时逐步补的。装完工具,我问能不能用;接着问 ARM 和环境冲突。给 APK 时,要求就很明确了:用桌面上已经打开的工具和手机解题。
写帖时把当时的需求合在一起,大致是下面这样:
在当前 Windows 机器上准备 Android 逆向环境。
沿用已安装的软件和 Java,新增工具尽量独立配置。
安装完成后,用实际样本检查需要的分析能力。
分析给定的公开 CTF APK,允许使用 CUA、Shell、jadx、Ghidra、
Python、ADB 和真实手机。保留关键文件与工具输出。
求出原始 APK 接受的输入,在手机上输入并验证。
若验证失败,检查程序实际收到的内容和完整校验链,再继续分析。
交付源码位置、推导过程、复核脚本与成功截图。
提交任务时,我提供仓库链接,并要求使用已打开的工具和手机解题,没有提供输入长度或 JNI 函数名。
长度 15、模 3 分组以及比较目标均来自后续样本分析。首次出现 Wrong 后,AI 继续检查当前输入,定位到括号转换。
六、Android2.0.apk 分析过程
6.1 Java 入口与 JNI 调用
它下载题目,计算摘要,并枚举 APK 内容。样本大小 1,427,355 字节,SHA-256 是:
16aad71017773a8cd536d997d2db53f7c7d1eea8a77d24186c9507051b0266ac
APK 里只有一个目标 native 库:
lib/armeabi-v7a/libNative.so
它用 ADB 把原始 APK 装到手机,启动 com.example.test.ctf03/.MainActivity。

一个输入框,一个确认按钮。
桌面这边,jadx 通过文件选择器打开 APK,搜索 MainActivity。命令行同时导出源码和资源,后面的定位与提取用这些本地文件完成。这台机器内存有限,本次 jadx 命令行使用了 384MB 堆、单工作线程。

按钮回调只有几行:
String str = MainActivity.this.pwd.getText().toString();
int result = JNI.getResult(str);
MainActivity.this.Show(result);
Show() 根据返回值显示 Wrong 或 Great。再看 JNI.java:
public static native int getResult(String str);
static {
System.loadLibrary("Native");
}
输入没有经过 Java 层额外处理,直接进了 native。目标函数随即缩小到:
Java_com_example_test_ctf03_JNI_getResult
Manifest 与应用入口定位
AI 先读了 AndroidManifest.xml,包名是 com.example.test.ctf03,主 Activity 也直接写在 Manifest 里。然后在导出源码中搜索 native、loadLibrary 和按钮监听。命中的应用代码很少,很快落到 MainActivity.java 和 JNI.java。
点击确认时,pwd.getText().toString() 交给 JNI.getResult()。Show() 的两个分支分别写入 Wrong 和 Great。返回值 1 就是要走到的成功路径。
资源导出目录包含 lib/armeabi-v7a/libNative.so,与 System.loadLibrary("Native") 对应。随后提取该库并交给 Ghidra,未继续逐个展开 Android support 库中的类。
打开 APK 前,jadx 仍停留在测试工程 JadxSmokeTest。处理保存提示和独立的 Swing 文件对话框后,窗口标题变为 Android2.0 - jadx-gui,再搜索 MainActivity 进入本题源码。
jadx 命令行导出
目录沿用本文的安装位置,PowerShell 中可以把参数放进数组,避免一长串命令难读:
$caseDir = Join-Path $env:USERPROFILE 'Documents\MobileCTF-Android2'
$jadxDir = Join-Path $env:LOCALAPPDATA 'Programs\jadx-1.5.6'
$javaExe = Join-Path $jadxDir 'jre\bin\java.exe'
$jadxArgs = @(
'-Xmx384m'
'-XX:ActiveProcessorCount=2'
'--enable-native-access=ALL-UNNAMED'
'-cp'
(Join-Path $jadxDir 'lib\*')
'jadx.cli.JadxCLI'
'-j'
'1'
'-d'
(Join-Path $caseDir 'replay-20261025\jadx')
(Join-Path $caseDir 'Android2.0.apk')
)
& $javaExe @jadxArgs
-Xmx384m 限制 Java 堆,-j 1 让 jadx 使用一个工作线程。日志结束于 INFO - done。入口源码和 so 都在这个新目录中,原来第一次分析留下的目录没有覆盖。
6.2 ARM 校验逻辑与常量恢复
Ghidra 将这个库识别为 ARM:LE:32:v8。它通过 headless 分析和 Java 脚本导出函数伪代码,保留工程,又在桌面打开对应库。

这张图展示导入的 libNative.so。函数伪代码在附件 native 目录中。
JNI 首先取得字符串字节,要求 strlen(input) == 15。随后调用 Init,按下标模 3 把输入拆成三组:
A = input[0], input[3], input[6], input[9], input[12]
B = input[1], input[4], input[7], input[10], input[13]
C = input[2], input[5], input[8], input[11], input[14]
每组 5 字节,末尾补零。接着对三组依次做校验。为了便于阅读,下文把反编译变量改成了 A、B、C:
// 第一组:只处理前四字节,写回 char 时截断为 8 位
for (int i = 0; i < 4; i++)
A[i] = (A[i] << 1) ^ 0x80;
if (strcmp(A, "LN^dl") != 0) return 0;
// 第二组:这里的 A 已经变换过
for (int i = 0; i < 4; i++)
B[i] ^= A[i];
if (strcmp(B, targetB) != 0) return 0;
// 第三组:这里的 B 也已经变换过
for (int i = 0; i < 4; i++)
C[i] ^= B[i];
if (strcmp(C, targetC) != 0) return 0;
return 1;
循环只处理前 4 个字节,strcmp 比较范围仍包含第 5 字节。B、C 的异或又依赖上一阶段变换后的缓冲区。JNI 主函数内联了后两个循环,单独导出的 Second、Third 中也能看到相同运算。
JNI 函数表偏移 0x2a4 的识别
原始伪代码没有直接写 GetStringUTFChars,开头是一次通过函数表的间接调用:
__s = (char *)(**(code **)(*param_1 + 0x2a4))
(param_1, param_3, 0);
sVar1 = strlen(__s);
param_1 是 JNI 环境指针,param_3 对应传进来的 Java 字符串。为了把偏移和函数名对上,补文时又从本机 JDK 的 include/jni.h 数了一遍函数表槽位。
GetStringUTFChars 位于索引 169。这个库是 32 位,函数指针占 4 字节:
169 × 4 = 676 = 0x2a4
它附近的条目依次为:
NewStringUTF
GetStringUTFLength
GetStringUTFChars
ReleaseStringUTFChars
GetArrayLength
取到字节指针以后立刻调用 strlen,接着比较 0xf,也与这一路调用相符。本文答案全是 ASCII,界面上 15 个字符正好对应这里的 15 个字节。
Init 的分组索引
Init 循环中的两个量是 i % 3 和 i / 3。余数决定写哪个缓冲区,商决定在组里的位置。
以开头六个字符为例:
| 输入下标 | 字符 | 组内位置 |
|---|---|---|
| 0 | f |
A[0] |
| 1 | l |
B[0] |
| 2 | a |
C[0] |
| 3 | g |
A[1] |
| 4 | { |
B[1] |
| 5 | s |
C[1] |
原始 Init.c 里,三条写入路径最后都会回到同一个循环。循环结束后,各组下标 5 处补 0,于是三个缓冲区都能交给 strcmp。
First 只修改 A[0] 到 A[3]。A[4] 仍然是原输入下标 12 的字符,比较目标末尾是 l,这个位置就被直接约束住了。B[4]、C[4] 同样保留原字节,分别落到输入下标 13、14。
比较目标中的控制字节
两处 strcmp 的目标指针显示为相对地址表达式。它又读取 ELF 原始字节,解析段表和小端常量。
本次 Ghidra 基址为 0x10000。常量所在的第一个 PT_LOAD 段,在这些位置上文件偏移与 ELF 段虚拟地址一致。核算结果如下:
DWORD[0x1120c] = 0x16b2
0x111d6 + 0x16b2 = 0x12888
DWORD[0x11210] = 0x1698
0x111f6 + 0x1698 = 0x1288e
| 目标 | ELF 文件偏移 | Ghidra 地址 | 比较的 5 个字节 |
|---|---|---|---|
| A | 0x1074 |
0x11074 |
4c 4e 5e 64 6c,即 LN^dl |
| B | 0x2888 |
0x12888 |
20 35 2d 16 61 |
| C | 0x288e |
0x1288e |
41 46 42 6f 7d,即 AFBo} |
第二行的 0x16 很关键,只看可打印字符串会漏掉它。附件 inspect_constants.py 可以独立读取 APK 中的 ELF,复核这些地址和字节。
在可打印 ASCII 范围内,逆运算如下:
A[i] = (targetA[i] ^ 0x80) >> 1
B[i] = targetB[i] ^ targetA[i]
C[i] = targetC[i] ^ targetB[i]
每组最后一个字节直接取比较目标的最后一字节。还原出来:
A = fgorl
B = l{sra
C = asoy}
按照之前的分组位置交错拼回去:
f l a | g { s | o s o | r r y | l a }
flag{sosorryla}
solve.py 按 JNI 顺序重建校验,同时做逆运算。随后把答案的每个位置分别翻转最低位,15 个反例都被拒绝。
左移丢失最高位,所以第一组前四个字节的方程还有高位候选。这里按 ASCII flag 格式取值。
第一组逆运算
First 的操作写回 char,所以 Python 中保留低 8 位:
after = ((before << 1) ^ 0x80) & 0xff
对前四个目标字节,逆运算的低位候选分别是:
| 目标 | 异或 0x80 | 再右移一位 | 字符 |
|---|---|---|---|
4c |
cc |
66 |
f |
4e |
ce |
67 |
g |
5e |
de |
6f |
o |
64 |
e4 |
72 |
r |
第五字节从目标末尾直接取 6c,得到 A 原文 fgorl。
B 用的 A 已经变成 4c 4e 5e 64 6c。例如 B[1]:0x35 XOR 0x4e = 0x7b,就是左花括号。C[1] 则是 0x46 XOR 0x35 = 0x73,恢复 s。
组间依赖在 JNI 主函数里看得很清楚。First 返回以后,A 没有恢复;B 的原缓冲区被异或覆盖,通过比较后直接作为下一轮的输入。脚本也按这个顺序修改三个 bytearray,没有另造一套不同的计算过程。
从比较指针换算文件偏移
本次 Ghidra 给程序加了 0x10000 的基址。要读文件里的值,先从分析地址减去这个基址,得到 ELF 虚拟地址,再通过 PT_LOAD 段找到文件偏移:
文件偏移 = ELF 虚拟地址 - 段的 p_vaddr + 段的 p_offset
本库第一个加载段的 p_vaddr 和 p_offset 都是 0,文件大小为 0x2894。三组常量及两项相对值都位于这个段内,所以这些位置上减去基址后,可以直接作为文件偏移读取。
例如 DAT_0001120c 先变成文件偏移 0x120c。读取 4 字节小端值是 0x16b2,再与伪代码中的 0x111d6 相加,得到分析地址 0x12888。它在文件中是 0x2888,读出来的内容如下:
20 35 2d 16 61 00
最后的 00 是字符串结束符。五个数据字节中,开头是空格,第四个是控制字节 0x16,所以这一组用十六进制表示更清楚。
动态符号表与 Thumb 状态位
这轮补跑还直接枚举了 ELF 的动态符号表。文件头是 ELF32、小端,e_machine = 40,对应 ARM。动态导出值如下:
| 符号 | ELF 符号值 | 本次 Ghidra 代码地址 |
|---|---|---|
First |
0x104d |
0x1104c |
Second |
0x107d |
0x1107c |
Third |
0x10b1 |
0x110b0 |
Init |
0x10e5 |
0x110e4 |
Java_com_example_test_ctf03_JNI_getResult |
0x116d |
0x1116c |
这里的奇数函数值带有 Thumb 状态标记,定位代码时去掉最低位,再加上本次的程序基址 0x10000。例如 JNI 入口:
(0x116d & ~1) + 0x10000 = 0x1116c

补跑截图:左侧定位到 0x1116c,右侧已出现 strlen、长度 0xf、Init 与 First 等调用。
GUI 定位之外,Ghidra 也对新导出的库重新创建了分析项目,重新自动分析并导出函数。日志有 Post-analysis succeeded、Save succeeded 和 Import succeeded。这轮的 JNI、Init、First、Second、Third 五个输出文件,与首次导出的对应内容逐个比较,全部一致。
逐列还原与脚本输出
新的复核脚本从 APK 重新读 ELF、解析符号和目标常量,再按列打印:
index | A B C | original input triplet
0 | 66 6c 61 | fla
1 | 67 7b 73 | g{s
2 | 6f 73 6f | oso
3 | 72 72 79 | rry
4 | 6c 61 7d | la}

真实终端输出。目标常量直接读取自样本,下面才是三组恢复、交错拼接与正反例校验。
第一列可以手算:(0x4c XOR 0x80) >> 1 = 0x66,恢复 f;0x20 XOR 0x4c = 0x6c,恢复 l;0x41 XOR 0x20 = 0x61,恢复 a。最后一列不经过四次循环,直接恢复 l、a、}。
6.3 首次真机验证与输入偏差定位
候选答案出来后,它开始操作手机投屏,点击输入框,输入字符串,再点“确认”。
结果是 Wrong。
接下来它读了一次手机控件树。

这张图保存的是首次输入后的状态。随后点击出现 Wrong,那一帧没有另存图片。
通过 Android 控件树读取,com.example.test.ctf03:id/pwd 的实际文本是:
flag[sosorryla]
投屏键盘把 {} 映射成了 []。
UI 文本显示两处括号发生变化。AI 用 CUA 选中原输入,通过 ADB 修正花括号,再点击投屏窗口中的确认按钮。
再次提交后显示 Great,对应文章开头的截图。收起键盘后,结果保留在手机窗口中。
方括号输入的失败分支
把 flag[sosorryla] 按原算法拆开,A 仍然是 fgorl,第一组会通过。变化在 B[1],这里从 { 变成了 [:
正确:0x7b XOR 0x4e = 0x35
错误:0x5b XOR 0x4e = 0x15
目标:0x35
B 的比较会失败,JNI 从这里返回 0。最后一字节的 ] 也与目标 } 不同,不过按这段函数的顺序,还没有走到 C 的比较。
补文时,我让它把已还原的检查过程再写成分支跟踪脚本。输入长度、A、B、C 分别记录停止位置,得到下面的输出。这一组是本地算法检查;上面的 Wrong 和 Great 仍是手机结果。
correct 15 bytes flag{sosorryla} -> accepted
square_brackets 15 bytes flag[sosorryla] -> B
short 14 bytes flag{sosorryla -> length
long 16 bytes flag{sosorryla}x -> length
A_first 15 bytes glag{sosorryla} -> A
A_fifth 15 bytes flag{sosorryma} -> A
B_fifth 15 bytes flag{sosorrylb} -> B
C_fifth 15 bytes flag{sosorryla] -> C
后三项分别修改三组的第五字节。它们没有参与循环变换,仍会被 strcmp 比较。脚本保存为 walk_checks.py,分组和变换后字节写入 branch-trace.json。
6.4 原始 App 的正反例复测
补跑时先重启题目进程,输入框和结果区都回到空白。
随后故意输入方括号版本 flag[sosorryla],用 CUA 点击确认。界面显示 Wrong,控件树也读到了相同内容:
{"result": "Wrong", "pwd": "flag[sosorryla]"}

这张是本轮复跑重新保存的 Wrong。与首次偶然遇到键盘映射问题不同,本轮故意输入方括号,用来做反例。
接着通过投屏选中输入,ADB 补正为花括号版本,再由 CUA 点击确认。手机显示 Great,控件树读到:
{"result": "Great", "pwd": "flag{sosorryla}"}

本轮复跑的成功画面。两次输入用的是同一个原始 APK,没有在失败和成功之间替换程序。
七、复测记录与内存分配问题
新一轮分析留在 attachments/replay-20261025/。solver-trace.txt 是终端输出,动态符号和三组常量记录在 derivation.json 中。手机的两次结果分别存为 phone-wrong.json、phone-great.json。
solve.py 还逐位置翻转了答案的最低位,15 个反例都被拒绝。终端结果是:
FLAG: flag{sosorryla}
PASS: solution accepted; square brackets and all 15 one-bit mutations rejected.
Native 代码中的三个缓冲区均通过 malloc(1) 分配。Init 随后分别写入 5 个数据字节和 1 个结束符,超出请求的分配大小。本题求解未利用这一问题,相关代码保留在 Init.c 和 JNI 导出文件中。
八、屏幕继续亮着,这一轮,让新王AI+GUI来。
咖啡还在桌上,使用多年的pixel机已经亮出了 Great。从 APK 到 Java,再追进 ARM 校验,写脚本、跑答案、回到真机验证,这一趟终于走通。中间投屏出了毛病,答案也吃过Wrong,AI 接着查、接着改,最后把那两个闹事的括号纠正回来。最让人兴奋震惊的,就是以前“这里还得手动处理”的脏活累活,现在也开始被它接过去了。
此时此刻,再看“模型是系统的主体,而不只是组件”,味道便已完全不同。IDA 还是那个 IDA,手机还是那部手机,推进任务的主体已经换成了模型。GUI 接上它的眼睛和手,Shell、Python、ADB 随时接力,读完代码就去执行,执行出了问题就回头查。那些过去要人守着、盯着、伸手接一下的地方,开始连成一段由 AI 推进的工作。MCP已死,GUI当立 当向前的时代终于把目光从工具列表移到了整个操作流程 实现全覆盖。
模型与框架皆为黑盒,唯有具体领域的真实评测作数。这次验证终于落在了手上:错误输入显示 Wrong,修正后显示 Great,补跑时又在同一个原始 APK 上走了一
遍。反编译窗口里的判断,经过脚本计算,最后变成了屏幕上的结果。一路分析到这里,再由 AI 操作界面最好按下确认,收尾
新王的第一场打完了,桌上还有下一份样本,就在这些操作里:今天接过一个窗口,接着贯通一段流程,再把整张桌子上的活一段段接过去。过去最熟练的动作,是人在几个工具之间反复切换;往后更让人期待的,是给出任务以后,看模型自己把这些工具用起来。各位,咖啡喝完可以续,那些古法手搓熬过的夜,也终于有了另一种过法。
屏幕继续亮着,这一轮,让新王来。
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。