本报告回答「控件 ID 从哪来、发命令到哪去」之后最后一环:0x68B2B0 分派层下面的 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 个分支)
结论:
(ecx=对话对象, 选项码),本来就不需要 push 0x3F4,栈也没问题。这解开了"旧辅助代码缺 push 0x3F4"的疑团:那人 call 的就是这一层。call 0xA3F700(E8 rel32 全搜索为空)——它和 0x68B2B0 一样经函数指针/尾跳转被调,直调属于"借道",能用但不在游戏自己的调用路径上。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] 拿助手窗口指针后走①的虚表路径。
先验链、再发命令,断在哪环一目了然:
[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 反作弊,动态调试/内存修改仅限单机或私服环境;本文仅作技术分析记录。