.版本 2 '════════════════════════════════════════════════════════════════════════ ' 内挂丸开关 · 注入版 · 单子程序完整版 ' 用法:整段复制到易语言,直接调用 ' 内挂丸操作 (游戏基址, 池基址, 小助手CALL, 31) ' 开 ' 内挂丸操作 (游戏基址, 池基址, 小助手CALL, 32) ' 关 ' 内挂丸操作 (游戏基址, 池基址, 小助手CALL, 0) ' 诊断(只读,不发命令) ' ' 代码运行在游戏进程内,所以: ' · 读内存直接读自己的,不需要 OpenProcess ' · 调游戏函数直接 call,不需要远程线程 / 申请远程内存 ' ' 调用约定 thiscall:ecx = this,栈参数从右到左 ' 必须先 push 附参数,再 push 命令字(命令字落在 [ebp+8]) ' 用 mov eax / call eax(FF D0 间接调用)避开跨模块 E8 rel32 算不准的问题 ' ' 控件字段(静态已验证): ' +0x364 (860) = 功能号 启动内挂丸=49 停止内挂丸=50 ' +0x368 (872) = 控件名(BIG5) ' +0x34C9(13513)= byte 开关标志,0=未开(开指令要求为 0,非 0 会被吞) ' this = 控件对象 + 4 ' ' 参数说明: ' 游戏基址 = YGOnline.exe ImageBase(无 ASLR,通常 0x400000) ' 池基址 = 全局大列表基址的绝对地址 ' 小助手CALL = 0x682880 的绝对地址(填 RVA 也没关系,代码自动补基址) '════════════════════════════════════════════════════════════════════════ .子程序 内挂丸操作, 逻辑型 .参数 游戏基址, 整数型 .参数 池基址, 整数型 .参数 小助手CALL, 整数型 .参数 状态, 整数型 ' 0=诊断 31=开 32=关 .DLL命令 CopyMemory, , "kernel32.dll", "CopyMemory" .参数 Destination, 整数型 .参数 Source, 整数型 .参数 Length, 整数型 .局部变量 槽位, 整数型 .局部变量 控件对象, 整数型 .局部变量 this值, 整数型 .局部变量 功能号, 整数型 .局部变量 名称, 文本型 .局部变量 开关位, 整数型 .局部变量 命令字, 整数型 .局部变量 缓冲, 字节集 .局部变量 长度, 整数型 .局部变量 打印数, 整数型 .局部变量 命中, 逻辑型 .局部变量 值, 整数型 ' 小助手CALL 表里若填的是 RVA,自动补基址成绝对地址 .如果真 (小助手CALL < 0x200000 且 游戏基址 ≠ 0) 小助手CALL = 小助手CALL + 游戏基址 .如果真结束 ' handler 只认 0x0B ~ 0xA3(11 ~ 163),超范围静默落空不报错 .如果真 (状态 ≠ 0) .如果真 (状态 = 31) 命令字 = 49 ' 0x31 启动内挂丸 .否则 命令字 = 50 ' 0x32 停止内挂丸 .如果真结束 .如果真 (命令字 < 11 或 命令字 > 163) 写日志 (“命令字越界:” + 到文本 (命令字) + “,有效 11 ~ 163” + #换行符) 返回 (假) .如果真结束 .如果真结束 '═════ 第一步:扫控件池 ══════════════════════════════════════ 写日志 (“── 控件池扫描 ──” + #换行符) 命中 = 假 打印数 = 0 槽位 = 0 .判断循环首 (槽位 < 3000) 值 = 0 CopyMemory (取变量地址 (值), 池基址 + 槽位 × 4, 4) 控件对象 = 值 .如果真 (控件对象 > 0 且 控件对象 ≠ 65535) 功能号 = 0 CopyMemory (取变量地址 (功能号), 控件对象 + 860, 4) ' +0x364 功能号 .如果真 (状态 = 0) ' 诊断模式:只看 40~60 .如果真 (功能号 ≥ 40 且 功能号 ≤ 60) 缓冲 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 } CopyMemory (取变量地址 (缓冲), 控件对象 + 872, 64) ' +0x368 名称 长度 = 1 .循环判断首 (长度 < 64 且 取字节集元素 (缓冲, 长度) ≠ 0) 长度 = 长度 + 1 .循环判断尾 () .如果真 (长度 > 1) 名称 = 编码_BIG5到简体 (取字节集部分 (缓冲, 1, 长度 - 1)) .否则 名称 = “” .如果真结束 开关位 = 0 CopyMemory (取变量地址 (开关位), 控件对象 + 4 + 13513, 4) ' this+0x34C9 .如果真 (打印数 < 20) 写日志 (“槽” + 到文本 (槽位) + “ 控件=” + 到文本 (控件对象) + “ 功能号=” + 到文本 (功能号) + “ 开关位=” + 到文本 (开关位) + “ 名称=” + 名称 + #换行符) 打印数 = 打印数 + 1 .如果真结束 .如果真 (功能号 = 49 或 功能号 = 50) 命中 = 真 .如果真结束 .如果真结束 .否则 ' 开/关模式:直接找 49 / 50 .如果真 (功能号 = 命令字) 命中 = 真 .如果真结束 .如果真结束 .如果真结束 槽位 = 槽位 + 1 .判断循环尾 () 写日志 (“扫描结束,找到启/停按钮 = ” + 到文本 (命中) + #换行符) .如果真 (命中 = 假) 写日志 (“池内没找到目标按钮,命令未发送” + #换行符) 返回 (假) .如果真结束 '═════ 第二步:定位 this(控件对象 + 4)并读当前开关位 ═════════ 槽位 = 0 .判断循环首 (槽位 < 3000) 值 = 0 CopyMemory (取变量地址 (值), 池基址 + 槽位 × 4, 4) .如果真 (值 > 0 且 值 ≠ 65535) 功能号 = 0 CopyMemory (取变量地址 (功能号), 值 + 860, 4) .如果真 (功能号 = 命令字) 控件对象 = 值 槽位 = 3000 到判断循环尾 () .如果真结束 .如果真结束 槽位 = 槽位 + 1 .判断循环尾 () .如果真 (控件对象 = 0) 写日志 (“二次定位失败” + #换行符) 返回 (假) .如果真结束 this值 = 控件对象 + 4 开关位 = 0 CopyMemory (取变量地址 (开关位), this值 + 13513, 4) 写日志 (“发送前 → 命令字=” + 到文本 (命令字) + “ this=” + 到文本 (this值) + “ 开关位=” + 到文本 (开关位) + “(0 = 未开,开指令要求为 0)” + #换行符) .如果真 (状态 = 0) ' 诊断就到这儿为止 返回 (真) .如果真结束 '═════ 第三步:发命令(进程内汇编)══════════════════════════ push 0 ' 附参数,先压 push 命令字 ' 命令字,后压 → 落在 [ebp+8] mov ecx, this值 ' thiscall:ecx = this mov eax, 小助手CALL call eax ' FF D0 间接调用 写日志 (“命令已发出” + #换行符) 返回 (真)