제목에는 화면제어라고 적었으나 "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의 구조는 바뀌었을 수도 있다. 분석을 해볼만한 가치는 베리베리 ㅅㅌㅊ!
'리버싱' 카테고리의 다른 글
x32 themida binary debugging in x64 machine 2부 (14) | 2018.05.10 |
---|---|
응용프로그램에서 출력되는 https 패킷 까기 (0) | 2018.03.07 |
C# DotPeek 디컴파일러 (0) | 2018.02.06 |
WinDBG 명령어 정리 (0) | 2018.02.05 |
x32 themida binary debugging in x64 machine (2) | 2016.02.26 |
댓글