본문 바로가기
리버싱

D3D 후킹? 월핵? 원리가 뭔데?

by dladbru 2018. 2. 6.

제목에는 화면제어라고 적었으나  "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의 구조는 바뀌었을 수도 있다. 분석을 해볼만한 가치는 베리베리 ㅅㅌㅊ!

반응형

댓글