这篇文章其实不能给外挂初学者带来什么帮助,因为我的目的不是调试NP保护下的游戏来找call什么的——事实上,在国服待的几年我已经把这游戏研究透了,不夸张地讲给我一年的全日制时间我能自己逆向写一份这游戏。
GhostCHD引擎只有一个功能——分析游戏的通信协议、然后自己发包。辅助软件坚决不读写游戏内存,即便是当年发布的GhostCHD辅助实际上也是一个小型的独立游戏客户端,而不依赖游戏本身。说白了就是脱机挂。脱机版本的GhostCHD我没有发布过,不过当年接代练生活技能就用的是脱机的,一天能同时接五六个单子,最后受不了的是宽带带宽的说。
这使得我的思路受NP的限制不大。众所周知NP主要防的是外部程序对游戏内存的读写。
在国服,我的思路的Hook部分需要做3个地方:发包的地方、收包的地方、以及游戏主循环中的一个地方。
由于NP据说会对游戏代码段进行校验,曾经有位大神曰过,解决代码段校验的最好办法就是不对游戏代码段进行修改,虽然我听了很恼火,但是想来想去没有更性价比的方法,所以原来的Hook需要改一改。
那么很自然地想到改为ws2_32.dll中的send与recv。这样比以前麻烦一些,主要是send一次有可能发两个以上的包,而recv接收的单位不一定是一个封包而是半个封包,需要把它看作字节流来分析。另外需要自己处理加密与解密——这个倒简单了,标准端不像国服用SDDynDll做动态加密,而是个很简单的xor加密,10年还是11年改过一次算法,不过还是很快被爱好者们贴到ragezone论坛上去了,由于早知道发包相关代码的位置,自己静态分析也非常方便。
至于游戏主循环的那个hook,接触过D3D编程的都知道D3D游戏的窗口循环调用的是PeekMessage。OK,hook到这儿。
因为不知道NP会不会对send和recv这么重要的函数做inline hook检查,所以我决定使用修改IAT的办法做Hook。IAT的具体函数指针的地址可以通过运行不加载NP的情况(如不加参数的启动,会报错)挂载OD调试来找出来。
在计算机程序战争中,永远只有一个公理:谁先来谁是爷。所以注入游戏没有什么防NP的技术——我们只需要在NP加载前把代码注入了就是了。
又有传言NP会扫描程序加载的模块,看有没有可疑的DLL。这个好办,我毕业设计就是干这个的,专门把DLL变成shell code来注入。
台服的客户端加了壳,所以不能注入后就去修改东西,要等壳加载完毕后再修改。最后Hook方面主要的代码就是这个样子:
#include <windows.h> #pragma comment(lib, "ws2_32.lib") void Encrypt(unsigned char *data, unsigned int len) { const char *key = "qmfaktnpgjs"; for (unsigned int i = 0; i < len; ++i) { if (data[i] != 0) data[i] ^= key[i % 11]; } } int PASCAL Hook_send(SOCKET s, const char *buf, int len, int flags) { //some code } int PASCAL Hook_recv(SOCKET s, char FAR * buf, int len, int flags) { //some code } BOOL WINAPI Hook_PeekMessageA(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg) { //some code return PeekMessageA(lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax, wRemoveMsg); } extern "C" __declspec(dllexport) void main() { DWORD* iat_send = (DWORD*)0x98A4AC; DWORD* iat_recv = (DWORD*)0x98A4E4; DWORD* iat_PeekMessageA = (DWORD*)0x98A410; DWORD pfn_send = (DWORD)GetProcAddress(GetModuleHandleA("ws2_32.dll"), "send"); DWORD pfn_recv = (DWORD)GetProcAddress(GetModuleHandleA("ws2_32.dll"), "recv"); DWORD pfn_PeekMessageA = (DWORD)GetProcAddress(GetModuleHandleA("user32.dll"), "PeekMessageA"); while (!(*iat_send == pfn_send && *iat_recv == pfn_recv && *iat_PeekMessageA == pfn_PeekMessageA)) { Sleep(10); } *iat_send = (DWORD)&Hook_send; *iat_recv = (DWORD)&Hook_recv; *iat_PeekMessageA = (DWORD)&Hook_PeekMessageA; }
鉴于当年经常被人偷学技术,所以删掉了与挂有关的代码。仅供技术探讨。
多年以后 居然让我在这里看到了 GhostCHD 哈哈~
热烈欢迎原住岛民加入程序员大家庭 哈哈~
邮箱给力!偶也做一个这样的~
回看过往还是对 SDDynDll 代码VM无力。。。