11、内存与进程检测(服务器辅助):服务器下发检测指令、客户端内存扫描上报、进程白名单机制
聊到反外挂,内存和进程检测这块,可以说是最经典的攻防阵地了。我个人习惯把这类检测叫做“服务器辅助型检测”——说白了,服务器不直接扫描你的内存,它只负责下指令、收结果、做裁决。真正动手的,是客户端的一个安全模块。
为什么要这么设计?你想想看,如果服务器直接去读客户端的内存,那网络开销得多大?而且每个客户端的操作系统、内存布局都不一样,服务器根本管不过来。所以,把扫描任务下放到客户端,服务器只做“裁判”,这才是工程上可行的方案。
11.1 服务器下发检测指令
服务器怎么通知客户端去扫描?不是靠心跳包里带个flag就完事了。我在项目中遇到过,有些外挂会直接篡改心跳包,把检测指令给过滤掉。所以,指令下发必须做到“不可跳过、不可伪造”。
我常用的做法是这样的:
- 指令嵌入协议流:检测指令不单独成包,而是混在正常的游戏协议里。比如,在移动同步包的最后4个字节,塞一个加密的检测指令ID。
- 指令时效性:每条指令带一个时间戳,超过500ms未响应,服务器直接判定异常。
- 指令签名:用服务器私钥对指令内容签名,客户端用公钥验签。这样外挂就算截获了指令,也没法伪造一条假的。
核心要点:指令下发不是“发出去就完事了”,必须做到“发必达、达必验、验必果”。
举个例子,服务器下发一条“扫描进程列表”的指令,大概长这样:
// 服务器端伪代码
function sendScanCommand(client, commandType) {
let cmd = {
type: commandType, // 0x01 = 扫描进程列表
timestamp: Date.now(),
nonce: randomBytes(8), // 防重放
signature: null
};
cmd.signature = rsaSign(cmd.type + cmd.timestamp + cmd.nonce);
client.send(cmd);
}
11.2 客户端内存扫描上报
客户端收到指令后,安全模块开始干活。这里有个坑——你直接扫描整个进程内存,性能开销太大了。我见过有些游戏,一扫描就卡顿半秒,玩家直接骂娘。
所以,内存扫描必须“精准打击”。我一般会维护一个敏感内存特征库,只扫描那些外挂常驻的内存区域:
- 特征码扫描:比如外挂的DLL注入后,内存里会有特定的字符串或字节序列。我们提前提取好特征码,扫描时做模式匹配。
- 内存保护属性检查:正常代码段是“可执行、不可写”。如果发现某块内存是“可执行+可写”,那基本就是外挂动态生成的代码。
- 模块基址校验:检查每个加载的DLL的基址是否和PE文件里的一致。不一致?大概率是被HOOK了。
我的经验:扫描频率别太高。我曾经把扫描间隔设成1秒一次,结果玩家CPU占用率直接飙到15%。后来改成“随机间隔+事件触发”,平均5~10秒一次,效果就好多了。
扫描结果怎么上报?直接发明文肯定不行。我建议这样:
// 客户端上报数据结构
{
"cmdId": "0x01",
"result": {
"processList": [
{"name": "cheat.exe", "pid": 1234, "path": "C:\\cheat\\cheat.exe"}
],
"memoryRegions": [
{"base": 0x400000, "size": 4096, "flags": "RWX", "hash": "a1b2c3d4"}
]
},
"signature": "客户端私钥签名",
"timestamp": 1700000000
}
注意,上报数据一定要签名。不然外挂可以伪造一个“一切正常”的结果来糊弄服务器。
11.3 进程白名单机制
进程白名单,说白了就是“只允许这些进程运行,其他的统统干掉”。这个机制在服务器辅助下,可以做得非常强硬。
我参与过一个项目,白名单机制是这么设计的:
- 服务器维护白名单库:里面存了所有允许的进程名、数字签名哈希、文件路径。
- 客户端定时枚举进程:每隔几秒,客户端枚举当前所有进程,把进程名、签名信息上报给服务器。
- 服务器做裁决:服务器比对上报的进程列表和白名单库。发现不在白名单里的进程,直接下发“踢下线”指令。
注意:白名单不是万能的。有些外挂会把自己的进程名改成“svchost.exe”这种系统进程名来伪装。所以,光靠进程名不够,必须加上数字签名校验。
我曾经遇到过一种外挂,它把自己的DLL注入到游戏进程里,然后进程名还是游戏本身的exe。白名单根本拦不住。后来我们加了一条规则:检查游戏进程的模块列表,只允许加载白名单里的DLL。这才把问题解决。
嗯,这里要注意,白名单机制不能太死板。比如玩家开了个“语音聊天工具”,它的进程名不在白名单里,你直接把人踢了,那玩家不得骂街?所以,白名单要分等级:
| 等级 | 处理方式 | 适用场景 |
|---|---|---|
| L0 | 直接踢下线 | 已知外挂进程 |
| L1 | 弹窗警告,记录日志 | 未知进程,但行为可疑 |
| L2 | 仅记录,不处理 | 常见工具进程(如输入法、录屏软件) |
11.4 整体架构图
说了这么多,我画张图帮你理一理整体流程。这张图展示了服务器、客户端安全模块、外挂三者之间的交互关系:
从图里你能看到,整个流程是闭环的:服务器下指令 → 客户端扫描 → 客户端上报 → 服务器裁决。外挂可以在任何一个环节搞破坏,但我们的每一层都有对应的防御措施。
避坑指南:我曾经犯过一个错——让客户端直接把扫描结果明文上报。结果外挂抓包一看,哦,原来你查的是这几个内存地址,直接把我特征码改了。后来我学乖了,上报数据必须加密,而且特征码要动态混淆,每次扫描用的特征码都不一样。
好了,内存与进程检测这块,核心思路就是“服务器指挥,客户端执行,结果上交给裁判”。别想着客户端能完全防住外挂,那不可能。但只要你的检测指令够隐蔽、扫描够精准、白名单够严格,就能把大部分外挂挡在门外。