들어가며
게임 클라이언트 해킹 연구는 단순한 메모리 변조를 넘어, 운영체제의 메모리 관리 구조, 3D 렌더링 파이프라인, 그리고 어셈블리 역공학 등을 아우르는 종합적인 기술의 집약체입니다. '게임 핵의 역사'를 되짚어보면 초기 유저 모드에서의 단순한 메모리 읽기/쓰기에서 시작해, 현재는 커널 레벨이나 하이퍼바이저 단에서 안티치트와 창과 방패의 싸움을 벌이는 수준까지 발전했습니다.
이전에 본 게임(CS:S)을 대상으로 DirectX(D3D) Hooking을 수행하여, 모델의 Stride 값을 기반으로 색상을 입히는 Chams(월핵의 일종)를 구현해 본 적이 있습니다. 하지만 Stride 방식은 렌더링되는 폴리곤의 고유값에만 의존해 객체를 식별하기 때문에, 모델이 겹치거나 특정 환경 요소와 혼동될 때 필연적으로 정확도가 떨어지는 태생적 한계가 있었습니다.
https://imoracle.tistory.com/67
[게임핵의 원리] 월핵은 어떻게 만들어지는가 -DirectX 후킹 원리 분석- 최종
들어가기에 앞서...본 포스팅은 학술적 연구 및 리버싱 학습을 목적으로 작성되었습니다. 모든 테스트는 오프라인(Local Bot Match) 환경에서 진행되었으며, 실제 멀티플레이 서버나 타인의 게임 경
imoracle.tistory.com
또한, 외부에서 화면을 캡처해 적을 인식하는 YOLO v3 기반의 AI 에임봇도 별도로 개발해 보았으나, 이 역시 비전(Vision) 처리 과정의 딜레이와 인식률 문제로 인해 게임 엔진 내부의 데이터를 직접 다루는 것만큼 빠르고 정교하지는 못했습니다.
https://imoracle.tistory.com/68
[AI 게임핵의 원리] YOLOv8로 CS:S 에임봇 구현하기
들어가며이전 포스팅에서 D3D9 HOOKING 으로 렌더링 파이프라인을 건드려봤다면, 이번엔 전혀 다른 접근법입니다.게임 내부를 전혀 건드리지 않고 오직 화면 캡처와 AI만으로 에임봇을 구현합니다.
imoracle.tistory.com
이러한 렌더링 파이프라인의 틈새를 노리는 방식이나 외부 이미지 프로세싱의 한계를 넘어서기 위해, 이번 시리즈에서는 게임 메모리에 직접 접근하는 방식을 채택했습니다. 게임 프로세스 깊숙이 침투하여, 엔진이 사용하는 3D 좌표계와 구조체 데이터를 직접 읽어오는 것입니다.
대상은 여전히 훌륭한 시스템 학습 교보재인 Counter-Strike: Source (Protocol 7)입니다. 본 포스팅에서는 과거의 불완전했던 방식들을 뒤로하고, 메모리 분석을 통해 오차율 없는 정교한 ESP(Extra Sensory Perception)와 에임봇(Aimbot)을 한 번에 구현해 내는 전체 과정을 상세히 기록하고자 합니다. 표면적 조작에서 메모리 구조 자체를 파고드는 과정을 함께 확인해 보시기 바랍니다.

⚠️ 주의
: 본 포스팅은 게임 보안 연구 및 시스템 구조 학습 목적으로 작성되었습니다. 모든 실습은 오프라인 환경(로컬 봇 서버)에서만 테스트되었으며, 온라인 서버에서의 악용은 게임사의 이용약관 위반 및 법적 제재 대상이 될 수 있음을 명시합니다.
대상환경
최대한 구버전으로 진행하였습니다. 초기 버전의 게임이며 해당 빌드의 오프셋은 구글에서조차 찾지 못합니다. 직접 찾는 여정에 함께해 보세요, 멀티서버는 존재하지도 않는 구버전이지만 빌드버전은 혹여 악용될 우려로 인해 밝히지 않겠습니다
게임 : Counter-Strike : Source
버전 : Protocol version 7
엔진 : Source Engine (DirectX 9 기반)
1. 오프셋 탐색 (Offset Exploration)
ESP나 에임봇이 정상적으로 작동하려면 적군의 위치, 체력, 팀 정보 등의 데이터를 실시간으로 읽어와야 합니다. 게임 업데이트마다 변동될 수 있는 동적 메모리 환경에서, 모듈(DLL)의 베이스 주소를 기준으로 한 고정된 거리(Offset)를 찾아내는 것이 첫걸음입니다. 본 과정에서는 Cheat Engine을 활용해 주요 오프셋을 역추적했습니다.
1-1. 엔티티리스트(EntityList) 추적
Source 엔진 뿐만이 아니라 FPS게임에서는 모든 플레이어와 오브젝트 객체는 전역 배열에 포인터 형태로 관리됩니다. 그리고 이를 보고 우리는 엔티티리스트 또는 플레이어포인터라고 부르기도 합니다.

Cheat Engine을 통해 분석한 결과, client.dll의 베이스 주소에서 0x30E3A4만큼 떨어진 곳에서 이 포인터 배열이 시작되는 것을 확인했습니다. 각 플레이어 객체의 슬롯은 0x10(16바이트) 간격으로 배치되어 있으므로, 인덱스를 통해 특정 플레이어의 베이스 포인터에 접근할 수 있습니다.

Cheat Engine에서 내 캐릭터의 체력 값(예:100)을 4Byte 타입으로 스캔한 뒤, 피해를 입을 때마다 감소한 수치로 Next Scan을 반복하여 정확한 메모리 주소를 특정했습니다.

정확한 주소를 특정하기 위해 1중 포인터 스캔을 통해 범위를 좁혀나갑니다.

메인 모듈이 결과로 뜨는 값을 찾았다면, 현재 몇 명의 봇 플레이어들도 추가된 상태이기에, 제일 많이 보이는 F94이지 않을까 유추해보며 Client.dll 값들을 구조체에 넣어보며 하나하나 확인해봅니다.

엔티티 리스트를 찾았습니다.
1-2. C_CSPlayer 구조체 내부 오프셋
EntityList에서 얻어낸 개별 플레이어의 베이스 주소를 기준으로, 내부 구조체(C_CSPlayer)에 접근해 필요한 상태 값들을 추출해야 합니다.

구조체 내에서 찾으면 쉽게 찾을 수 있습니다.

팀 번호의 경우 테러리스트(T=2)와 대테러리스트(CT=3) 진영을 번갈아 선택하며 Byte 타입으로 값을 좁혀나갔으며,

플레이어의 X, Y, Z 좌표의 경우 캐릭터를 움직이며, 앵글의 경우 마우스를 굴려가며 찾았습니다.
1-3. ViewAngles (시점 각도)
플레이어의 시점 각도(Pitch/Yaw) 데이터는 클라이언트 구조체 내부에도 복사본이 존재하지만, 에임봇 구현을 위해 시점을 강제로 조작(Write)하려면 입력 처리를 담당하는 engine.dll 영역의 전역 변수에 직접 접근해야 합니다.

2. ViewMatrix의 이해와 탐색
앞서 언급했듯 Stride Chams는 렌더링 파이프라인의 표면을 가로채는 방식이고, YOLO v3는 화면에 출력된 결과물(픽셀)을 사후 분석하는 방식입니다. 반면, ESP(Extra Sensory Perception)의 핵심은 게임 엔진이 연산하는 '실시간 3D 월드 좌표'를 우리 모니터의 '2D 화면 픽셀 좌표'로 정확히 투영(Projection)하는 것입니다.
이 오차 없는 정교한 좌표 변환을 가능하게 만드는 수학적 매개체가 바로 ViewMatrix(뷰 행렬)입니다. 3차원 공간상의 적군 위치 좌표를 내 화면의 몇 행, 몇 열 픽셀에 그려야 하는지 계산하려면 이 행렬을 완벽히 이해해야 합니다.
2-1. 3D 좌표 변환 파이프라인
게임 속 3D 공간의 물체가 최종적으로 우리 모니터(2D)에 매핑되기까지는 다음과 같은 엄격한 기하학적 파이프라인을 거치게 됩니다.
[World Space (월드 좌표계)]
- 게임 맵 전체를 기준으로 한 절대적인 3차원 좌표 (X, Y, Z)
- 예: 적군의 실시간 위치 데이터 (m_vecOrigin) - > [View Matrix 변환]: 카메라(내 시점)를 원점(0,0,0)으로 회전 및 이동
[View Space (뷰/카메라 좌표계)]
- 내 캐릭터의 시점을 기준으로 재정렬된 3차원 공간 -> [Projection Matrix 변환]: 시야각(FOV)과 원근감(가까운 것은 크게, 먼 것은 작게) 반영
[Clip Space (클립 좌표계)]
- 모니터에 표시될 가시 영역(Frustum) 내부의 공간
- 이 단계에서 카메라 뒤에 있거나 시야를 벗어난 객체를 잘라내는 '클리핑' 작업 수행 -> [Viewport Transform]: 원근 나누기(Perspective Divide) 및 해상도 매핑
[Screen Space (화면 좌표계)]
- 우리가 실제로 보는 모니터의 2D 픽셀 좌표 (X, Y)
일반적인 그래픽스 프로그래밍에서는 View Matrix와 Projection Matrix를 각각 계산하지만, 성능이 중요한 FPS 게임의 런타임 환경(그리고 이를 역공학하는 핵 개발자 입장)에서는 이 두 행렬이 하나로 곱해진 합성 행렬(View-Projection Matrix)을 찾아내어 한 번에 연산하는 것이 효율적입니다.
2-2. 4×4 ViewMatrix의 수학적 구조 및 데이터 해석
Source 엔진에서 사용되는 ViewMatrix는 4×4 부동소수점(float) 행렬로, 메모리상에는 총 16개의 float 값(64바이트)이 연속적으로 배열된 형태를 가집니다.
[ m[0][0] m[0][1] m[0][2] m[0][3] ] → 1~3열: 카메라의 Right(우측) 벡터 및 회전 정보
[ m[1][0] m[1][1] m[1][2] m[1][3] ] → 1~3열: 카메라의 Up(상단) 벡터 및 회전 정보
[ m[2][0] m[2][1] m[2][2] m[2][3] ] → 1~3열: 카메라의 Forward(정면) 벡터 및 회전 정보
[ m[3][0] m[3][1] m[3][2] m[3][3] ] → 4열: 공간 이동(Translation) 및 원근(W) 계수
- 1열 ~ 3열 (회전 성분): 카메라의 방향 사영(Orientation)을 담당하므로, 삼각함수 연산 결과에 의해 모든 값이 항상 -1.0에서 1.0 사이의 소수점 내에서 유기적으로 움직입니다.
- 4열 (이동 및 스케일 성분): 월드 좌표계의 원점과 카메라 사이의 절대적 거리가 반영되므로, 게임 맵의 크기에 따라 수백에서 수천 단위의 큰 수(실수)가 포함될 수 있습니다.
실제로 Cheat Engine을 통해 소스 엔진 내부에서 포착한 실시간 ViewMatrix 데이터 블록의 예시는 다음과 같습니다.
-0.05 -0.75 0.00 -664.23
0.00 0.00 1.33 -248.04
1.00 -0.07 0.00 393.56
1.00 -0.07 0.00 400.46
앞선 3개의 열이 철저하게 정규화된 소수점 범위를 유지하는 반면, 가장 오른쪽 열은 -664.23, 393.56과 같이 맵 상의 물리적 거리를 나타내는 큰 수들이 정렬되어 있는 것을 확인할 수 있습니다. 데이터의 이러한 정형화된 형태적 특징을 기억해 두면 메모리 스캔 시 수많은 더미 데이터 사이에서 오차 없이 ViewMatrix를 구별해 낼 수 있습니다.
2-3. ViewMatrix 탐색 방법 (Cheat Engine 실전 역공학)
메모리 상에서 ViewMatrix의 기하학적 특성을 이용하면 코딩 없이 Cheat Engine만으로도 매우 직관적이고 빠르게 정적 주소를 찾아낼 수 있습니다. 시점을 극단적으로 제어하는 방법이 가장 정석적입니다.

- 초기 스캔: 게임 내에서 시점을 행렬 내 특정 원소들이 1.0 혹은 -1.0에 위치하도록 마우스 앵글을 옮겨 스캔해줍니다.
- 조건 변경 및 변동 스캔: 1.0으로 초기스캔을 진행했다면 -1.0으로 변경 후 재스캔 해줍니다
- 반복 필터링: 계속 반복해주어 값을 점진적으로 좁혀나갑니다
- 메모리 구조 확인 (Memory Browse): 필터링을 거치면 engine.dll+2EDCAC, engine.dll+2EDCBC 등 engine.dll 기반의 몇 가지 유력한 메모리 포인터 후보군들이 남게 됩니다. 이 주소 중 하나를 선택해 우클릭 후 Browse this memory region을 실행합니다.
- 최종 오프셋 확정: 메모리 뷰어에서 4바이트(float) 단위로 데이터를 나열했을 때, 4x4 행렬 구조 형태(64바이트)를 유지하며 내 시점 전환에 따라 16개의 float값이 한꺼번에 일사불란하게 움직이는 연속된 메모리 블럭을 찾습니다. 그 거대한 변화의 시작점(배열의 첫 번째 원소 주소)을 역산하여 정적 오프셋을 최종 확정합니다

마우스를 움직이니 일사불란하게 움직이는 메모리 들이 보여 위로 올려 전체를 봐줍니다

그리고 그 시작점을 첫 메모리로 맞춘 후 보기쉽게 4x4로 보이도록 하고 확인합니다.

2-4. 꼼수와 실패의 기록: 왜 메모리를 직접 뒤져야 했는가?
사실 처음부터 무식하게 게임 메모리를 스캔하여 ViewMatrix를 찾을 생각은 없었습니다. 게임 엔진도 결국 화면을 그리기 위해 DirectX를 거치므로, DirectX API를 가로채면(Hooking) 행렬 데이터를 쉽게 날로 먹을 수 있을 것이라는 계산이었습니다.
하지만 Source 엔진은 호락호락하지 않았고, 다음과 같은 처절한 실패 과정을 겪어야 했습니다.
1차 시도: GetTransform() API호출
- 접근: DirectX 디바이스 포인터를 얻어온 뒤, GetTransform(D3DTS_VIEW)를 호출해 뷰 행렬을 얻어오려 했습니다.
- 결과: 반환된 행렬 값이 0.00으로 고정되어 있었습니다. 기이하게도 총을 쏠 때(격발 이펙트 발생 시)만 값이 일시적으로 갱신되었습니다.
- 분석: CS:S(Source 엔진)는 퍼포먼스 최적화를 위해 매 프레임마다 D3D 파이프라인에 SetTransform을 호출하지 않고, 내부적으로 좌표를 연산하여 버텍스를 직접 밀어 넣는 방식을 사용한다고 생각합니다.
2차 시도: SetTransform VMT 후킹
- 접근: Microsoft Detours 라이브러리를 사용해 DirectX 디바이스의 가상 함수 테이블(VMT) 44번 인덱스인 SetTransform을 직접 후킹했습니다.
- 결과: callCount가 폭발적으로 증가하는 것을 보아 후킹 자체는 완벽히 성공했습니다. 하지만 가로챈 행렬로 WorldToScreen을 계산하니 좌표가 화면 밖으로 튀거나 완전히 틀어졌습니다.
- 분석: DirectX의 행렬 기준(행 우선/열 우선)과 Source 엔진 내부의 수학 라이브러리 포맷이 다르거나, 엔진이 자체적인 카메라 트랜스폼 연산을 거친 후 최종 렌더링에만 API를 사용하는 것으로 의심되었습니다.
3차 시도: D3DXVec3Project 활용
- 접근: DirectX에서 기본 제공하는 3D → 2D 투영 수학 함수인 D3DXVec3Project를 사용해 좌표 변환을 시도했습니다.
- 결과: 일부 플레이어에게만 간헐적으로 ESP 박스가 씌워지고 깜빡거리는 등 매우 불안정했습니다.
결론 및 우회: DirectX API 계층에서 행렬을 가로채려는 여러 꼼수(우회법)를 시도했으나, 독자적인 렌더링 파이프라인을 구축한 상용 게임 엔진 앞에서는 모두 무용지물이었습니다.
결국 API 계층의 껍데기에 의존하는 것을 포기하고, "엔진이 자체적으로 계산하여 들고 있는 ViewMatrix 원본 메모리를 직접 찾아내자"는 가장 원초적이고 확실한 방법(Cheat Engine 탐색)으로 선회하게 되었습니다.
2-5. WorldToScreen 알고리즘
확보한 ViewMatrix를 이용하여 3D 좌표를 2D 화면 픽셀 좌표로 변환하는 C++ 기반의 수식은 다음과 같습니다. 원근 나누기(Perspective Divide)를 통해 거리에 따른 비율을 계산하는 것이 핵심입니다.

3. 오프셋 최종 정리
지금까지 분석을 통해 획득한 ESP 및 에임봇 개발용 핵심 오프셋 데이터입니다.

마치며
이번 1편에서는 게임 메모리를 분석하여 플레이어의 데이터를 가져오기 위한 핵심 오프셋을 추출하고, 이를 2D 화면에 투영하기 위한 수학적 기반인 ViewMatrix와 WorldToScreen 알고리즘에 대해 심도 있게 정리해 보았습니다.
데이터의 위치와 변환 공식을 모두 확보했으니, 이제 남은 것은 이 정보를 바탕으로 게임 화면 위에 직접 그림을 그리는 일입니다. 이어지는 2편에서는 작성한 C++ 코드를 게임 프로세스 내부로 침투시켜, 화면 렌더링의 흐름을 가로채는 DirectX 9 EndScene 후킹(Hooking)을 통해 실제 화면에 ESP 박스를 출력하는 렌더링 파이프라인 구현을 다루도록 하겠습니다.
'Reverse Engineering > GAMEHACK' 카테고리의 다른 글
| [CS:S] ESP 월핵 / 에임봇 개발기 - 2편: ESP 구현, D3D 후킹 (0) | 2026.06.11 |
|---|---|
| [AntiCheat] 취약 드라이버를 이용한 커널 레벨 안티치트 우회 -1- (3) | 2026.04.08 |
| [게임핵의 역사] 안티치트를 우회해온 기술들의 역사 -왜 게임핵은 사라지지 않는가- (0) | 2026.04.03 |
| [AI 게임핵의 원리] YOLOv8로 CS:S 에임봇 구현하기 (0) | 2026.03.30 |
| [게임핵의 원리] 월핵은 어떻게 만들어지는가 -DirectX 후킹 원리 분석- 최종 (0) | 2026.03.27 |