D3D 후킹? 월핵? 원리가 뭔데?
제목에는 화면제어라고 적었으나 "D3D를 후킹해 월핵을 써보자!" 하고 다른것은 없다.
하지만 많이 민감한 부분인 만큼 Reversing.kr 의 DirectX FPS 게임 문제를 이용해서 진행하고자 한다. (원리를 기억하기 위해 적어놓는거니까!)
< Figure 0. 벽 넘어 보이는 넘나 귀여운 몹들 >
Reversing.Kr 에서 찾아볼 수 있는 이 문제는 메모리에 키가 암호화되어 있고 한마리를 잡을 때마다 한자리씩 복호화 된다. 하지만 벽뒤에 있어 보이지도
않는 몹들이 많다. 하지만 다른 방법을 찾아서 풀 수 있는 문제다.
근데 이 문제의 이름은 DirectX FPS 게임인만큼 우리가 테스트해보기에 좋은 재료(?)다.
----------------------
Direct3D란?
Direct3D는 마이크로소프트의 DirectX API에서 3차원 그래픽 연산과 출력을 담당하는 부분 이다. Direct3D와 비슷한 역할을 하는 API로는 OpenGL이 있으며 역할은 같지만 각자가 서로 다른 장단점을 가지고 있다.
출처 : http://ko.wikipedia.org/wiki/Direct3D
요약하면 3D게임이나 3D를 요구하는 작업에서 뭔가를 그릴 때 사용하는 게 Direct3D이다.
vTable 이란?
vTable(virtual table)이란 가상 함수의 번지 목록을 가지는 일종의 포인터 배열이다. 즉, 이 클래스에 소속된 가상 함수들이 어떤 번지에 저장되어 있는지를 표 형태로 저장해 놓은 목록이다.
컴파일러는 가상 함수를 단 하나라도 가진 클래스에 대해 vTable을 작성하는데, 이 테이블에는 클래스에 소속된 가상 함수들의 실제 번지들이 선언된 순서대로 기록되어 있다. D3D9 의 vTable에는 42번째에 EndScene가 존재한다.
#include "common.h"
#include "d3dhooks.h"
#include <stdio.h>
#define ENDSCENE 42
DXGH DXGameHook;
typedef HRESULT(__stdcall* EndScene_t)(LPDIRECT3DDEVICE9);
typedef HRESULT(WINAPI* tDrawIndexedPrimitive)(LPDIRECT3DDEVICE9 pDevice, D3DPRIMITIVETYPE PrimType, INT BaseVertexIndex, UINT MinVertexIndex, UINT NumVertices, UINT startIndex, UINT primCount);
EndScene_t org_EndScene;
LPDIRECT3DTEXTURE9 Red;
LPDIRECT3DTEXTURE9 Yellow;
LPDIRECT3DTEXTURE9 Green;
LPDIRECT3DTEXTURE9 Blue;
LPDIRECT3DTEXTURE9 Purple;
LPDIRECT3DTEXTURE9 Orange;
LPDIRECT3DTEXTURE9 Pink;
LPDIRECT3DTEXTURE9 White;
LPDIRECT3DTEXTURE9 Black;
bool chams = false;
bool Color = true;
tDrawIndexedPrimitive oDrawIndexedPrimitive = NULL;
const D3DCOLOR txtPink = D3DCOLOR_ARGB(255, 255, 0, 255);
DWORD D3DPattern, *vTable, DXBase = NULL;
BYTE TEMP[5];
BYTE TEMP2[5];
void *DetourFunc(BYTE *src, const BYTE *dst, const int len)
{
	BYTE *jmp = (BYTE*)malloc(len + 5);
	DWORD dwback;
	VirtualProtect(src, len, PAGE_READWRITE, &dwback);
	memcpy(jmp, src, len); jmp += len;
	jmp[0] = 0xE9;
	*(DWORD*)(jmp + 1) = (DWORD)(src + len - jmp) - 5;
	src[0] = 0xE9;
	*(DWORD*)(src + 1) = (DWORD)(dst - src) - 5;
	VirtualProtect(src, len, dwback, &dwback);
	return (jmp - len);
}
bool bDataCompare(const BYTE* pData, const BYTE* bMask, const char* szMask)
{
	for (; *szMask; ++szMask, ++pData, ++bMask)
		if (*szMask == 'x' && *pData != *bMask)
			return false;
	return (*szMask) == NULL;
}
DWORD FindPattern(DWORD dwAddress, DWORD dwLen, BYTE *bMask, char * szMask)
{
	for (DWORD i = 0; i < dwLen; i++)
		if (bDataCompare((BYTE*)(dwAddress + i), bMask, szMask))
			return (DWORD)(dwAddress + i);
		return 0;
}
HRESULT GenerateTexture(LPDIRECT3DDEVICE9 pDevice, IDirect3DTexture9 **ppD3Dtex, DWORD colour32)
{
	if (FAILED(pDevice->CreateTexture(8, 8, 1, 0, D3DFMT_A4R4G4B4, D3DPOOL_MANAGED, ppD3Dtex, NULL)))
		return E_FAIL;
	WORD colour16 = ((WORD)((colour32 >> 28) & 0xF) << 12)
		| (WORD)(((colour32 >> 20) & 0xF) << 8)
		| (WORD)(((colour32 >> 12) & 0xF) << 4)
		| (WORD)(((colour32 >> 4) & 0xF) << 0);
	D3DLOCKED_RECT d3dlr;
	(*ppD3Dtex)->LockRect(0, &d3dlr, 0, 0);
	WORD *pDst16 = (WORD*)d3dlr.pBits;
	for (int xy = 0; xy < 8 * 8; xy++)
		*pDst16++ = colour16;
	(*ppD3Dtex)->UnlockRect(0);
	return S_OK;
}
void DXGH::DrawRect(LPDIRECT3DDEVICE9 Device_t, int X, int Y, int L, int H, D3DCOLOR color)
{
	D3DRECT rect = { X, Y, X + L, Y + H };
	Device_t->Clear(1, &rect, D3DCLEAR_TARGET, color, 0, 0);
}
HRESULT WINAPI DXGH::h_DIP(PDIRECT3DDEVICE9 pDevice, D3DPRIMITIVETYPE PrimType, INT BaseVertexIndex, UINT MinVertexIndex, UINT NumVertices, UINT startIndex, UINT primCount)
{
	
	memcpy((PBYTE)vTable[82], TEMP2, 5);
	if (NumVertices == 1688 && primCount == 824)
	{
		pDevice->SetRenderState(D3DRS_ZENABLE, D3DZB_FALSE);
		pDevice->SetRenderState(D3DRS_ZFUNC, D3DCMP_NEVER);
		oDrawIndexedPrimitive(pDevice, PrimType, BaseVertexIndex, MinVertexIndex, NumVertices, startIndex, primCount);
		pDevice->SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE);
		pDevice->SetRenderState(D3DRS_ZFUNC, D3DCMP_LESSEQUAL);
			
	}
		
	DWORD ret = oDrawIndexedPrimitive( pDevice,  PrimType,  BaseVertexIndex,  MinVertexIndex,  NumVertices,  startIndex,  primCount);
	(tDrawIndexedPrimitive)DetourFunc((PBYTE)vTable[82], (PBYTE)DXGameHook.h_DIP, 5);
	return ret;
}
HRESULT WINAPI DXGH::h_EndScene(LPDIRECT3DDEVICE9 pDevice)
{
	memcpy((PBYTE)vTable[ENDSCENE], TEMP, 5);
	if (Color)
	{	
		GenerateTexture(pDevice, &Red, D3DCOLOR_ARGB(255, 255, 0, 0));
		GenerateTexture(pDevice, &Blue, D3DCOLOR_ARGB(255, 0, 0, 255));
		Color = false;
	}
	
	DWORD ret= org_EndScene(pDevice);
	(EndScene_t)DetourFunc((PBYTE)vTable[ENDSCENE], (PBYTE)DXGameHook.h_EndScene, 5);
	return ret;
}
LPDIRECT3D9 g_pD3D = NULL;
LPDIRECT3DDEVICE9 g_pd3dDevice = NULL;
DWORD dwback;
int StartD3DHooks()
{
	
	
	DXBase = (DWORD)LoadLibraryA("d3d9.dll");
	printf("DXBase=%x\n", DXBase);
	while (!DXBase);
	{
		D3DPattern = FindPattern(DXBase, 0x128000,
			(PBYTE)"\xC7\x06\x00\x00\x00\x00\x89\x86\x00\x00\x00\x00\x89\x86", "xx????xx????xx");
	}
	if (D3DPattern)
	{
		printf("Pattern Addr= %x\n",D3DPattern);
		memcpy(&vTable, (void *)(D3DPattern + 2), 4);
		memcpy(TEMP, (PBYTE)vTable[ENDSCENE], 5);
		VirtualProtect(((LPVOID)(vTable[ENDSCENE] & 0xFFFFF000)), 0x3000, PAGE_EXECUTE_READWRITE, &dwback);
		VirtualProtect(((LPVOID)(vTable[82] & 0xFFFFF000)), 0x3000, PAGE_EXECUTE_READWRITE, &dwback);
		org_EndScene =(EndScene_t)DetourFunc((PBYTE)vTable[ENDSCENE], (PBYTE)DXGameHook.h_EndScene, 5);
		org_EndScene = (EndScene_t)vTable[ENDSCENE];
		
		memcpy(TEMP2, (PBYTE)vTable[82], 5);
		oDrawIndexedPrimitive = (tDrawIndexedPrimitive)DetourFunc((PBYTE)vTable[82], (PBYTE)DXGameHook.h_DIP, 5);
		oDrawIndexedPrimitive = (tDrawIndexedPrimitive)vTable[82];
	}
	return 0;
}< Figure 1. 프로젝트 파일중 발췌 [ 핵심 ] >
1. d3d9.dll 로드
2. 로드한 메모리에서 메모리 패턴을 찾아내서 VTable 주소를 얻는다.
3. VirtualProtect 를 이용해 후킹한 지점에 모든 권한을 준다 RWX 읽고 쓰고 실행하고 씹고 뜯고 맛보고 즐기고 하고싶은거 다해~
4. VTable 로부터 42번째 존재하는 EndScene를 후킹한다.
5. VTable 로부터 82번째 존재하는 DrawIndexedPrimitive를 후킹한다.
6. 이제 4,5 위 함수에서는 내가 만든 함수를 거쳐서 실제 함수를 호출한다.
7. EndScene 에서는 최초에 한번 Red와 Blue 의 색깔을 생성한다.
8. DrawIndexedPrimitive 에서는 NumVertices 와 primCount가 1688,824 인것은 깊이 버퍼를 무효화해서 벽뒤에 있는 물체도 보이게한다. 1688과 824의 printf를 이용해 여러 객체들을 출력해 알아낼 수 있었다. 유저의 손을 나타내는 값도있을것이며 벽을 나타내는 값도 있을터다.
여기서...
D3DPattern = FindPattern(DXBase, 0x128000, (PBYTE)"\xC7\x06\x00\x00\x00\x00\x89\x86\x00\x00\x00\x00\x89\x86", "xx????xx????xx");
그런데 나는 D3D와 관련된 내용을 찾아보다 VTable을 얻기위해 위의 패턴으로 찾는 이유가 무엇인가요? 항상 저 패턴으로 찾을 수 있는건가요? 라는 질문을 했었고 직접 찾아봤다.
< Figure 3. DirectX FPS 메모리 >
C7 06 XX XX XX XX 89 86 XX XX XX XX ············· 의 패턴에서 C7 06 80 1F 73 57 89 86 ············· 의 값을 볼 수 있다. 이건 VTable 이다!
그래서 해당 메모리로 가보면 메모리 주소들로 가득차있다. 4자리가 하나의 주소값이니 42번째를 보면 30 6D 78 57 이고 이것은 EndScene의 주소 값이다 ㅋㅋㅋㅋ
이렇듯 VTable의 값만 얻어올 수 있다면, D3D의 어느 함수든 후킹이 가능하다. 현존하는 게임에서도 그대로 적용가능하겠다. 하지만 지금 게임들이 바보가 아니고서야 후킹을 되게 해놓진 않았겠지. 하지만, 어떤방법이든 가능하니 게임에 핵들이 판을 치고 있겠지? -_-
마지막으로...
위의 설명은 D3D9 으로 예를 들었으나, D3D11 도 많이쓰이는 최근에는 VTable의 구조는 바뀌었을 수도 있다. 분석을 해볼만한 가치는 베리베리 ㅅㅌㅊ!