들어가며
꽤나 긴 시간 후에 글을 이어 작성하네요. 포스팅하기가 귀찮아져버려 이미 글을 올릴때 작업은 다 해놓았는데 포스팅만 미루다 이제서야 이어 작성합니다. 잃어버린 사진들과 기억속의 뒤죽박죽으로인해 글이 중구난방 두서없을수 있음을 양해드립니다.
AssaultCube와 CounterStrike 1.6 Single를 기반으로 했던거 같은데 사진 유실과 기억유실로 코드가 섞였을 수도있고..
CS 작업한건 어딨는지 보이지 않네요
간단설명
그래픽 API는 3D게임, 렌더링등에 사용되는 필수적인 라이브러리며
익숙하게 들어본적 있는 DirectX 이외에도 OpenGL, Vulkan 등이 존재합니다.
Direct3D Hooking을 통해 월핵을 구현할 예정이므로 DirectX SDK를 설치해주겠습니다.
사실 AssaultCube는 OpenGL기반 게임입니다. 그렇기에 D3D를 아무리 지지고 볶아도 동작할리 만무하죠
당연히 D3D일거라 생각해 해당 라이브러리를 설치하고 진행을 해버렸는데.. 안일한 생각이 일을 늘렸습니다..
그래도 다이렉트X로 삽질하는 부분부터 포팅합니다.

후킹을 직접 구현하기엔 일이 너무 많아집니다. inline hooking 을 예로들면
함수의 앞부분 5byte(x86기준)에 작성해둔 코드로 점프하는 jmp를 박아넣으면
함수가 호출될때 작성해둔 코드로 점프해오고 저장해둔 원본 5byte + 나머지 원본으로 가는 jmp를 어쩌구 저쩌구...
명령어 길이도 계산해 넣어줘야하고.. 어쨌든 굉장히 번거로워 집니다.
그걸 해결하기위해 요즘은 좋은 후킹 라이브러리들이 존재하죠 MS공식 라이브러리인 Detours 를 사용하겠습니다.
사실 처음에는 minhook을 통해서 진행하다 뭔가 꼬여... Detours로 갈아탔습니다.

시작
만들어볼 월핵은 D3D 월핵 또는 DIP 월핵 뭐 이렇게 불릴 수 있겠네요
VTable 에서 함수 주소를 추출해서 EndScene 주소를 획득합니다. 그 후 Detours를 사용해 작성할 코드를 붙이고
디바이스 획득 후 DIP를 후킹하는거죠
DIP는 DrawIndexedPrimitive 로 실제 3D 오브젝트 렌더링을 맡고 있으며 사실상 모든걸 관장한다고 보면 됩니다.
게임의 모든 3D 메시는 이 함수를 통해 그려지기 때문이죠 그렇기에 어떤 모델인지 식별하고, 해당 특정 모델을
벽 너머로도 표시하도록 월핵 구현이 가능한거죠.
VTalbe 얻기

임시 D3D9 디바이스를 생성해서 VTable을 얻어내겠습니다. (GetDeviceFunctionAddress)
Direct3DCreate9() 으로 더미 디바이스를 생성합니다. 그 후
*(void***)pDevice 로 VTable 포인터를 획득하고는 vTable[index] 로 원하는 함수 주소를 return 해줍니다.
참고로 해당 다이렉트 버전에서는 VTable[42] = EndScene, VTable[82] = DIP 입니다.
EndScene 후킹

VTable[42] 에서 EndScene 주소를 획득 후, Detours로 hkEndScene을 붙여줍니다.

함수 내부에선 디바이스값을 획득합니다. g_pDevice.
이제 DIP를 후킹할 차례네요 차근차근 진행중입니다.
DIP 후킹

g_pDevice의 VTable[82]에서 DIP 주소를 획득합니다.
그리고는 똑같이 Detours를 이용해 hkDrawIndexedPrimitive를 붙여줍니다.

NumVertices 와 primCount를 통해 캐릭터 모델을 특정해주려합니다.

Stride는 버텍스 하나의 데이터 크기입니다. 버텍스 하나에 어떤 정보를 담느냐에 따라 크기가 달라지겠죠.
같은 게임이면 보통 항상 같은 Stride를 사용합니다. 모델 포맷이고 고정이니까요
NumVertices는 드로우콜에서 사용하는 버텍스의 총 개수이며
삼각형 1개는 버텍스 3이겠죠. 그렇다면 캐릭터는 수백~ 수천개 이상으로 이루어져있을겁니다만 해당 게임에서는
그래픽이 좋은 최신 게임도 아닐뿐더러 뭐.. 한마디로 맵이거나 이런 큰 오브젝트라면 더 많은 버텍스로 이루어져 있겠죠
primCount 는 그려지는 Primitive Shapes 의 수입니다. D3D9 기준 보통 TRIANGLELIST 또는 TRIANGLESTRIP을 사용하죠
TRIANGLELIST 일때 primCount = 실제 삼각형 개수이며 총 인덱스 수 = primCount x 3 겠네요
예를 들어 NumVertices : 507 | primCount : 445 라면
삼각형 445개의 메시, 인덱스 1335개 사용이 되..겠죠?
이를 바탕으로 모델을 찾을때 서로의 비율도 힌트가 될 수 있습니다. primCount << NumVertices 라면 넓고 단순한 메시겠죠 지형같은.primCount = NumVertices x 2 라면 촘촘한 메시 일겁니다. 캐릭터같은BaseVertexIndex 는 버텍스 버퍼의 시작 오프셋입니다. 뭐 0이라면 버텍스 버퍼가 이 메시 전용이라는 뜻이예요StartIndex는 인덱스버퍼에서 읽기시작위치 입니다. 
로그를 수집하게되면 무수히 많은 로그들이 찍힙니다. 캐릭터가 많은 곳에서 로그수집을 시작하는게 좋겠죠의심되는 값들을 전부 집어넣고 하나씩 테스트 해가며 찾으면 됩니다만
어째서인지 전혀 먹히지 않았습니다. 굉장히 이상한 느낌을 받고 이 게임 혹시 DirectX 아닌거 아니야? 라는 섬뜩한 기운이 뇌리를 스치며 RenderDoc으로 확인해보기로 합니다.
OpenGL 기반이었어 !

네 AssaultCube는 OpenGL 기반의 게임이었습니다. 처음엔 EndScene주소도 잘 얻어오고 하길래 당연히 계속 진행했으나
사실 Windows는 d3d9.dll 을 거의 항상 프로세스에 로드합니다. DirectX는 시스템 컴포넌트라서 게임이 사용하지 않아도 다른이유로 올라와 있는 경우가 많기 때문이죠. 아까 위에서 만든 더미 디바이스의 VTable[42] -> d3d9.dll 안의 진짜 EndScene 함수 주소 -> 후킹성공 (d3d9.dll 함수니까) 이렇게 되었던 거였습니다. 해당 게임은 EndScene을 호출 하지 않습니다.
즉 후킹은 됐으나 직접 작성한 후킹함수인 hkEndScene이 절대 실행되지 않는거죠. 그렇다면
g_pDevice도 영원히 nullptr 일거고 DIP값도 트래시 값만 나오겠죠.
OpenGL인걸 알아차렸으니 OpenGL 후킹으로 노선을 변경해야합니다.
glAlphaFunc 함수는 1.x 레거시 전용 함수이며 현대의 OpenGL에는 존재하지않습니다.
glBegin 또한 3.x에서는 삭제되었기에
OpenGL 1.x 레거시를 사용한다고 볼 수 있겠네요
glDrawElements 하나만 후킹해도 월핵이 될 겁니다 아마
지금이라면 셰이더 단에서 막혀서 훨씬 복잡해지겠죠.

인라인후킹을 직접 구현하겠습니다. 인라인후킹 디토어후킹 트램폴린후킹 다 같은 방식입니다. 부르는 사람마음~
원본 함수 앞 5바이트를 JMP 명령어로 덮어써서 작성한 함수로 진행하도록 흐름을 바꿉니다.
전 지금까지 LONG JMP라 불렀는데 정확한 명칭은 NEAR JMP 더군요..
SHORT JMP 는 OPCODE가 EB로 시작하며 -128 ~ + 127바이트 범위내에서 이동될 때 사용합니다 .
NEAR JMP는 OPCODE가 E9으로 시작하며 +-2GB 까지 이동가능합니다. 같은 JMP 명령어라도 OPCODE는 다르니 유념하시기 바랍니다. 작성한 DLL 메모리영역으로 점프해 작성한 함수를 실행해야하니 당연 128바이트는 넘어가겠죠 그러니 NEAR JMP를 사용합니다. 같은 EXE 내에서 작성해 사용하더라도 128바이트내에서는 거의 불가능 하다고 보시면 되겠네요 보통 코드케이브에 넣어 사용하지만 이또한 멀리 존재하기에..
코드 영역은 기본적으로 쓰기 금지가 되어있으므로 보호해제를 해줍니다.
그리고 원본 5바이트를 저장해줘야 해요 나중에 트램폴린에서 원본을 실행하려면 백업이 필수 입니다.

범위 내 카운트에 걸린 메시에 대해 2번 그려주겠습니다.
reinterpret_cast를 통해 원본 함수로 실제 렌더링을 실행해줍니다.
동작시키게 되면 벽 앞에 플레이어가 있을때는 초록색으로 보이고 벽 뒤일때는 빨간색으로 플레이어가 칠해지겠네요

좌측 계단뒤를 보면 벽에 가려져있는데 파란색으로 보이는 모습이 보입니다.
그런데 지정한 색상으로는 표시가 되질 않네요
아마 OpenGL 1.x 레거시 기반의 게임이라 그런지 지정한 색상이 씹히는거 같습니다.
색을 지정해주었지만 텍스처가 활성화 되어있기에 텍스처 색상(기본팀컬러)이 glColor3f를 덮어쓴거죠
기본 텍스처 모드는 GL_MODULATE 로 최종 색상은 텍스처 색상 x glColor3f색상이 되었을겁니다 아마
실제로는 텍스처가 완전 단색이 아니라 복잡하니 결과적으론 파랗게 보일겁니다.
강제로 색상을 고정하고 싶다면 GL_REPLACE 로 교체 해주면 되지 않을까 생각이 드네요
마치며
아마 이 이후에 CS 1.6으로 진행해 제대로 구현했었는데... 찍어두었던 사진이 보이지 않네요..
찾거나 아니면 새로 만들어 추후에 제대로 두서있게 포스팅 해볼게요 감사합니다.
'Reverse Engineering > GAMEHACK' 카테고리의 다른 글
| [AI 게임핵의 원리] YOLOv8로 CS:S 에임봇 구현하기 (0) | 2026.03.30 |
|---|---|
| [게임핵의 원리] 월핵은 어떻게 만들어지는가 -DirectX 후킹 원리 분석- 최종 (0) | 2026.03.27 |
| [게임핵의 원리] 월핵 제작기 및 월핵에 관해서 -0- (4) | 2025.07.18 |
| [Anti - Reversing] 보안솔루션 직접 제작하기 - 4 - (0) | 2024.07.06 |
| [Anti - Reversing] 보안솔루션 직접 제작하기 - 3 - (2) | 2024.07.06 |