학회_공부해요/기술_스터디

[Theori] 게임핵의 원리(1) - Wall Hack

yenas0 2025. 2. 19. 06:59
반응형

https://blog.theori.io/game-hacking-1-881e940ffe00

 

게임핵의 원리에 대해 알아보자 (1) — Wall Hack 편

FPS 게임에서 자주 발견되는 “Wall Hack” (월핵)은 벽 너머의 적을 보여주어 위치를 알 수 있도록 한다. 월핵을 구현하는 방법은 그래픽 렌더링 라이브러리마다 조금씩 다르다.

blog.theori.io


월핵(Wall Hack)이란?

FPS게임 내에서 구조물 뒤의 시야를 제공하는 핵(벽 너머의 적 을 확인할 수 있다)

 

Direct X(Direct3D, D3D)로 개발해보는 월핵 구현 방법

 

D3D9.ver

Z Buffer(Depth Buffer)에 대한 이해

Z Buffer란 렌더링 시 어떤 물체가 보여야 할지에 대한 여부를 판별하기 위해 사용되는 방법 중 하나

https://ko.wikipedia.org/wiki/Z_%EB%B2%84%ED%8D%BC%EB%A7%81

 

Z 버퍼링 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. Z 버퍼 데이터 컴퓨터 그래픽스에서 Z 버퍼링(z-buffering)은 3차원 그래픽스의 이미지 심도 좌표 관리 방식이며, 일반적으로는 하드웨어적으로 처리되나 이따금은

ko.wikipedia.org

 

https://racketboy.com/retro/about-video-games-rasterization-and-z-buffer#google_vignette

 

이 이미지로 봤을 때 S1이 제일 멀고 S3이 제일 가까움

1번은 아무것도 그리지 않았을 대고 2, 3, 4 순서대로 S2, S1, S3도형을 그렸을 때이다.

위에서 볼 수 있듯이 가장 가까운 도형이 버퍼를 차지하게 된다.

 

Z Buffer 월핵의 기본 원리

특정 물체를 렌더링 할 때 Z Buffer 기능을 비활성화하면 렌더링 엔진은 해당 물체가 보여야 할 지의 여부를 구별할 수 없어 물체를 항상 화면에 보여주되는데 이를 이용한다. Z Buffer 기능 비활성화를 위해렌더링 흐름을 제어해야함 -> Direct3D의 라이브러리 함수 후킹해야함

 

D3D9 Hook

1. 후킹 할 함수의 주소 찾기

Direct3D의 동작 기본 원리

Direct3D 인터페이스를 생성하는 코드

// this function initializes and prepares Direct3D for use
void initD3D(HWND hWnd){
    d3d = Direct3DCreate9(D3D_SDK_VERSION);    // create the Direct3D interface

    D3DPRESENT_PARAMETERS d3dpp;    // create a struct to hold various device information

    ZeroMemory(&d3dpp, sizeof(d3dpp));    // clear out the struct for use
    d3dpp.Windowed = TRUE;    // program windowed, not fullscreen
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;    // discard old frames
    d3dpp.hDeviceWindow = hWnd;    // set the window to be used by Direct3D

    // create a device class using this information and information from the d3dpp stuct
    d3d->CreateDevice(D3DADAPTER_DEFAULT,
                      D3DDEVTYPE_HAL,
                      hWnd,
                      D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                      &d3dpp,
                      &d3ddev);
}

http://www.directxtutorial.com/Lesson.aspx?lessonid=9-4-1

 

DirectXTutorial.com

First of all, I officially welcome you to Direct3D. I would like to teach you both the basics and the advanced topics of 3D programming. Whether you want to build you own engine, borrow one and modify it, or just buy one and use it, it is important that yo

www.directxtutorial.com

 

 

후킹 과정

1. 게임 프로세스의 메로리에 접근하기 위해 DLL을 인젝션

https://yenas0.tistory.com/260

 

23. DLL 인젝션

DLL 인젝션 DLL 인젝션은 실행 중인 다른 프로세스에 특정 DLL 파일을 강제로 삽입하는 기법. 이는 다른 프로세스로 하여금 LoadLibrary() API를 스스로 호출하도록 명령하여 사용자가 원하는 DLL을 로딩

yenas0.tistory.com

이건 내가 리버싱 공부할 때 적어둔 DLL 인젝션...

기사 원글에는 내용이 많지 않으니 위에 걸로 다시 공부..

 

2. vtable의 주소 찾기

IDirect3DDevice9 인터페이스의 vtable 찾기 위해 CreateDevice 함수를 기점으로 분석(인터페이스 생성하는 함수라)

2.1. CreateDivice 함수에서 IDirect3DDevice9 반환과정

- CreateDevice 함수는 ppReturnedDeviceInterface를 통해 IDirect3DDevice9 인터페이스를 반환

- CreateDeviceImpl 함수에서 8번째 인자로 ppReturnedDeviceInterface를 전달해서 값 설정

2.2. CreateDeviceImp 함수 내부 흐름

- CD3DHal::CD3DHal 함수에서 객제(v26)가 생성되고, 이게 ppReturnedDeviceInterface로 전달

- 최종적으로 CD2DHal 객체가 IDirect3DDevice9의 인터페이스 역할

2.3. CD2DHal::CD3DHal의 역할

- 객체 생성 후, vtable을 CD3DHal::vtfable로 초기화

- IDirect3DDevice9의 실제 vtable 주소가 포함된다

2.4. vtable 주소를 찾기 위한 패턴 추출

- CD3DHal::CD3DHal함수의 어셈블리 코드에서 vtable 설정 부분 확인

C7 06 ?? ?? ?? ?? 89 86 ?? ?? ?? ?? 89 86
// C7 06 ?? ?? ?? ?? → vtable을 설정하는 명령어.
// 89 86 ?? ?? ?? ?? → 해당 객체의 특정 필드 초기화.
// 환경에 따라 오프셋이 달라질 수 있어 일부는 ??로 치환.

2.5. 패턴 검색 함수

bool bCompare(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 (bCompare((BYTE*)(dwAddress + i), bMask, szMask))
            return (DWORD)(dwAddress + i);
    return 0;
}

- 특정 패턴(bMask)을 메모리에서 검색하는 함수

- FindPattern을 사용해 d3d9.dll에서 CD3DHal::CD3Dhal의 vtable 주소를 찾음

2.6. vtable 주소 찾기 코드

DWORD table = FindPattern(
    (DWORD)hModule,
    0x128000,
    (PBYTE)"\xC7\x06\x00\x00\x00\x00\x89\x86\x00\x00\x00\x00\x89\x86",
    "xx????xx????xx" // 가변적인 주소 부분은 ?로 마스킹
);
memcpy(&vTable, (void*)(table + 2), 4); // vtable 주소 값을 복사

- FindPattern을 통해 vtable 주소를 포함한 메모리 패턴을 찾음

- memcpy를 사용해 vtable의 실제 주소를 vTable 변수에 저장

 

3. DrawIndexedPrimitive 함수 주소 구함

3.1. DrawIndexedPrimitive(DIP) 함수란?

- D3D9에서 도형 및 물체를 렌더링할 때 사용하는 주요 함수

- 성능상의 이유로 Direct3D개발에서 가장 많이 사용됨

- IDirect3DDevice9의 가상 함수 테이블(vtable)에 존재한다

3.2. vtable index를 이용한 DIP 함수 주소 찾기

- d3d9.dll의 vtable은 컴파일된 상태에서 고정된 인덱스를 가짐

- 이미 알려진 IDirect3DDevice9의 vtable index를 활용해 함수 주소 찾기

3.3. IDirect3DDevice9의 vtable index값

#define QUERY_INTERFACE             0
#define ADDREF                      1
#define RELEASE                     2
#define TESTCOOPERATIVELEVEL        3
#define GETAVAILABLETEXTUREMEM      4
...
#define ENDSCENE                    42
...
#define DRAWINDEXEDPRIMITIVE        82
#define DRAWPRIMITIVEUP             83
#define DRAWINDEXEDPRIMITIVEUP      84

- DrawIndexedPrimitive의 vtable index는 82

2.4. vtable 주소를 이용한 DIP 함수 주소 계산

- IDA에서 확인된 CD3DHal::vftable주소는 0x10001D24

- DrawIndexedPrimitive의 vtable index는 82

- 32비트 환경에서 포인터는 4바이트이므로 0x10001D24 + (82 * 4) = 0x10001E6C 가 CD3DBase::DrawIndexedPrimitive 함수 포인터의 실제 위치와 일치한다.

2.5. 코드로 vtable에서 DIP 함수 주소 찾기

DWORD vTableBase = GetVTableBaseAddress(); // vtable의 시작 주소 구하기
DWORD dipFunctionAddress = *(DWORD*)(vTableBase + (82 * 4)); // 82번째 인덱스에서 주소 가져오기

- GetVTableBaseAddress() 함수는 vtable의 시작 주소를 반환하는 코드

- 82 x 4 바이트를 더한 위치에서 drawIndexedPrimitive함수의 주소를 추출

 

4. Inline Hook 설치

DIP 함수의 주소 구했으니 후킹해 Z Buffer 기능 비활성화 하는 단계

2.1. Inline Hook이란?

- 게임이나 프로그램에서 특정 함수를 후킹해 원하는 코드를 실행하는 기술

2.2. Inline Hook 어떻게 동작하나?

- 원본 함수(Target Function)의 시작 부분을 가로챔: jmp DetourFunction 명령어를 삽입해 원래 실행될 함수 대신 다른 함수(Detour)를 실행하도록 변경

- Trampoline을 사용하여 원본 함수도 실행 가능: 원본 함수의 프롤로그(처음 몇 바이트)를 저장한 후, 후킹 함수 실행 후 다시 원래 함수로 돌아갈 수 있도록 함

 

hkDrawIndexedPrimitive 함수에 후킹하는 코드를 작성해 사물이 렌더링 되는 시점에 원하는 코드를 실행할 수 있다.

 

5. 숨겨진 물체를 보이게 함

5.1. Z Buffer 비활성화

- DIP 함수를 통해 물체를 그리기 전에 Z Buffer를 비활성화해 물체가 벽 너머에서도 보일 수 있게 만든다.

5.2. oDrawIndexPrimitive 호출

- Z Buffer가 비활성화된 상태에서 물체를 그리기 위해 원본 DrawIndexedPrimitive함수를 호출

5.3. Z Buffer 활성화

- Z Buffer를 다시 활성화해서 다른 물체가 정상적으로 그려질 수 있도록 함

 

 

근데 위 과정을 쭉~하면 모든 물체가 보이게 된다 -> GlassWall

내가 원하는 대상만 보이게 하고 싶다면 원하는 물체가 무엇인지에 대한 조건을 추가한다.

 

어떻게 조건을 추가할까? -> Stride 값을 이용

Stride 값이란?

- 정점 버퍼 구조체의 크기를 가지고 있는 값

원하는 물체의 Stride값 == 렌더링하고 있는 물체의 Stride 값? -> Z Buffer 비활성화하면 원하는 물체만 볼 수 있다.

어떻게 비교할까? - Stride 값을 조금씩 증가시키면서 어떤 값일때 원하는 물체가 표현되는지 직접 확인한다

HRESULT __stdcall hkDrawIndexedPrimitive(
	LPDIRECT3DDEVICE9 pDevice,
	D3DPRIMITIVETYPE pType,
	INT BaseVertexIndex,
	UINT MinVertexIndex,
	UINT NumVertices,
	UINT startIndex,
	UINT primCount
){
	IDirect3DVertexBuffer9* pStreamData;
	UINT iOffsetInBytes, iStride;
	pDevice->GetStreamSource(0, &pStreamData, &iOffsetInBytes, &iStride);
	if (iStride == 32) // Stride 값 비교
	{
		pDevice->SetRenderState(D3DRS_ZENABLE, D3DZB_FALSE); // Z Buffer 비활성화
		// Drawing 전
		oDrawIndexedPrimitive(pDevice, pType, BaseVertexIndex, MinVertexIndex, NumVertices, startIndex, primCount);
		// Drawing 후
		pDevice->SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE); // Z Buffer 활성화
	}
	return oDrawIndexedPrimitive(pDevice, pType, BaseVertexIndex, MinVertexIndex, NumVertices, startIndex, primCount); // Stride 값이 우리가 원하는 값이 아니더라도 호출되어야 함
}

 

 

근데 보통 게임할 때보는 월핵은 사물에 형광색이 쳐져있음. 이 형광월핵은 어케할까??

- 색을 입히기 위해 물체에 생성한 텍스쳐를 설정한다.

HRESULT GenerateTexture(IDirect3DDevice9* pD3Ddev, IDirect3DTexture9 **ppD3Dtex, DWORD colour32){
	if (FAILED(pD3Ddev->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;
}

bool generated = false;
LPDIRECT3DTEXTURE9 red, green;
HRESULT __stdcall hkDrawIndexedPrimitive(LPDIRECT3DDEVICE9 pDevice, D3DPRIMITIVETYPE pType, INT BaseVertexIndex, UINT MinVertexIndex, UINT NumVertices, UINT startIndex, UINT primCount)
{
	IDirect3DVertexBuffer9* pStreamData;
	UINT iOffsetInBytes, iStride;
	if (!generated) // 이미 빨간색, 초록색 텍스쳐가 생성되어 있으면 다시 생성할 필요가 없음
	{
		GenerateTexture(pDevice, &red, D3DCOLOR_ARGB(255, 255, 0, 0)); // 빨간색 텍스쳐
		GenerateTexture(pDevice, &green, D3DCOLOR_ARGB(255, 0, 255, 0)); // 초록색 텍스쳐
		generated = true;
	}
	pDevice->GetStreamSource(0, &pStreamData, &iOffsetInBytes, &iStride); // 물체의 stride값을 가져옴
	if (iStride == 32)
	{
		pDevice->SetRenderState(D3DRS_ZENABLE, D3DZB_FALSE); // Z Buffer 비활성화
		pDevice->SetTexture(NULL, red); // 물체를 빨간색 텍스쳐로 설정
		// Drawing 전
		oDrawIndexedPrimitive(pDevice, pType, BaseVertexIndex, MinVertexIndex, NumVertices, startIndex, primCount);
		// Drawing 후
		pDevice->SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE); // Z Buffer 활성화
		pDevice->SetTexture(NULL, green); // 물체를 초록색 텍스쳐로 설정
	}
	return oDrawIndexedPrimitive(pDevice, pType, BaseVertexIndex, MinVertexIndex, NumVertices, startIndex, primCount);
}

 

GenerateTexture 함수란?

- CreateTextrue를 사용해 텍스처를 생성하는 D3D 함수

 

후킹 과정

1. 빨간색 & 초록색 텍스처 생성

- GenerateTexture로 각각 생성

2. Z Buffer 비활성화 & 빨간색 텍스처 적용

- 특정 물체(표현하고 싶은거)를 감지하면 Z Buffer 비활성화

- SetTexture로 빨간색 텍스처 설정

- oDIP()호출 - > 벽 뒤에 가려진 부분까지 빨간색으로 표시

3. Z Buffer 활성화 & 초록색 텍스처 적용

- SetTexture로 초록색 텍스처 설정

- 다시 oDIP() 호출 -> 시야에 보이는 부분만 초록색으로 렌더링

반응형