← 返回总索引03_内存逆向与基址 03_内存逆向与基址/23_IFManager单例与通用范式活体验证.html
03_内存逆向与基址更新:2026-09-18对象:YGOnline.exe + CE 活体(PID 5488)

IFManager 单例结构、0x0397xxxx 数据区与通用调用范式活体验证

三件事:① 挖 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「手法对其它窗口通用」由推论升级为实测事实

二、0x0183C520 / 0x183C52C:两个单例的身份

capstone 逐指令扫全 .text0x183C520/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]ProcControl0x10956A0(本体;此槽存其配置子对象)小(≈0x20+)ybmsg.cfg 配置/消息流程控制
[0x183C52C]IFManager0x10A05EC≥0x1088UI 总管理器(主控单例)

上一轮报告说「0x183C520 ≈ 游戏主控单例」——现在精化:主控是 0x183C52C 指向的 IFManager(引用量、对象规模、字段数量都大一个数量级)。0x183C520 只是它旁边的小弟。

IFManager 字段地图(引用数 Top)

字段访问语义(静态推断 → 活体验证)
+0x39CR×620背包窗口实例(InterInventory)—— 最热字段
+0x390R×511主界面(InterMAIN),收消息码 0x46/0x3C/0xD…
+0x3ECR×465当前活动窗口(弹窗/NPC 对话)
+0x394 / +0x398R×287/121聊天 / 状态窗口实例
+0x414R×244助手窗口(InterAutoPlay)—— 与实机遍历用的窗口指针同源
+0x444R×61夫妻窗口(InterCouple);我们的助手分发函数 0xA3F700 也读它 4 次
+0xD48/+0xD49W×62/34少量写点:状态字节区

规律:+0x390 ~ +0x3C0 这一段 4 字节对齐的指针槽 = 「每类窗口的当前实例」注册表。要找任何窗口对象,不必再从 HWND 爬 —— 直接 [[0x183C52C]+槽] 一步拿到。


三、CE 活体验证:窗口注册表当场闭合

CE 附加 YGOnline.exe(PID 5488,32 位),一次读出整条链,并用静态 RTTI 给每个活体虚表定名:

IFManager 槽活体实例活体虚表类名(RTTI)状态
+0x3900x407F45D00x10A7D40InterMAIN打开
+0x3940x45375BE80x10A30B4InterChat打开
+0x3980x44FFD3C80x10AE084InterStatus打开
+0x39C0x35EB0D680x10A5890InterInventory(背包)打开
+0x3EC0x3E0757880x10AFDB8InterNpc当前活动窗
+0x4140x20121C500x10A20F8InterAutoPlay(助手)已注册,slot1=0xA3F3D0 ✓
+0x4440x456D27200x10B0B34InterCouple已注册

静态字段地图与活体 100% 吻合[0x183C520] 活体 = 0x05EC9708,但其 [+0] 读回为堆指针(0x90C7020)而非虚表 —— 说明它存的是 ProcControl 初始化流程(0xC531E0,读 ybmsg.cfg)产出的配置子对象;带虚表 0x10956A0 的 ProcControl 本体由构造函数 0x826B20 创建(字符串 ybmsg.cfg 紧贴其虚表 = 0x10956A4,两处地址只差 4 字节,属于同一构造序列)。


四、§5.3 通用调用范式 · 实测

4.1 槽1 活体反汇编(InterInventory slot1 = 0xAE3760)

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 动作分派范式在活体内存中得到直接确认。

4.2 范式调用实测(thiscall)

按引擎自己的手法(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,就能不开窗口直接驱动背包、状态等界面。

4.3 两个教训(下次直接避开)


五、0x0397xxxx 数据区结构

重扫 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/0CF80x39747A8/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 的记录表被逐字段遍历

对辅助的意义


六、给易语言的调用模板(范式落地)

.版本 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.py0x183C520/52C 直接引用全表 + 寄存器追踪字段地图 + 写入点上下文(16253/4949)
scan_183c2.py构造函数反汇编(拿虚表立即数)+ 关键字段用户上下文 + 0x0397 区初览
scan_397.py0x0397xxxx 全区重扫(普通/索引/写点/样本)+ 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