目标二进制:D:\rxjh2025\client\YGOnline.exe
PE 信息:32 位(ImageBase = 0x400000,Magic = PE32)
分析方法:用 Python 解析 PE,对 基址更新 子程序里的 34 条 Q_gw.FindData 字节特征,逐条在文件里做精确匹配;对未命中的再做通配(??)容错复扫,区分「帧偏移漂移(可修)」与「架构/版本不符(修不了)」。
| 状态 | 数量 | 含义 |
|---|---|---|
| ✅ 精确命中(直接可用) | 20 | 该二进制的代码段里就有这段特征,基址更新 运行时能找到 |
| 🔧 帧偏移漂移(改几个字节即可) | 3 | 特征存在但 ebp 帧偏移字节变了,给出修正字节 |
| ❌ 本构建缺失(x86 但整段不存在) | 2 | 昇天列表基址、辅组列表基址 在该二进制里完全搜不到 |
| 🚫 64 位特征(架构不符) | 9 | 含 48 8B/44 0F B6 等 REX 前缀,是 64 位客户端的特征,32 位 exe 永远 0 命中 |
核心结论:你贴的
基址更新是「按字节特征动态找基址」的权威做法,思路完全正确。但它是一份混合了多个游戏版本的特征库——当前rxjh2025这个 32 位客户端只认其中 20 条;另外 14 条要么字节漂移、要么来自别的版本(含 9 条 64 位客户端特征)。
Q_gw.FindData 返回运行地址,临时地址 = 读数 + 模块地址 - 模块地址 得到模块相对偏移(即 .常量 值)。下表 target=mod_rel+调整 就是该 .常量 应得的模块相对偏移:
| 名称 | 类型 | target(mod_rel,即常量值) |
|---|---|---|
| 血量基址 | data | 0x5F52B9 |
| 周围数量基址 | data | 0x9E09A2 |
| 周围列表基址 | data | 0x9E09AC |
| 背包列表基址 | data | 0x6DEACB |
| 状态列表基址 | data | 0x2B97BD |
| 技能栏基址 | data | 0x7473A8 |
| 放置技能CALL基址 | call | 0x953490 |
| 选中对象基址 | data | 0x934F23 |
| 使用快捷CALL基址 | call | 0x749A1E |
| NPC对话基址 | data | 0x268951 |
| NPC_CALL基址 | call | 0x879661 |
| NPC选项基址 | data | 0x87914F |
| NPC选项CALL基址 | call | 0x879154 |
| 小助手CALL基址 | call | 0x63F6E7 |
| NPC功能CALL基址 | call | 0x881C5F |
| 副本道具基址 | data | 0x9539F5 |
| 副本道具CALL | call | 0x9539FA |
| BUFF遍历基址 | data | 0x8E3CD0 |
| 背包使用CALL | call | 0x9538BE |
| 道具遍历基址 | data | 0x368D49 |
注意:
周围列表基址(0x9E09AC)就是实体数组指针变量的位置,运行时读整数(模块地址+0x9E09AC)才是实体数组本身——与你之前怪物基址值/#怪物基址的用法一致(只是表述成模块相对偏移,抗 ASLR,比写死绝对值稳)。
特征本身存在,只是 ebp 帧偏移字节变了(原特征 0 命中,通配后命中)。已替你抠出该二进制里正确的字节,直接替换 FindData 的第三个参数即可:
8B 8D 30 B6 FF FF 8B 51 0C 89 90 24 1D 00 008B 8D 20 B6 FF FF 8B 51 0C 89 90 24 1D 00 00(唯一命中,帧尾 B6 FF FF 与原特征一致,仅低位 30→20)6A 00 6A 00 68 50 04 00 00 8B 85 3C B6 FF FF 8B 88 24 1D 00 006A 00 6A 00 68 50 04 00 00 8B 85 2C B6 FF FF 8B 88 24 1D 00 00(唯一命中,帧尾 B6 FF FF 一致,3C→2C)通配后有 11 处命中,原特征帧尾 F6 FF FF 在本构建里没出现(帧整体偏移了),无法单凭二进制判断哪一个是「活的」那一条。候选(取最低地址一条做首选,游戏里若攻击列表为空再换下一条):
首选(最低VA): 89 85 3C BF FF FF 89 95 40 BF FF FF 8B 95 3C BF FF FF 0B 95 40 BF FF FF
其它候选: 89 85 28 F9 FF FF ... / 89 85 FC F5 FF FF ... / 89 85 5C FF FF FF ...
89 85 50 AE FF FF ... / 89 85 88 F9 FF FF ... / 89 85 90 FD FF FF ...
89 85 14 FE FF FF ... / 89 85 68 FF FF FF ... / 89 85 60 FF FF FF ...
89 85 78 FF FF FF ...
判断方法:替换后运行
基址更新,进游戏打怪看「攻击列表基址」是否填充且数量正确;不对就逐条试上面候选(adj 不变,仍为 -0x34 / 派生 0x32)。
昇天列表基址:C6 84 0A 18 07 00 00 01 8B 45 FC ... 全二进制 0 命中。辅组列表基址:8B 8D 7C F9 FF FF 8B 95 88 F9 FF FF 8B 84 8A B8 0F 00 00 ... 0 命中。这两条在 rxjh2025 这版客户端里代码逻辑已变,需要从当前运行进程用 CE「查找访问」重新抠特征。
以下 9 条含 REX 前缀(48/44/45/49),是64 位游戏版本的特征库,对当前 32 位 YGOnline.exe 无效:
- 对话call基址、对话选项call基址、小范围瞬移call基址、函数遍历基址
- 输入文本call基址、出售物品call基址、快速出售灰色物品call基址、自动堆叠call基址、购买物品call基址
含义:你的
基址更新同时服务多个版本(含 64 位)。若 rxjh2025 用不到对话/瞬移/出售/堆叠/购买这些功能,0 命中无影响;若需要,必须从 32 位 rxjh2025 运行进程用 CE 重新抠这 9 条特征(方法同下「队员基址」)。
基址更新 里完全没有 队员基址 这一项(与之前结论一致:它不在你任何字节特征库里,也不在 常量.e.txt 里)。补法与其它基址一样——加一段「按特征找指针变量」的代码。模板如下(特征字节需你用 CE 提供,静态二进制无法可靠抠出,原因见下):
' ===== 队员基址(需 CE 提供特征字节)=====
临时文本 = Q_gw.FindData (窗口句柄, 查找范围, “<在此填 CE 抠出的特征>”)
.如果 (临时文本 ≠ “”)
临时地址 = 进制_十六到十 (临时文本) + 进制_十六到十 (“<调整量>”)
临时地址 = Q_gw.ReadIntAddr (窗口句柄, 临时地址, 0) ' 读出的应是 8 槽指针数组
调试输出 (“队员基址”, 十到十六 (临时地址))
_启动窗口.编辑框_偏移文本1.加入文本 (“队员基址” + 十到十六 (临时地址) + #换行符)
_启动窗口.编辑框_偏移文本.加入文本 (“.常量 队员基址, ” + 到文本 (临时地址 - 模块地址) + “, ,” + #换行符)
.否则
_启动窗口.编辑框_偏移文本.加入文本 (“.常量 队员基址, ” + “未找到” + “, ,” + #换行符)
.如果结束
一至尊霸王花一)。一=A440,至=A456,尊=BDA4,霸=BBAF,王=AA4C,花=AA44,一=A440 → A4 40 A4 56 BD A4 BB AF AA 4C AA 44 A4 40),勾「也扫描未知初始值 / 先精确再未知」。队员结构(+0x338 读名字,+0x334 读成员ID字,+0x38C 队长名,+0x3A0 队伍等级)。mov eax,[全局X];lea ecx,[eax+(slot-1)*4]」那一句——那个 [全局X] 就是 队员基址(8 槽指针数组起点)。FindData 特征字节,按上面模板填进去即可。关键点:队员结构与你之前分析的「玩家对象(type=0x31,名字 +0x18)」不是同一个结构——队员是独立的 8 槽小结构(名字 +0x338),所以不能直接复用玩家对象偏移,必须单独抠。
人物基址、全局大列表基址 的字节改掉(唯一命中,稳)。攻击列表基址 用首选候选,游戏内确认攻击列表是否填充。基址更新。昇天列表、辅组列表,从运行进程用 CE 重抠(同上法)。附:扫描脚本 scan_bases.py(精确)、scan_missing.py(通配容错)、extract_fix.py(抠修正字节)均在本工作区,可复跑核对。