1. 为什么我要让 Antigravity 学会“拆”App
先说清楚这套东西是什么、能做什么、适合谁。Antigravity 是 Google DeepMind 推出的 AI 编程助手,你可以把它理解成一个住在你电脑里的资深程序员,写代码、调 bug、查文档都很顺手。但它有个明显短板:不会用逆向工具。你让它分析一个 APK,它只能给你讲理论,没法真的把包拆开、把函数反编译出来、把运行时行为 Hook 下来。
我想要的是一句话丢过去——“帮我看看这个 APK 里的加密逻辑”——然后它自己调 IDA Pro 反编译、用 Frida 挂 Hook、通过 ADB 把结果拉回来。要做到这一点,核心只有一件事:用 MCP(Model Context Protocol,模型上下文协议)把本地逆向工具的能力“喂”给 AI。MCP 说白了就是一套让 AI 调用外部工具的标准接口,每个工具是一个电器,MCP 是那个万能遥控器,AI 拿着遥控器就能操作任何接入的工具。
这套工作流适合谁?适合做 Android 安全分析、协议逆向、CTF 的工程师,也适合想把重复性逆向操作自动化的开发者。不适合完全没碰过命令行的纯小白,因为你需要至少能看懂 Python 脚本和 JSON 配置。但只要你愿意跟着敲一遍,整个链路是可以复现的。
我试过把这套流程跑在一个 Cocos2d-x 游戏 App 上,从静态分析到动态 Hook 再到批量解密,全程 AI 自主决策,我只负责看。下面把配置、脚本、验证命令和踩过的坑全部摊开讲。
2. 前置准备:TaoToken 接入与 MCP 环境搭建
在讲逆向工具接入之前,得先把 AI 侧的模型通道打通。Antigravity 本身需要一个大模型后端来驱动推理,我用的是 TaoToken 提供的 API 通道,它兼容 OpenAI 风格的接口,配置起来比较直接。官网地址是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 端点是 https://taotoken.net/api 。
你需要先拿到一个 API Key。登录后进控制台,在 API Keys 页面创建一个新 Key,复制出来备用。这个 Key 后面会写进 Antigravity 的模型配置里。如果你还没决定用哪个模型,可以先去模型对话页面试一下不同模型的响应质量,再决定给逆向工作流配哪个。
环境方面,你需要准备这些东西:Python 3.10 以上(MCP Server 用 Python 写)、Node.js 18 以上(部分 MCP 客户端依赖)、IDA Pro 7.5 以上(带 idapython)、Frida 16.x、ADB 平台工具、一台已经 root 或者可调试的 Android 设备或模拟器。把这些工具的路径都记下来,后面配置里要填绝对路径。
MCP 的架构分两层:Server 端是你自己写的 Python 脚本,负责把工具能力注册成 MCP 工具;Client 端是 Antigravity,它通过 stdio 或者 SSE 连接 Server,读取工具列表并调用。所以你要做的是:写 Server 脚本 → 在 Antigravity 的 MCP 配置里登记 Server → 重启 Antigravity → AI 就能看到新工具。
这里有个关键点:MCP Server 脚本本身不执行逆向逻辑,它只是“翻译官”,把 AI 的调用请求转成对 IDA Pro、Frida、ADB 的实际命令。真正的分析工作还是这些工具在做,MCP 只是让 AI 能指挥它们。
3. 可复制配置:MCP Server 脚本与 Antigravity 登记
这一节是全文最核心的部分,所有配置都可以直接复制。先写 IDA Pro 的 MCP Server。新建一个文件ida_mcp_server.py:
from mcp.server.fastmcp import FastMCP import subprocess import json import os mcp = FastMCP("ida-pro", instructions="二进制静态分析工具,支持反编译、函数枚举、字符串提取") IDA_PATH = r"C:\Program Files\IDA Pro 7.7\idat64.exe" IDA_SCRIPT = r"D:\mcp\ida_scripts\decompile.py" @mcp.tool() async def decompile_function(binary_path: str, function_name: str) -> str: """反编译指定函数,返回伪代码""" cmd = [IDA_PATH, "-A", f"-S{IDA_SCRIPT} {function_name}", binary_path] result = subprocess.run(cmd, capture_output=True, text=True, timeout=300) return result.stdout @mcp.tool() async def list_functions(binary_path: str) -> str: """列出二进制文件中所有函数名""" cmd = [IDA_PATH, "-A", f"-S{IDA_SCRIPT} --list", binary_path] result = subprocess.run(cmd, capture_output=True, text=True, timeout=300) return result.stdout @mcp.tool() async def extract_strings(binary_path: str, min_length: int = 6) -> str: """提取二进制中的字符串,用于搜索密钥、URL""" cmd = [IDA_PATH, "-A", f"-S{IDA_SCRIPT} --strings {min_length}", binary_path] result = subprocess.run(cmd, capture_output=True, text=True, timeout=300) return result.stdout if __name__ == "__main__": mcp.run()Frida 的 Server 脚本frida_mcp_server.py:
from mcp.server.fastmcp import FastMCP import frida import json mcp = FastMCP("frida", instructions="运行时 Hook 工具,支持注入脚本、拦截函数、枚举类方法") @mcp.tool() async def inject_script(package_name: str, script_source: str) -> str: """向目标 App 注入 Frida 脚本""" device = frida.get_usb_device() session = device.attach(package_name) script = session.create_script(script_source) script.load() return f"script injected into {package_name}" @mcp.tool() async def hook_function(package_name: str, class_name: str, method_name: str) -> str: """Hook 指定类的指定方法,打印参数和返回值""" script_source = f""" Java.perform(function() {{ var target = Java.use("{class_name}"); target.{method_name}.overload().implementation = function() {{ console.log("[*] {method_name} called"); var ret = this.{method_name}(); console.log("[*] return: " + ret); return ret; }}; }}); """ device = frida.get_usb_device() session = device.attach(package_name) script = session.create_script(script_source) script.load() return f"hooked {class_name}.{method_name}" @mcp.tool() async def enumerate_classes(package_name: str) -> str: """枚举目标 App 加载的所有类""" script_source = """ Java.perform(function() { Java.enumerateLoadedClasses({ onMatch: function(name) { console.log(name); }, onComplete: function() {} }); }); """ device = frida.get_usb_device() session = device.attach(package_name) script = session.create_script(script_source) script.load() return "class enumeration started"ADB 的 Server 脚本adb_mcp_server.py:
from mcp.server.fastmcp import FastMCP import subprocess mcp = FastMCP("adb", instructions="Android 设备控制工具,支持安装、卸载、文件传输、命令执行") ADB_PATH = r"C:\platform-tools\adb.exe" @mcp.tool() async def install_apk(apk_path: str) -> str: """安装 APK 到设备""" result = subprocess.run([ADB_PATH, "install", "-r", apk_path], capture_output=True, text=True) return result.stdout @mcp.tool() async def pull_file(remote_path: str, local_path: str) -> str: """从设备拉取文件""" result = subprocess.run([ADB_PATH, "pull", remote_path, local_path], capture_output=True, text=True) return result.stdout @mcp.tool() async def shell_command(command: str) -> str: """在设备上执行 shell 命令""" result = subprocess.run([ADB_PATH, "shell", command], capture_output=True, text=True) return result.stdout @mcp.tool() async def list_packages() -> str: """列出设备上所有已安装的包名""" result = subprocess.run([ADB_PATH, "shell", "pm", "list", "packages"], capture_output=True, text=True) return result.stdout三个 Server 写好后,在 Antigravity 的 MCP 配置文件里登记。配置文件路径通常是%APPDATA%\Antigravity\mcp_settings.json(Windows)或~/.config/antigravity/mcp_settings.json(macOS/Linux)。内容如下:
{ "mcpServers": { "ida-pro": { "command": "python", "args": ["D:\\mcp\\ida_mcp_server.py"], "env": {} }, "frida": { "command": "python", "args": ["D:\\mcp\\frida_mcp_server.py"], "env": {} }, "adb": { "command": "python", "args": ["D:\\mcp\\adb_mcp_server.py"], "env": {} } } }注意路径里的反斜杠要转义成双反斜杠。如果你用的是虚拟环境,command要填虚拟环境里 python.exe 的绝对路径。保存后重启 Antigravity,在工具面板里应该能看到这三个 Server 注册的工具列表。
4. 验证请求:静态+动态分析闭环实测
配置完成后,先做一次最小验证,确认 AI 真的能调用工具。在 Antigravity 对话框里输入:“列出当前连接的 Android 设备上所有已安装的包名。”如果配置正确,AI 会调用adbServer 的list_packages工具,返回一长串包名。这一步成功,说明 MCP 链路通了。
接下来做完整的静态+动态闭环。我拿一个 Cocos2d-x 游戏 App 做测试,包名假设为com.example.game。第一步,让 AI 解析 APK 结构:
adb shell pm path com.example.game adb pull /data/app/com.example.game-1/base.apk ./target.apkAI 会自动执行这两条命令,把 APK 拉到本地。然后调用 IDA Pro 的extract_strings工具,在 22MB 的引擎库里搜索xxtea关键字,返回结果里出现xxtea_decrypt和jsb_set_xxtea_key,确认使用了 XXTEA 加密。
第二步,静态分析找密钥。AI 调用decompile_function反编译启动函数,在伪代码里看到密钥的一部分,但因为编译器优化被截断了。这时候 AI 自己决定切换到动态分析——这一步不是我告诉它的。
第三步,动态 Hook。AI 调用 Frida 的hook_function,Hookjsb_set_xxtea_key函数:
Java.perform(function() { var target = Module.findExportByName("libcocos2djs.so", "jsb_set_xxtea_key"); if (target) { Interceptor.attach(target, { onEnter: function(args) { console.log("[*] xxtea key: " + Memory.readUtf8String(args[0])); } }); } });App 运行时,完整密钥被打印出来。两条路线交叉验证,确认了加密密钥。
第四步,批量解密。AI 写了一个 Python 脚本,用提取到的密钥一次性解密 27 个.jsc文件:
import xxtea import gzip import os KEY = b"extracted_key_here" input_dir = "./jsc_files" output_dir = "./decrypted" for filename in os.listdir(input_dir): if filename.endswith(".jsc"): with open(os.path.join(input_dir, filename), "rb") as f: encrypted = f.read() decrypted = xxtea.decrypt(encrypted, KEY, padding=False) try: decompressed = gzip.decompress(decrypted) except: decompressed = decrypted with open(os.path.join(output_dir, filename.replace(".jsc", ".js")), "wb") as f: f.write(decompressed) print(f"decrypted: {filename}")解密流程是:加密文件 → XXTEA 解密 → Gzip 解压 → 可读的 JavaScript 源码。跑完后decrypted目录里就是还原出来的源码。
第五步,源码分析。AI 自动分析还原出的源码结构,识别出模块系统、网络协议、调试接口,甚至找到开发者遗留的内部调试系统。整个闭环从静态到动态再到批量处理,AI 在工具之间自主切换,我只在关键节点确认了一下。
5. 常见报错排查:401、local proxy failed、reading choices
配置和运行过程中会遇到几类典型报错,这里逐个对照排查。
401 Unauthorized:这个通常出现在模型 API 调用环节。检查 TaoToken 的 API Key 是否正确写入 Antigravity 的模型配置,Key 有没有过期,请求头里的Authorization: Bearer <key>格式对不对。如果用的是环境变量,确认变量名和代码里读的一致。另外检查 Base URL 是不是https://taotoken.net/api,末尾不要多加斜杠。
local proxy failed:MCP Server 启动失败时常见。先确认mcp_settings.json里的command路径正确,Python 解释器能正常执行。然后在命令行手动跑一下python ida_mcp_server.py,看有没有 import 错误。常见原因是mcp包没装,执行pip install mcp即可。如果报ModuleNotFoundError: No module named 'frida',装pip install frida frida-tools。
reading choices 报错:这个一般出现在模型返回格式解析环节,通常是模型输出被截断或者返回了非预期结构。检查max_tokens设置是不是太小,逆向分析返回的伪代码可能很长,建议调到 8192 以上。如果用的是流式输出,确认客户端正确处理了 SSE 分块。
OAuth 相关报错:如果你在 Antigravity 里配置了需要 OAuth 的模型通道,报OAuth token expired或者invalid_grant,重新走一遍授权流程即可。TaoToken 的 API Key 方式不涉及 OAuth,直接用 Key 就行。
IDA Pro 路径含空格报错:C:\Program Files\...里的空格会导致命令行解析出错。解决方法是在 subprocess 调用时把路径作为列表元素传入,而不是拼成字符串。上面脚本里subprocess.run([IDA_PATH, ...])的写法已经规避了这个问题。如果你手动拼命令,记得给路径加引号。
Frida 线程崩溃:Frida 脚本在自己的线程运行,但 Cocos2d-x 的 JS 引擎函数必须在 GL 渲染线程调用,直接调用会崩溃。解决方案是 Hook 引擎的帧更新函数(每秒调用 60 次),把要执行的代码排队,在下一帧由正确的线程安全执行。这就像不能在高速路上突然停车修轮胎,但可以在下一个服务区停下来修。
ADB 设备未授权:adb devices显示unauthorized,在手机上确认 USB 调试授权弹窗,或者执行adb kill-server && adb start-server重启 ADB 服务。
6. 把能力边界往外推一步
这套方案跑通之后,最直接的变化是效率。传统逆向分析要在 IDA、Frida、ADB、文件管理器、编辑器之间来回切换,手动复制粘贴数据。现在对 AI 说一句话,它自己协调所有工具完成分析。四个 MCP Server 脚本是通用的,换个 App 照样能用,就像买了一套厨具,做中餐能用,做西餐也能用。
更值得说的是 AI 在工具之间做出的决策。静态分析遇到瓶颈时自动切换动态分析,一种 Hook 方式崩溃时自动尝试替代方案,字符串搜索没结果时改用二进制模式搜索。这些决策不是我预设的,是 AI 根据工具返回结果自己判断的。工具的能力边界就是 AI 的能力边界,通过 MCP 把工具接进来,等于把 AI 的能力边界往外推了一步。
如果你想把模型对话能力也接进这套工作流,可以直接用 TaoToken 的模型对话接口测试不同模型在逆向场景下的表现。长期做编码和 Agent 任务的话,Coding Plan 更适合高频调用。接入文档里有完整的 API 说明和示例代码,照着改就能把模型通道换成你自己的。
最后留一个实用技巧:MCP Server 脚本里的工具描述(instructions和 docstring)写得越具体,AI 调用时越准确。比如extract_strings的 docstring 里写明“用于搜索密钥、URL”,AI 在需要找密钥时就会优先想到这个工具。这比在提示词里反复强调“记得用字符串提取”有效得多。