GhostCHD网络封包引擎用户层过LaTale台服nProtect

这篇文章其实不能给外挂初学者带来什么帮助,因为我的目的不是调试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;
}

 

鉴于当年经常被人偷学技术,所以删掉了与挂有关的代码。仅供技术探讨。

防调试代码

程序功能:“断线程”技术防调试
运行平台:x86 & x64
编写时间:2012.12.13-2012.12.14
参考资料:毕业设计第一版、《加密与解密》、网上的一些x64平台资料

00401013  |.  60            PUSHAD
00401014  |.  6A 00         PUSH 0
00401016  |.  6A 00         PUSH 0
00401018  |.  6A 11         PUSH 11
0040101A  |.  6A FE         PUSH -2
0040101C  |.  33C9          XOR ECX,ECX
0040101E  |.  64:8B71 30    MOV ESI,DWORD PTR FS:[ECX+30]
00401022  |.  8B76 0C       MOV ESI,DWORD PTR DS:[ESI+0C]
00401025  |.  8B76 1C       MOV ESI,DWORD PTR DS:[ESI+1C]
00401028  |.  8B6E 08       MOV EBP,DWORD PTR DS:[ESI+8]
0040102B  |.  8B5D 3C       MOV EBX,DWORD PTR SS:[EBP+3C]
0040102E  |.  8B5C2B 78     MOV EBX,DWORD PTR DS:[EBP+EBX+78]
00401032  |.  03DD          ADD EBX,EBP
00401034  |.  8B4B 18       MOV ECX,DWORD PTR DS:[EBX+18]
00401037  |>  8B7B 20       /MOV EDI,DWORD PTR DS:[EBX+20]
0040103A  |.  03FD          |ADD EDI,EBP
0040103C  |.  8B7C8F FC     |MOV EDI,DWORD PTR DS:[ECX*4+EDI-4]
00401040  |.  03FD          |ADD EDI,EBP
00401042  |.  33C0          |XOR EAX,EAX
00401044  |.  99            |CDQ
00401045  |>  3217          |/XOR DL,BYTE PTR DS:[EDI]
00401047  |.  C1C2 05       ||ROL EDX,5
0040104A  |.  AE            ||SCAS BYTE PTR ES:[EDI]
0040104B  |.^ 75 F8         |\JNE SHORT 00401045
0040104D  |.  8B43 24       |MOV EAX,DWORD PTR DS:[EBX+24]
00401050  |.  03C5          |ADD EAX,EBP
00401052  |.  0FB74448 FE   |MOVZX EAX,WORD PTR DS:[ECX*2+EAX-2]
00401057  |.  8B7B 1C       |MOV EDI,DWORD PTR DS:[EBX+1C]
0040105A  |.  03FD          |ADD EDI,EBP
0040105C  |.  8BF5          |MOV ESI,EBP
0040105E  |.  033487        |ADD ESI,DWORD PTR DS:[EAX*4+EDI]
00401061  |.  81FA 8D98A0E2 |CMP EDX,E2A0988D
00401067  |.  74 02         |JE SHORT 0040106B
00401069  |.^ E2 CC         \LOOP SHORT 00401037
0040106B  |>  8B46 01       MOV EAX,DWORD PTR DS:[ESI+1]
0040106E  |.  8BD4          MOV EDX,ESP
00401070  |.  33C9          XOR ECX,ECX
00401072  |.  64:8B35 C0000 MOV ESI,DWORD PTR FS:[0C0]
00401079  |.  85F6          TEST ESI,ESI
0040107B  |.  75 04         JNZ SHORT 00401081
0040107D  |.  CD 2E         INT 2E
0040107F  |.  EB 07         JMP SHORT 00401088
00401081  |>  89E5          MOV EBP,ESP
00401083  |.  FF5E 01       CALL FAR FWORD PTR DS:[ESI+1]            ; Far jump or call
00401086  |.  89EC          MOV ESP,EBP
00401088  |>  83C4 10       ADD ESP,10
0040108B  |.  61            POPAD
HEX: 606A006A006A116AFE33C9648B71308B760C8B761C8B6E088B5D3C8B5C2B7803DD8B4B188B7B2003FD8B7C8FFC03FD33C0993217C1C205AE75F88B432403C50FB74448FE8B7B1C03FD8BF503348781FA8D98A0E27402E2CC8B46018BD433C9648B35C000000085F67504CD2EEB0789E5FF5E0189EC83C41061

【原创】简单通用的Inline Hook代码

前不久给羽毛写一个Hook时弄的,当时用的VirtualAlloc,今天发现可以创建可执行的堆,那么为了节省空间就改成HeapAlloc了。

适用于已经确定要Hook位置和需要取下的代码长度的inline hook,hook为相对地址jmp,占用5字节。不适用于要hook的地址已经有jmp类指令,比如已经被别的程序装了钩子,那样的话需要单独处理,即将原来的钩子移走的同时修改原来钩子的相对跳转值,一般情况用不上,不符合“简单”的要求。

下面贴代码:

HANDLE hHookHeap = NULL;
 
PVOID InstallHook(PVOID CodeAddr, LONG CodeLen, PVOID HookProc)    //ret: StubProc
{
    if (hHookHeap == NULL) hHookHeap = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE, 0, 0);
    DWORD oldProtect;
    VirtualProtect(CodeAddr, CodeLen, PAGE_EXECUTE_READWRITE, &oldProtect);
    PVOID StubProc = HeapAlloc(hHookHeap, 0, CodeLen + 5);
    memcpy(StubProc, (PVOID)CodeAddr, CodeLen);
    *((PBYTE)StubProc + CodeLen) = '\xE9';
    *(PDWORD)((PBYTE)StubProc + CodeLen + 1) = (DWORD)CodeAddr + CodeLen - ((DWORD)StubProc + CodeLen + 5);
    *((PBYTE)CodeAddr) = '\xE9';
    *(PDWORD)((PBYTE)CodeAddr + 1) = (DWORD)HookProc - ((DWORD)CodeAddr + 5);
    VirtualProtect(CodeAddr, CodeLen, oldProtect, &oldProtect);
    return StubProc;
}

VOID UninstallHook(LPVOID CodeAddr, LONG CodeLen, LPVOID StubProc)
{
    DWORD oldProtect;
    VirtualProtect(CodeAddr, CodeLen, PAGE_EXECUTE_READWRITE, &oldProtect);
    memcpy(CodeAddr, StubProc, CodeLen);
    HeapFree(hHookHeap, 0, StubProc);
    VirtualProtect(CodeAddr, CodeLen, oldProtect, &oldProtect);
}