PROJECT · 2026
gx-mcp:给「石文 Gxplorer」接一个 AI 用的 MCP 服务
用窗口消息 / 对话框编排 / 进程内调用三种手段,让 AI 能用自然语言驱动这款 2005 年的 MFC 地质成图软件:批量投井、批量改样式、导出矢量 PDF。
一句话
石文 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.dll | CLoadData | ReadFile() / GetWellHead() / GetWellPath() |
Gxplorer.dll | CContour | SetLevelInterval(double) / SetIsCreateContour(bool) |
Facies.dll | CGeoFacies | 沉积相绘制 |
RegionDLL.dll | CRegionSys | 研究区域 |
用 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之类一律排除)
下一步
- 用 Frida 在
Gxplorer.dll里按字符串交叉引用定位「加载地层数据」的处理函数,读它的前置检查,搞清"分层结点"到底是什么对象 - 拿到
CContour实例后直接SetLevelInterval+ 生成等值线,绕开菜单 - 把「写一个 32 位 C# 桥接程序,用
csc.exe编译,通过 COM 调LoadData.ReadOriginalData」做成数据预处理层
代码、
README.md(含完整的逆向笔记)和生成的 PDF 都在压缩包里。