← 返回总索引助手UI控件逆向 05_助手UI控件逆向/助手CALL双层调用与调用方式对比.html
助手UI控件逆向生成:2026-09-18分析对象:D:\rxjh2025\client\YGOnline.exe

助手 CALL 双层调用与易语言调用方式对比

本报告回答「控件 ID 从哪来、发命令到哪去」之后最后一环:0x68B2B0 分派层下面的 0xA3F700 双层分发函数,并对比三种在用/可选的易语言调用方式(虚表直调、0xA3F700 直调、大漠远程汇编),给出验证脚本与判读表。

一、0xA3F700 真身(静态验证)

0x00A3F700  push ebp / mov ebp,esp / sub esp,0x1AC
0x00A3F714  mov  [ebp-0xF8], ecx          ; ecx = NPC对话对象([NPC基址]+0x414)
0x00A3F71A  mov  eax, [ebp+8]             ; 参数1 = 选项码(如 0x31)
0x00A3F729  sub  eax, 0xB                 ; ★ 选项码减 0xB
0x00A3F732  cmp  eax, 0x99                ; 合法范围 = 选项码 0x0B ~ 0xA3
0x00A3F748  movzx eax, byte [eax+0xA41698]  ; 字节索引表
0x00A3F74F  jmp  [eax*4+0xA41588]         ; ★ 跳转表分发(约 154 个分支)

结论:

  1. 0xA3F700 不是 0x68B2B0,而是它下一层的命令处理函数——参数只有 (ecx=对话对象, 选项码),本来就不需要 push 0x3F4,栈也没问题。这解开了"旧辅助代码缺 push 0x3F4"的疑团:那人 call 的就是这一层。
  2. 全 exe 没有一条 call 0xA3F700(E8 rel32 全搜索为空)——它和 0x68B2B0 一样经函数指针/尾跳转被调,直调属于"借道",能用但不在游戏自己的调用路径上。
  3. 0xA41204 为公共出口。
  4. 两条特征码实测命中:NPC 对话 → 0x667955;小助手 call → 0xA3E676(0xA3E676 + 0x108A = 0xA3F700,可运行时求址免写死)。
  5. 特征里的 mov eax,[edx+0x34B4] 在 0xA3E676 与 0xA46FB4(Premuim 登记处)都出现 → +0x34B4 = 对话对象里存"助手窗口指针"的成员

二、三种调用方式对比

① 虚表直调(0x68B2B0 路径) ② 0xA3F700 直调(旧辅助路径) ③ 大漠远程汇编(别人辅助)
this 总控父窗口实例(需虚表校验) NPC 对话对象(基址+0x414,好找) 控件对象 +4 读出的父窗口
参数 push 0 / ID / 0x3F4 三个 push 0 + push 选项码 两个 同②(大漠 AsmAdd)
ID 来源 全枚举 65 个,可硬编码 已验证 0x31;同层选项码 0x0B~0xA3 运行时读 [+0x364](抗版本变化)
层级 与真实点击完全同路径 更底层,绕过总控转发 同①或②(取决于其 CALL 基址)
执行载体 注入后 置入代码 内联直调 同左 AsmCall(主窗口句柄, 6),窗口线程执行
维护成本 需要父窗口实例校验 只依赖 NPC 对话链,好找 需维护"控件名→控件地址"表

各自要点

① 虚表直调(推荐主用):机器码全静态——{255,82,4} = call [edx+4] 虚表自寻址,不需要任何 call 地址/基址/特征码;动态值只有 ecx;参数在 [ebp+8]/[ebp+0xC],不像旧路赌 [ebp-4] 是第一个局部变量。

② 0xA3F700 直调(旧路,保留备份):样例机器码——

置入代码 ({ 96 })                  ' pushad
置入代码 ({ 106, 0 })              ' push 0
置入代码 ({ 106, 49 })             ' push 0x31
置入代码 ({ 139, 77, 252 })        ' mov ecx,[ebp-4]  ← ECX 必须是第一个局部变量
置入代码 ({ 184, 0, 247, 163, 0 }) ' mov eax,0xA3F700
置入代码 ({ 255, 208 })            ' call eax
置入代码 ({ 97 })                  ' popad

三个优点值得保留:pushad/popad 护栈、置入机器码绕开字符串汇编器的十六进制误判、读失败即 return 防闪退。两个风险:mov ecx,[ebp-4] 赌 ECX 是第一个局部变量(加局部变量就崩);0xA3F700 写死在机器码里(可改为特征码命中后 +0x108A 运行时求)。

③ 大漠远程汇编:底层机制同①(+0x364=控件ID、+4=父窗口均已互证),ID 与父窗口运行时从控件对象读,抗版本变化;代价是大漠依赖 + "控件名→地址"表维护。其汇编若真调 0x68B2B0 缺 push 0x3F4 会栈失衡(ret 0xC 只压 8 字节),大概率 OCR 丢失或其实调的是 0xA3F700 层。

建议

两条路都留着互为备份:旧路只依赖 NPC 对话链、好找;新路命令 ID 全、扩展性好。旧路要停止挂机(0x32),可用 [对话对象+0x34B4] 拿助手窗口指针后走①的虚表路径。

三、验证脚本链路自检(5 环)

先验链、再发命令,断在哪环一目了然:

[1] NPC对话对象    ← [NPC基址] + 0x414
[2] 助手窗口       ← [对话对象 + 0x34B4]
[3] 开始按钮/控件ID ← [助手窗口 + 0x3938] → [+0x364](应 = 49)
[4] 父窗口         ← [开始按钮 + 4]
[5] 虚表           ← 应 = 基址 + 0xC79A68,不符就不发命令,防闪退

测试序列(每步间隔 3 秒):① 新路发 0x31(应开始挂机)→ ② 新路发 0x32(应停止)→ ③ 新路再发 0x31(确认可反复)→ ④ 旧路 0xA3F700 对照。

判读表

现象 结论
5 环全通 + 测试 1~3 有反应 虚表直调成立,以后不用维护 call 基址
环 3 断(ID ≠ 49) +0x34B4 不是助手窗口 → 换偏移
环 4 断(虚表不符) 按钮父窗口不是总控窗口 → 发回 [3][4][5] 实测值修正
指针全对但没反应 线程问题 → 换寻路 call 同款调用时机

四、安全边界

客户端带 XIGNCODE 反作弊,动态调试/内存修改仅限单机或私服环境;本文仅作技术分析记录。