← 返回总索引05_助手UI控件逆向 05_助手UI控件逆向/06_偏移0x364全量反汇编分析.html
05_助手UI控件逆向更新:2026-09-18对象:D:\rxjh2025\client\YGOnline.exe

偏移 0x364 全量反汇编分析 —— 谁在读、谁在写、各是什么

对象D:\rxjh2025\client\YGOnline.exe(capstone 逐指令口径,373 万条指令全扫)
问题:实机遍历证实「控件 ID = [控件+0x364]」,那游戏自己在哪里读它、写它?这个偏移还有没有别的含义?
生成:2026-09-18


一、结论速览

结论 内容
总量 .text879 处 [x+0x364] 访问,分布在约 300 个函数 —— 是个高频复用偏移
★ ID 写入点找到了 0x0075B230:控件工厂 InterUI_Manufacture(虚表 0x010831E0,RTTI 验证)构造控件时写 [控件+0x364] = 控件ID(样本值 0x38
★ 游戏自己的 0x3F4 分发原文挖到了 0x0075A6F8push 0 / push 1 / push 0x3F4ecx = [[0x183C52C]+0x3EC]+0x39Ccall [虚表+4] —— 与你的机器码写法逐条同款
★ 引擎级取控件新链路 [0x183C52C](UI管理器) → +0x3EC(当前活动窗口) → +0x39C(当前目标控件),不依赖助手窗口 +0x414
⚠ 语义随类而变 同一个 +0x364,在控件上是 ID、在 Inter* 窗口上是「当前活动子对象指针」、在数据结构上是数组首 —— 不能见 364 就当 ID

二、扫描口径与形态统计

方法与《全局地址与基址分析报告》一致:capstone detail=True + skipdata=True 逐指令,收所有 disp==0x364 的内存操作数,区分读/写、标量/变址、位宽。

形态 次数 说明
读 · 标量 · 32位 550 大多数是取 ID 或取指针
写 · 标量 · 32位 185 含构造初始化(ID / -1 / 指针)
读 · 变址 [reg+reg*4+0x364] 86 数组用法(另一批类)
写 · 标量 · 8位 24 字节标志
写 · 变址 · 32位 13 数组写入
读 · 标量 · 8/16位 10 + 6 字节/字标志
写 · 标量 · 16位 5

第一课:879 处里只有一部分是「控件 ID」。判定对象身份必须先看 [x] 是不是 .rdata 虚表(RTTI 法),不能见 364 就当 ID。


三、★ 控件 ID 的写入点:InterUI_Manufacture 控件工厂

0x0075B230 处的构造序列(上下文直接可读):

0075B272   mov dword ptr [eax], 0x10831E0     ; ← 虚表 = 0x010831E0
0075B27B   mov dword ptr [ecx + 0x358], 5     ; 类型/样式字段
0075B288   mov dword ptr [edx + 0x35C], 0x80
0075B295   mov dword ptr [eax + 0x360], 0x9D
0075B2A2   mov dword ptr [ecx + 0x364], 0x38  ; ★ 控件 ID 写入点(样本 0x38)
0075B2AF   mov dword ptr [edx + 0x330], 0
0075B2BC   mov dword ptr [eax + 0x354], 0
0075B2C9   mov dword ptr [ecx + 0x334], 0

虚表 0x010831E0 走 RTTI 链([-4]→COL 0x01185D44 → TD 0x011F7458)解出类名:

.?AVInterUI_Manufacture@@      ← 「UI 制造」,即控件工厂类

解读:这个类生产的每个控件实例都把「分配到的控件 ID」放在自己的 +0x364。与你实机遍历 74 个控件读出的 [控件+0x364]=ID 完全吻合 —— 静态与活体两头闭合。相邻字段族(+0x330 / +0x334 / +0x354 / +0x358 / +0x35C / +0x360 / +0x364)就是控件对象头的初始化区。


四、★ 游戏自己的 0x3F4 分发原文(最重要的发现)

0x0075A6F8 起,一个函数的收尾处(ret 8 前):

0075A6FF   push 0                      ; 参数3(lparam?)
0075A701   push 1                      ; 参数2
0075A703   push 0x3F4                  ; ★ 动作码 —— 和你 push 0x3F4 一模一样
0075A708   mov  eax, [0x183C52C]       ; ★ UI 管理器单例(.data 全局)
0075A70D   mov  ecx, [eax + 0x3EC]     ; → 当前活动窗口
0075A713   mov  edx, [ecx + 0x39C]     ; → 窗口的「当前目标控件」
0075A719   mov  eax, [0x183C52C]
0075A71E   mov  ecx, [eax + 0x3EC]
0075A724   mov  edx, [edx]             ; 控件虚表
0075A726   mov  ecx, [ecx + 0x39C]     ; ecx = 控件(this)
0075A72C   mov  eax, [edx + 4]         ; 虚表槽1 = 动作分派
0075A72F   call  eax                   ; 槽1(0x3F4, 1, 0)
0075A732   mov  esp, ebp / pop ebp / ret 8

这是引擎原生的「点控件」调用,与你的机器码逐条对应:

你的机器码 游戏引擎(0x75A6FF)
push 0 ×2 push 0 / push 1
push 0x3F4 push 0x3F4
mov ecx, 控件 mov ecx, [[[0x183C52C]+0x3EC]+0x39C]
mov edx,[ecx] mov edx,[edx]
call [edx+4] call [edx+4]

你的写法不是"旁门左道",就是引擎自己的手法。双向证实。

4.1 顺手拿到的新链路(活体验证候选)

[0x183C52C]          UI 管理器单例(.data,与已知的 0x183C520 相邻)
  +0x3EC    →   当前活动窗口
    +0x39C  →   当前目标控件

这条链不依赖[[NPC基址值]+0x414] 助手窗口」那条路,是引擎级的「当前窗口 → 当前控件」旁路。值得开 CE 实测:打开助手窗口后看 [[0x183C52C]+0x3EC] 是否等于助手窗口句柄、+0x39C 是否随鼠标悬停/点击变化。


五、Inter* 窗口槽1 里的 +0x364:父 → 子动作转发

5.1 InterNpc 槽1(0x00C7FCC0)—— 0x3EE 转发 ×32

push [ebp+0x10]
push [ebp+0xC]
push 0x3EE                  ; 通用动作码
mov  edx, [ebp-0x4078]      ; 窗口 this
mov  eax, [edx + 0x364]     ; ★ [窗口+0x364] = 当前活动子对象(指针!)
mov  ecx, [ebp-0x4078]
mov  edx, [eax]             ; 子对象虚表
mov  ecx, [ecx + 0x364]
mov  eax, [edx + 4]         ; 子对象虚表槽1
call eax                    ; 子对象.槽1(0x3EE, ...)

→ 在 Inter* 窗口类上,+0x364 不是 ID,是「当前活动子对象」指针:窗口收到动作后转手递给活动子对象的槽1。这解释了为什么 0x3EE/0x3F4 会"跨类通用"—— 大家都在走同一条转发链。

5.2 同族佐证

函数 身份 +0x364 的用法
0x006254F0 InterAutosell 槽1(11 读) mov edx,[ecx+0x364] 后当指针用:mov eax,[edx+0xB3C]
0x00A59C10 InterByulho 槽1 movzx ecx, byte [eax+0x364][edx+0x365] —— 字节标志
0x00652520 高引用(12 读 + 0x3F4/0x3EE 各 1) 混合用法
0x00A3F3D0 InterAutoPlay 槽1 0 次 —— 助手分派不读 +0x364,ID 全靠栈上参数(这正是你 push ID 就能驱动它的原因)

六、其余三大用户:不是控件,是别的类

函数 命中 +0x364 的真实语义(证据)
0x00723070 32 读 数据块指针族:[obj+0x364] → 解引用访问 +0x53C 数组
0x00646080 20 读 同上:mov eax,[edx+0x364]cmp [eax+edx*4+0x53C], 0
0x00BA6F70 30 读 + 3 写 malloc(0x37C) 出来的结构体,+0x364 是其数组首[eax+edx+0x364] 变址写)
0x0072F4F0 1 写 0xBAADF00D —— CRT 堆调试标记,与控件无关
0x0064F7C0 / 0x00BA4A60 / 0x00EFFC30 各 1 写 0xFFFFFFFF —— 字段初始化为 -1
0x00CBA960 / 0x00CBAC00 各 1 写 0xC8FFFFFF(-56)—— 数据字段

第二课+0x364 在这 exe 里至少被 4 个类族复用:①控件 ID(InterUI_Manufacture 系)②Inter* 窗口的活动子对象指针 ③数据结构数组首 ④杂项初始化。遍历控件时务必先用 [ptr] 是否落在 .rdata 虚表区 + RTTI 名来验明正身,否则会把数据块当控件。


七、push 0x3F4 / 0x3EE 全景(77 个函数)

函数 0x3F4 0x3EE +0x364 备注
0x00A05B30 16 78 0 0x3EE 最大户(通用动作转发器)
0x00A3CBC0 29 0 0
0x009F7F50 19 0 0
0x00CDABE0 16 0 0
0x007E63B0 15 0 0
0x009FC290 14 0 1 ★ 联合命中
0x0075CF30 7 0 1 ★ 联合命中
0x00C7FCC0 0 32 2 ★ InterNpc 槽1(§5.1)
0x0068B2B0 4 0 0 InterInGameTotalShop 槽1 自身
0x00A3F700 2 0 0 小助手下一层分发

同时命中 +0x364 与分派特征的 13 个函数(见脚本输出 _s364b.txt):0x652520 / 0x6254F0 / 0x741710 / 0x75A310 / 0x9FC290 / 0x75CF30 / 0xC7FCC0 / 0xAFACE0 / 0xC572F0 / 0xCB33F0 / 0xCB70D0 / 0x6FC030 / 0xBED4C0 —— 这一批就是「读到控件/子对象 → 直接分派动作」的引擎枢纽,是后续逆向的最佳切入点。


八、给你的三条实操结论

  1. 写法已双证push 0x3F4 → mov ecx,控件 → call [edx+4] 就是引擎原生手法(0x75A6FF 原文对照),不用再怀疑这层。
  2. 新链路待活体[[0x183C52C]+0x3EC] = 当前活动窗口、[+0x39C] = 当前目标控件。若成立,找控件不再依赖 +0x414,而且对所有窗口通用(不止助手)。
  3. +0x364 别盲用:判对象四步——取 [ptr] → 是否 .rdata → RTTI 解名 → 再决定把 +0x364 当 ID 还是当指针。

九、脚本与原始输出

脚本 作用 输出
scan_364.py .textdisp==0x364 + 上下文反汇编 + 函数归属 _s364.txt / _s364_fn.txt
scan_364b.py 虚表 RTTI 验证 + 0x3F4 分发原文 + 联合特征 _s364b.txt
scan_364c.py 形态统计(读写/标量/变址/位宽) _s364c.txt

配套:《助手 0x3F4 分派机制与控件 ID 实现表》·《助手控件实机对照表(74 个)》·《全局地址与基址分析报告》
分析对象:D:\rxjh2025\client\YGOnline.exe · capstone 逐指令口径