三件事:① 挖
0x0183C520的对象结构;② 挖0x0397xxxx的表结构;③ CE 活体验证第 22 篇 §5.3 的「9 槽 / 槽1 动作分派」通用调用范式。
结果:①② 静态挖穿并拿到 RTTI 类名;③ 范式在两个非助手窗口类上调用成功,游戏稳定。
| 项 | 结论 |
|---|---|
| 0x0183C520 | = ProcControl 系配置单例指针(构造序列 0x826B20/0xC531E0,读 ybmsg.cfg)—— 配置/流程控制,体量小 |
| 0x183C52C | = IFManager 单例指针(虚表 0x10A05EC),大对象 ≥0x1088 字节,全部 UI 的总管理器 —— 这才是主控 |
| 构造函数 | IFManager = 0x9E6740;ProcControl = 0x826B20;析构置零点 0x9EC122 / 0x826E1A |
| ★ 窗口注册表 | IFManager 的 +0x390 ~ +0x444 每个槽 = 一类窗口的当前实例指针(+0x414 = 助手)。活体一次读出 7 个窗口并全部 RTTI 定名 |
| 0x0397xxxx | 不是一张表,是 3 个变量簇 + 2 类索引表:玩家状态簇(0x3970940~0x3970B90)、属性簇(0x39741xx~0x39747xx)、第三簇(0x39780xx~0x39782CC);指针表 [reg*8+0x39741B8/1BC]、记录表 stride=0x354(852字节) |
| ★ 活体验证 | 槽1 动作分派范式在 InterInventory(背包)与 InterStatus(状态)两个窗口上 thiscall slot1(this, 0x3F4, 1, 0) 调用成功(返回 0,无崩溃,指针表无扰动)—— §5.3「手法对其它窗口通用」由推论升级为实测事实 |
capstone 逐指令扫全 .text:0x183C520/0x183C52C 直接引用 16253 处,取值后的二次解引用(字段访问)4949 处。构造点只有一个:
009E6782 mov [eax], 0x10A05EC ; 构造函数 0x9E6740 → RTTI = .?AVIFManager@@
009E67E5 …对象字段初始化到 +0x1080 (对象 ≥ 0x1088 字节)
00826B4B mov [eax], 0x10956A0 ; 构造函数 0x826B20 → RTTI = .?AVProcControl@@
00826BB0 push 0x10956A4 ; "ybmsg.cfg" ← ProcControl 读配置文件
| 地址 | 类 | 虚表 | 规模 | 语义 |
|---|---|---|---|---|
[0x183C520] | ProcControl 系 | 0x10956A0(本体;此槽存其配置子对象) | 小(≈0x20+) | ybmsg.cfg 配置/消息流程控制 |
[0x183C52C] | IFManager | 0x10A05EC | ≥0x1088 | UI 总管理器(主控单例) |
上一轮报告说「0x183C520 ≈ 游戏主控单例」——现在精化:主控是
0x183C52C指向的 IFManager(引用量、对象规模、字段数量都大一个数量级)。0x183C520只是它旁边的小弟。
| 字段 | 访问 | 语义(静态推断 → 活体验证) |
|---|---|---|
+0x39C | R×620 | 背包窗口实例(InterInventory)—— 最热字段 |
+0x390 | R×511 | 主界面(InterMAIN),收消息码 0x46/0x3C/0xD… |
+0x3EC | R×465 | 当前活动窗口(弹窗/NPC 对话) |
+0x394 / +0x398 | R×287/121 | 聊天 / 状态窗口实例 |
+0x414 | R×244 | 助手窗口(InterAutoPlay)—— 与实机遍历用的窗口指针同源 |
+0x444 | R×61 | 夫妻窗口(InterCouple);我们的助手分发函数 0xA3F700 也读它 4 次 |
+0xD48/+0xD49 | W×62/34 | 少量写点:状态字节区 |
规律:+0x390 ~ +0x3C0 这一段 4 字节对齐的指针槽 = 「每类窗口的当前实例」注册表。要找任何窗口对象,不必再从 HWND 爬 —— 直接 [[0x183C52C]+槽] 一步拿到。
CE 附加 YGOnline.exe(PID 5488,32 位),一次读出整条链,并用静态 RTTI 给每个活体虚表定名:
| IFManager 槽 | 活体实例 | 活体虚表 | 类名(RTTI) | 状态 |
|---|---|---|---|---|
+0x390 | 0x407F45D0 | 0x10A7D40 | InterMAIN | 打开 |
+0x394 | 0x45375BE8 | 0x10A30B4 | InterChat | 打开 |
+0x398 | 0x44FFD3C8 | 0x10AE084 | InterStatus | 打开 |
+0x39C | 0x35EB0D68 | 0x10A5890 | InterInventory(背包) | 打开 |
+0x3EC | 0x3E075788 | 0x10AFDB8 | InterNpc | 当前活动窗 |
+0x414 | 0x20121C50 | 0x10A20F8 | InterAutoPlay(助手) | 已注册,slot1=0xA3F3D0 ✓ |
+0x444 | 0x456D2720 | 0x10B0B34 | InterCouple | 已注册 |
静态字段地图与活体 100% 吻合。[0x183C520] 活体 = 0x05EC9708,但其 [+0] 读回为堆指针(0x90C7020)而非虚表 —— 说明它存的是 ProcControl 初始化流程(0xC531E0,读 ybmsg.cfg)产出的配置子对象;带虚表 0x10956A0 的 ProcControl 本体由构造函数 0x826B20 创建(字符串 ybmsg.cfg 紧贴其虚表 = 0x10956A4,两处地址只差 4 字节,属于同一构造序列)。
00AE3760 push ebp / mov ebp,esp / sub esp,0x40
00AE3766 mov [ebp-08], ecx ; this 从 ECX 取
00AE3769 mov eax,[ebp+08] ; 第一个栈参 = 动作码
00AE376F cmp [ebp-0C], 0x3EE → je 0xAE3834
00AE37A2 cmp [ebp-0C], 0x3F4 → je 0xAE3A64
00AE37AF cmp [ebp-0C], 0x44A → je 0xAE3A44
00AE37BC cmp [ebp-0C], 0x46E → je 0xAE3B58
00AE378E …其余 0x03~0x08 走 jmp [edx*4+0xAE3C20] 跳转表
与助手槽1 0xA3F3D0 的分派形态逐条同款 —— 9 槽/槽1 动作分派范式在活体内存中得到直接确认。
按引擎自己的手法(0x75A6FF 处原文 push 0 / push 1 / push 0x3F4),对两个非助手窗口执行:
thiscall slot1( this, 0x3F4, 1, 0 )
① InterInventory this=0x35EB0D68 slot1=0xAE3760 → 返回 0 ✓
② InterStatus this=0x44FFD3C8 slot1=0xC291E0 → 返回 0 ✓
调用后复核:IFManager 及 7 个窗口指针、HP、MAPID 全部无扰动,游戏稳定
结论:第 22 篇 §5.3 的推论「你驱动助手的这套手法对其它 Inter* 窗口同样适用」已升级为实测事实。下一步只要给对应窗口发它认识的动作码/控件ID,就能不开窗口直接驱动背包、状态等界面。
"0x44FFD3C8"),或先用 Lua readInteger 取好再拼脚本。[符号]+off 解引用语法不可用,走 Lua readInteger 一次读整条链最稳。重扫 0x03970000~0x0397FFFF:约 240 个被引用目标。它不是一张大表,而是「角色运行时状态区」,由三个变量簇 + 两类索引表拼成:
| 区块 | 形态 | 关键证据 / 语义 |
|---|---|---|
0x3970940~0x3970B90 | 玩家状态变量簇 | 0x3970A70=当前血量(写者 0x6DBC8F,旁边 imul ×0x64 做百分比);0x3970A20=状态码(与 0xC/0xE/0x10 比较);0x3970ADC/AE0=float 对(movss);0x3970A9C=与 0x1DCD6500(5×10⁸) 比较的计时器;0x3970AB4=与 −500 比较的阈值 |
0x3970988/0x397098C | 记录表索引 | imul edx,[0x397098C],0x354 —— 852 字节记录 × 索引(玩家/对象数组元素的定位乘式,与 0x3970988 成对比较) |
0x39741B8 / 0x39741BC | 指针表 [reg*8+表] | 8 字节 stride 的对象指针数组(读者 0x6BAxxx/0x6BBxxx/0x6BCxxx 家族) |
0x3970C60/0CF8、0x39747A8/0x3974840 | 字数组表 [reg*2+表] | movsx word 读取,stride=2(id→值映射) |
0x3970F40/0x3970F44 | 成对 8 字节表 [reg*8+表] | 读-改-写成对(or / mov),读者 0xAC88xx |
0x39741xx~0x39747xx | 属性变量簇 | 写者高度集中:0x9EE1xx~0x9F56xx 家族(属性包处理),含 idiv ×0x64 百分比换算、movss float 段(0x3974B88/0x3974B90) |
0x39780xx~0x39782CC | 第三变量簇 | 读者 0x79Exxx/0x79Fxxx 家族,含 float 三连(0x3978084~0x397808C)与 [reg*4+0x39782CC] 指针表 |
0x3971A11 …(长串) | 等距字读序列 | 读者 0xACCxxx 家族,目标间距 ≈0x5C(92字节) —— 另一张固定 stride 的记录表被逐字段遍历 |
0x3970A70(本次活体 = 482,与 UI 一致);0x3970A7C 与它同值同写者,应为最大血/基准血,CE 改血测试即可分辨。imul ×0x354 说明存在 852 字节的角色/对象记录——顺着 [0x397098C]×0x354 + 记录首 能挖出完整的记录结构体(下一轮可做)。0x39741B8/1BC 两张指针表就是「对象实例数组」,配合 stride 可直接遍历周围玩家/怪物。.版本 2
.子程序 Inter_发动作, 整数型, 公开, 通用:对任意 Inter* 窗口发动作码(本回复实测通过)
.参数 窗口槽偏移, 整数型, , 如 0x39C=背包 0x398=状态 0x414=助手
.参数 动作码, 整数型, , 如 0x3F4
.参数 参数一, 整数型, 可空, 默认 1
.参数 参数二, 整数型, 可空, 默认 0
.局部变量 管理器, 整数型
.局部变量 窗口, 整数型
.局部变量 虚表, 整数型
.局部变量 槽1, 整数型
管理器 = 读整数 (十六到十 ("0x183C52C"))
.如果真 (管理器 = 0 或 窗口槽偏移 < 0)
返回 (-1)
.如果真结束
窗口 = 读整数 (管理器 + 窗口槽偏移)
.如果真 (窗口 = 0)
返回 (-2)
.如果真结束
虚表 = 读整数 (窗口)
.如果真 (虚表 = 0)
返回 (-3)
.如果真结束
槽1 = 读整数 (虚表 + 4)
.如果真 (槽1 = 0)
返回 (-4)
.如果真结束
' thiscall:ECX=窗口对象,栈参 = 动作码, 参数一, 参数二
返回 (调函数_ThisCall (槽1, 窗口, 动作码, 参数一, 参数二))
要点:窗口对象一律从 IFManager 注册表槽取(
[[0x183C52C]+槽]),比从 HWND/FindWindow 爬链稳;动作码先用0x3F4(刷新)验证通路,再逐个试目标窗口认识的码。
| 文件 | 作用 |
|---|---|
scan_183c.py | 0x183C520/52C 直接引用全表 + 寄存器追踪字段地图 + 写入点上下文(16253/4949) |
scan_183c2.py | 构造函数反汇编(拿虚表立即数)+ 关键字段用户上下文 + 0x0397 区初览 |
scan_397.py | 0x0397xxxx 全区重扫(普通/索引/写点/样本)+ RTTI 定名 |
rtti_live.py | 活体虚表 → RTTI 类名(7 窗口全部定名) |
| CE 工具链 | attach → Lua 链读 → 槽1 反汇编 → thiscall 调用 ×3 → 稳定性复核 |
生成:2026-09-18 · 方法:capstone 逐指令 + MSVC RTTI + CE 活体(附加 PID 5488)· 数据文件 _183c.txt / _397.txt / _rtti_live.txt