들어가며
지난 1편에서는 치트 엔진을 활용해 플레이어 데이터를 읽어올 수 있는 오프셋과 3D 좌표 변환의 핵심인 ViewMatrix를 메모리에서 직접 찾아내는 과정을 다루었습니다.
이번 2편에서는 앞서 확보한 데이터를 바탕으로, 실제 C++ DLL을 제작하여 게임 프로세스 내부에 인젝션(Injection)하고, DirectX 9 렌더링 파이프라인을 후킹하여 화면에 ESP(위치 추적) 박스를 그리는 전체 과정을 구현해 보겠습니다.
이전 포스팅 보러가기
https://imoracle.tistory.com/81
[CS:S] ESP 월핵 / 에임봇 개발기 - 1편: 엔티티리스트 탐색과 오프셋
들어가며게임 클라이언트 해킹 연구는 단순한 메모리 변조를 넘어, 운영체제의 메모리 관리 구조, 3D 렌더링 파이프라인, 그리고 어셈블리 역공학 등을 아우르는 종합적인 기술의 집약체입니다.
imoracle.tistory.com
⚠️ 주의
: 본 포스팅은 게임 보안 연구 및 렌더링 파이프라인 학습 목적으로 작성되었습니다. 모든 실습은 오프라인 환경(로컬 봇 서버)에서만 진행하였으며, 온라인 서버에서의 악용은 게임사의 이용약관 위반 및 법적 제재 대상이 될 수 있음을 명시합니다.
1. 프로젝트 구조
Visual Studio 2022에서 x86 아키텍처 기반의 DLL 프로젝트를 생성하고, 역할을 분리하여 아래와 같이 헤더와 소스 파일을 구성했습니다.

2. DLL 인젝션 구조 설계
외부 프로그램이 아닌 게임 프로세스의 메모리를 직접 공유하기 위해 DLL 인젝션 방식을 사용합니다.
2-1. DllMain 진입점 설정
DLL이 타겟 프로세스(CS:S)에 성공적으로 로드되면 DLL_PROCESS_ATTACH 이벤트가 발생합니다. 이때 메인 스레드를 멈추지 않고 후킹 작업을 진행하기 위해 별도의 스레드를 생성합니다.

DllMain에서의 데드락 주의: DllMain 내부에서 직접 무거운 루틴이나 복잡한 API를 호출하면 Loader Lock에 의해 데드락(Deadlock)이 발생할 위험이 큽니다. 반드시 스레드를 분리하여 초기화를 진행해야 합니다.
2-2. MainThread 로직
생성된 스레드에서는 DirectX 후킹을 초기화하고, 종료 단축키 입력을 대기합니다.

3. DirectX 9 EndScene 후킹
화면 위에 ESP를 자연스럽게 덮어 씌우려면 DirectX의 렌더링 파이프라인에 끼어들어야 합니다. 이를 위해 가장 보편적으로 사용되는 EndScene 후킹을 적용했습니다.
3-1. 왜 하필 EndScene인가?
DirectX 9의 프레임 렌더링 순서는 대략적으로 BeginScene() → [게임 오브젝트 렌더링] → EndScene() → Present() 순으로 진행됩니다.
EndScene()이 호출되는 시점은 게임 엔진이 이번 프레임에 그려야 할 모든 3D 오브젝트의 렌더링을 마친 직후입니다. 따라서 이 시점을 가로채서(Hooking) 우리가 원하는 그림을 추가하면, 게임 화면의 최상단에 ESP가 오버레이되며, 깊이 버퍼(Z-Buffer)를 무시할 수 있어 벽 너머의 적도 표시할 수 있습니다.
3-2. VMT(가상 함수 테이블)와 후킹 지점 탐색
DirectX API 함수들을 가로채려면 VMT 구조를 파악해야 합니다. 보이지 않는 더미(Dummy) 디바이스를 하나 생성하여 메모리상에 올라간 VMT 주소를 추출합니다.

[ 주요 D3D9 Device VMT 인덱스 ]
| 인덱스 | 함수명 | 설명 |
| VMT[17] | Present | 최종 화면 출력 |
| VMT[42] | EndScene | 렌더링 파이프라인 종료 |
| VMT[44] | SetTransform | 카메라/오브젝트 행렬 변환 |
3-3. Microsoft Detours 적용
메모리에 직접 JMP 인스트럭션을 쓰는 수동 패치 방식은 계산 오류로 인한 크래시 위험이 큽니다. 안정적인 트램펄린(Trampoline) 생성을 위해 Microsoft의 Detours 라이브러리를 사용했습니다. (vcpkg를 통해 vcpkg install detours:x86-windows로 쉽게 설치 가능합니다.)

3-4. 가로챈 hkEndScene 구현
게임이 EndScene을 호출할 때마다 아래 함수가 대신 실행됩니다. 이곳이 바로 우리의 "도화지"가 됩니다.

4. WorldToScreen 구현
1편에서 확보한 수학적 기반을 코드로 옮길 차례입니다. 3D 월드 좌표를 2D 화면 좌표로 변환합니다. engine.dll에서 확보한 고정 오프셋(0x2EDC84)을 통해 실시간 ViewMatrix를 읽어옵니다.

5. ESP 렌더링 요소 구현
5-1. 박스 ESP (Bounding Box)
캐릭터의 발 좌표(origin)와 머리 좌표(발 좌표 Z + 72.0f)를 각각 2D로 변환한 뒤, 두 점 사이의 높이를 계산해 직사각형을 그립니다.

5-2. 체력바 (Health Bar)
박스 좌측에 현재 체력 비율에 맞게 채워지는 수직 바를 그립니다. 체력 구간에 따라 시각적인 경고 색상(녹색 → 노랑 → 빨강)을 부여했습니다.

6. 인게임 메뉴 구성
D3D 텍스트 렌더링을 활용해 게임 내에서 실시간으로 기능을 제어할 수 있는 메뉴를 구성했습니다.
| 조작 키 | 기능 설명 |
| HOME | 메뉴 UI 열기 / 닫기 |
| NUMPAD 8 / 2 | 상하 항목 이동 |
| NUMPAD 5 | 선택 항목 기능 토글 (ON/OFF) |
| INSERT | DLL 언로드 |
7. 결과물

8. 개발 삽질 기록 (Troubleshooting)
이론과 실제 구현 사이에서 마주친 뼈아픈 트러블슈팅 기록입니다.
1. DirectX API(GetTransform)에 의존한 오판 초기엔 메모리 서치 없이 편하게 가보려고 D3D API인 GetTransform()으로 행렬을 가져오려 했습니다. 하지만 CS:S 엔진은 최적화 때문인지 매 프레임 SetTransform을 호출하지 않았고, 오직 내가 총을 쏠 때(격발 이벤트)만 행렬 값이 갱신되는 황당한 버그를 겪었습니다. API VMT(VMT[44]) 후킹도 시도했지만, 행우선/열우선 행렬 인덱스 구조 문제로 화면이 미쳐 날뛰었고, 결국 1편에서 다루었듯 Cheat Engine으로 ViewMatrix 원본 주소를 찾아 다이렉트로 읽는 방식으로 회귀하여 완벽한 안정성을 얻었습니다.
2. RenderState 오염으로 인한 시각적 버그 EndScene에 ESP 드로잉 코드만 덜렁 넣고 실행했더니 재앙이 일어났습니다. 하늘을 바라보면 화면에 잔상이 진하게 남고, 내가 그린 ESP 박스 자체가 반투명하게 뭉개졌습니다. 원인은 우리가 DrawLine 등을 사용할 때 DirectX의 렌더링 상태(알파 블렌딩, Z-Buffer 등)를 임의로 변경했는데, 이를 원래대로 되돌려 놓지 않아 게임 엔진의 다음 렌더링 프레임이 오염되었기 때문이었습니다. 앞서 코드에서 강조한 CreateStateBlock(D3DSBT_ALL)을 사용하여 게임 본연의 RenderState를 캡처해 두고, 내 작업이 끝난 뒤 Apply()로 깨끗하게 원복 시켜주어 버그를 완벽히 잡았습니다.
3. 과도한 후킹의 문제 (Present vs EndScene) 초창기 설계 땐 완벽을 기하기 위해 로직은 EndScene, 최종 표출은 Present에서 각각 후킹하여 분리하려 했습니다. 하지만 불필요한 후킹 포인트가 늘어나 충돌 가능성만 커질 뿐이었습니다. 테스트 결과 EndScene 단일 후킹 지점에서 데이터 갱신과 렌더링을 모두 처리하는 것이 가장 직관적이고 퍼포먼스도 좋았습니다.
마치며
이번 2편에서는 외부 프로그램의 한계를 넘어 게임 내부로 침투하는 DLL 인젝션의 구조와, DirectX의 렌더링 파이프라인(EndScene)을 가로채어 우리가 원하는 2D 인터페이스(ESP)를 덧그리는 기술적 과정을 모두 완성했습니다.
다음 3편에서는 단순한 위치 추적을 넘어, 플레이어의 뼈대(Bone) 좌표를 분석해 마우스를 강제로 이동시키는 BoneMatrix 기반 에임봇(Aimbot)과 반동 제어(No Recoil) 구현에 대해 심도 있게 다루어 보겠습니다.
다음 편 예고: BoneMatrix 구조 분석과 정교한 에임봇의 수학적 구현 알고리즘
'Reverse Engineering > GAMEHACK' 카테고리의 다른 글
| [CS:S] ESP 월핵 / 에임봇 개발기 - 1편: 엔티티리스트 탐색과 오프셋 (0) | 2026.06.09 |
|---|---|
| [AntiCheat] 취약 드라이버를 이용한 커널 레벨 안티치트 우회 -1- (3) | 2026.04.08 |
| [게임핵의 역사] 안티치트를 우회해온 기술들의 역사 -왜 게임핵은 사라지지 않는가- (0) | 2026.04.03 |
| [AI 게임핵의 원리] YOLOv8로 CS:S 에임봇 구현하기 (0) | 2026.03.30 |
| [게임핵의 원리] 월핵은 어떻게 만들어지는가 -DirectX 후킹 원리 분석- 최종 (0) | 2026.03.27 |