香菜的博客
全部项目

PROJECT · 2026

gx-mcp:给「石文 Gxplorer」接一个 AI 用的 MCP 服务

用窗口消息 / 对话框编排 / 进程内调用三种手段,让 AI 能用自然语言驱动这款 2005 年的 MFC 地质成图软件:批量投井、批量改样式、导出矢量 PDF。

实验中 逆向工程MCPWindowsMFC地质软件FridaPython

一句话

石文 Gxplorer 3.0 是一款 MFC 7.1(VS2003)时代的老地质成图软件,没有 API、没有脚本、没有命令行。
这个项目给它加了一个 MCP 服务,让 AI 可以用自然语言完成「新建平面图 → 投 36 口井 → 批量改井位样式 → 导出矢量 PDF」。


现在能做什么

你说:新建一张平面图,把川西坳陷这 36 口井投上去,井名和符号缩小一点,导成矢量 PDF

AI 调用:
  gx_new_figure("平面图")
  gx_add_wells(file="井位.csv", create_track=true)
  gx_well_style(scale=0.5, font_size=28)   × 35 次(定位法批量)
  gx_export_pdf(path="川西坳陷_平面图.pdf")

产出:1 页矢量 PDF,188 个矢量图元,231 个文字对象,36 个井名可选中可搜索(不是截图)。


架了三层,从"能点"到"能调"

第一层:窗口消息驱动(不碰鼠标)

软件是 Xtreme Toolkit 自绘菜单,没有 HMENU、没有 UIA。所以:

  • 从注册表 HKCU\Software\石文软件\Gxplorer\CommandBars-Controls(150 KB)逆出 266 条「菜单文字 → 命令 ID」
  • 用 PostMessage(hFrame, WM_COMMAND, id, 0) 直接触发命令
  • 解析 MENU 资源模板(flags(2) id(2) name(utf16z))补齐主菜单 ID

这一层的好处:不需要鼠标、不需要前台、软件被遮挡也能干活。

第二层:对话框编排

石文的对话框是真的 Win32 控件,所以能dump / 填 / 点 / 选:

gx_dialog_dump(title="添加多个井位")   # 拿到全部控件与下拉项
gx_fill(id=23399, value="米")
gx_click(id=1)

几个必须踩出来的细节:

  • 文件对话框里 SysListView32 的 id 也是 1,和"打开"按钮撞车 → 必须按 Button 类匹配
  • 「添加多个井位」只吃纯 3 列、无表头、无引号、GBK 编码、ASCII 路径;带表头会整行当数据 → 弹"数据有问题"
  • 本机剪贴板被常驻程序长期占用(Set-Clipboard 也失败)→ 改走文件通道

第三层:进程内调用(Frida)

老软件真正的内核在 DLL 里,而且导出符号没被 strip:

模块类能力
LoadDataDllu.dllCLoadDataReadFile() / GetWellHead() / GetWellPath()
Gxplorer.dllCContourSetLevelInterval(double) / SetIsCreateContour(bool)
Facies.dllCGeoFacies沉积相绘制
RegionDLL.dllCRegionSys研究区域

用 Frida 附着后可以直接构造对象、调成员函数、再析构:

obj = Memory.alloc(0x800)
CLoadData::CLoadData(obj, type, NULL)
CLoadData::ReadFile(obj, &wstring)
CLoadData::~CLoadData(obj)
// → { constructed: true, readResult: 0, destroyed: true }

另外还挖到一个注册过的 COM 组件(软件自己的原始数据解析内核):

ProgID : LoadData.ReadOriginalData
CLSID  : {86AF54BE-492B-4F88-AD73-A051646C9DA0}
成员   : Read / ReadThisFile / GetColumn / GetRow / ColumnNum / RowNum

四项能力:两项交付,两项卡住

#能力状态证据
1图元样式自动化✅35/36 口井设为 符号 0.5x + 字号 7pt,180 秒跑完
2高清/矢量导出✅打印预览 → Microsoft Print to PDF,文字可选中
3等值线 / 沉积相⬜命令与参数对话框已定位(克里金插值),缺 Z 值数据通道
4连井剖面 分层/连层🟡剖面 + 井 + 分层道都建好了,卡在「分层结点」

第 1 项的批量:没有多选,怎么办

石文的多选要 Ctrl+A / 框选,都需要真实键盘焦点。绕法是定位法:

定位井的位置(命令 0xC614) → 输入井名 → 视图自动居中到该井
→ 画布中心双击 → 弹出的「设置井属性」就是那口井(读控件名称校验)
→ 改尺寸/字体 → 确定

每口井 5 秒,36 口井 3 分钟,井名逐个校验,错了就跳过。

第 2 项:为什么不用截图

打印预览 → 打印选项(全幅) → 页边距 → 打印 → 选择 Microsoft Print to PDF → 另存为。
全程标准 Win32 控件,可以完全脚本化。100%比例 模式会因图幅超出纸张而报错,全幅 稳定。


踩过的坑(按痛苦程度排序)

① 锁屏状态下什么都不能点。
排查了很久才发现前台窗口是「Windows 默认锁屏界面」——不是权限问题,是会话被锁。
锁屏下 PostMessage 依然有效(对话框、按钮、控件读写全能用),但菜单、工具栏、Ctrl+A 全部失效。
这也是后来转去做代码层的直接原因。

② XTP 弹出菜单既没有 HMENU 也没有 UIA 子项。
最后用"截取弹出条 → 按文字行墨迹切出每个菜单项 → 按行点击"的办法绕过去。
更坑的是菜单项数会随状态变化(建了子层之后「添加子地层」会从菜单里消失),按固定 index 点击非常脆弱。

③ 报错框是自定义窗口(类名 Gxplorer,不是 #32770)。
所以 hook MessageBoxW 完全不会触发;想抓弹框的调用方得 hook CreateDialogIndirectParamW。

④ 双击空白画布会新建一口空井。
调试早期不知道,白白污染了数据。

⑤ 「打开」命令的文件对话框标题是「加载井处理文件」。
按"打开"去匹配永远找不到。顺带还发现 0xE101 会按文件内容分派:.gxp 当工程,其它当井数据解析。

⑥ Frida 挂钩 + 模态框同时存在时,session.detach() 会挂住,甚至把软件带崩。
后来改成独立进程 + os._exit(0) 收尾,并且用软件自带的 Para\AutoLoadProject 做崩溃恢复。

⑦ 「分层结点」是个隐藏前置条件。
连井剖面里加「分层道」之后点「加载地层数据」,一直报 没有相应分层结点。
后来在工程树里找到入口:分层 → 地层管理器 → 右键 → 添加子地层,
建出来的层却不写进 .gxp(保存后文件字节数不变),推测存在独立的"地层库",需要先做「引用信息表」绑定。


合规边界

  • 只在本机、对已安装自用的软件做互操作:发窗口消息、读写它自己的对话框
  • 不修改二进制、不绕过授权校验、不转发任何厂商文件
  • 仓库里不含安装包与授权组件(Gxplorer.bat 之类一律排除)

下一步

  1. 用 Frida 在 Gxplorer.dll 里按字符串交叉引用定位「加载地层数据」的处理函数,读它的前置检查,搞清"分层结点"到底是什么对象
  2. 拿到 CContour 实例后直接 SetLevelInterval + 生成等值线,绕开菜单
  3. 把「写一个 32 位 C# 桥接程序,用 csc.exe 编译,通过 COM 调 LoadData.ReadOriginalData」做成数据预处理层

代码、README.md(含完整的逆向笔记)和生成的 PDF 都在压缩包里。