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

[Theori] 게임핵의 원리(2) - ESP

yenas0 2025. 5. 11. 06:43
반응형

https://theori.io/ko/blog/game-hacking-2

 

게임핵의 원리 (2) - ESP - Theori 블로그

ESP 핵은 게임 내 적의 위치, 상태 정보를 화면에 표시하는 기술입니다. 이 글에서는 ESP의 기본 원리부터 메모리 읽기 방식(External vs. Internal), World to Screen 변환, DirectX 후킹, Overlay 기법, Bone ESP 구

theori.io


ESP 핵이란?

게임 내 적의 위치, 상태 정보를 화면에 표시하는 기술 월핵이랑 차이점은 위치만 보이는 건 아니고 상태나 정보까지 화면에 표시하는 형식

기본 원리

ESP 원리 → 메모리를 읽어서 필요한 정보를 얻고 화면에 표시하는 것, 대상 프로세스 메모리 읽기 必

어떤 데이터를 얻어야 하는가?

→ 게임 플레이에 도움되는 정보 (물체 위치, 회전 뱡향, 닉네임, 생사 상태, 팀, 체력, 궁 etc)

→ 메모리 내부 구조체 or 클래스 형태로 저장

→ 메모리 내 포인터 트레이싱 해야함

 

메모리를 어떻게 읽는가?

  • externel, 외부 접근 방식
    • windows에서 제공하는 API를 이용해 구현 가능
    • 구현 방법
      • 유저 모드 API 사용
        • 게임 프로세스의 Process ID 구하기 → OpenProcess()로 프로세스 열어서 Process Handle 획득 → 얻은 Process Handle 이용해 ReadProcessMemory()로 게임 프로세스의 메모리 읽음
      • 커널 모드 API 사용
    • 특징
      • 메모리 읽을 때마다 API 호출 → 속도 느림
      • 탐지 가능성 大
    • Game 구조체의 주소로부터 각 플레이어의 위치 구하는 코드 예시(externel)
struct Game game;
uint64_t game_addr = ;

// Mem.Read(uint64_t address, size_t length, void* output);
Mem.Read(game_addr, sizeof(struct Game), &game);

for(uint64_t i; i < 4; i++){
 struct Player player;
  uint64_t player_addr = (uint64_t)game.players[i];
 Mem.Read(player_addr, sizeof(struct Player), &player);
 printf(
  "player %d: pos(%f, %f, %f)\n",
  i, player.x, player.y, player.z
 );
}

 

 

  • internel, 내부 접근 방식
    • DLL Injection or Code Injection → 게임 프로세스 내부에서 임의의 코드 실행 해서 메모리에 접근하는 방법 사용
    • 특징
      • 핵 프로세스==게임 프로세스로 취급 → 게임 메모리 접근 용이
      • 메모리 읽을 시 포인터로 접근 → 개발 용이
      • 메모리 접근 속도 빠름
    • Game 구조체의 주소로부터 각 플레이어의 위치를 구하는 코드(internel)
uint64_t game_addr = ;
struct Game *game = (Game*)game_addr;

for(uint64_t i; i < 4; i++){
 printf(
  "player %d: pos(%f, %f, %f)\n",
  i,
  game->player[i]->x, game->player[i]->y, game->player[i]->z
 );
}

 

어떻게 물체의 화면 좌표를 구해야 하는가?

위에서 예시 코드에서 얻은 좌표 == 월드 좌표(3D) ≠ 화면 좌표(2D)

3D → 2D: World to Screen

좌측에 눈 = 카메라 (물체를 보기 위한 것)

→ 물체는 카메라를 기준으로 보이게 됨 (카메라 시점에서..)

→ 실제 위치가 어떻게 보일지를 의미하는게 아님

→ 그래서 변환해야된다. 화면좌표로

 

변환 방법

  1. Direct 3D 함수를 이용한 좌표 변환
  • D3D 함수를 호출해 필요한 값을 얻을 수 있음 → 개발 편리
  • D3D 함수를 사용하기 위해 DLL 인젝션 必 → 보통 internel로 구현

D3D함수는 D3DXVec3Project() 이용 (3D→2D 변환하는 핵심 함수)

D3DXVECTOR3* D3DXVec3Project(
  _Inout_       D3DXVECTOR3  *pOut, ##화면좌표로변화되어저장
  _In_    const D3DXVECTOR3  *pV, ##월드좌표가
  _In_    const D3DVIEWPORT9 *pViewport,
  _In_    const D3DXMATRIX   *pProjection,
  _In_    const D3DXMATRIX   *pView,
  _In_    const D3DXMATRIX   *pWorld ##하위4개는 GetViewport(), GetTransform()함수로 얻음 -> pDevice필요함
  ##EndScene()이나 DIP Hook 등으로 pDevice 얻음 → Wall Hack에서 설명
);
typedef struct D3DVIEWPORT9 { ##단순연산->디바이스 인스턴스 필요없음
  DWORD X;
  DWORD Y;
  DWORD Width;
  DWORD Height;
  float MinZ;
  float MaxZ;
} D3DVIEWPORT9, *LPD3DVIEWPORT9;

HRESULT GetViewport(
  D3DVIEWPORT9 *pViewport ##게임화면의 크기, 위치 등의 값
);
HRESULT GetTransform( ##행렬구함
  D3DTRANSFORMSTATETYPE State, ##행렬 타입 지정
  D3DMATRIX             *pMatrix
);

필요한 인자 다 구한 후 함수 호출해 월드 좌표를 화면 좌표로 변환

void WorldToScreen(
 D3DXVECTOR3 *world_pos, // input
 D3DXVECTOR3 *screen_pos // output
){
 D3DVIEWPORT9 vp;
 D3DXMATRIX proj_matrix, view_matrix, world_matrix;
 pDevice->GetViewport(&vp);
 pDevice->GetTransform(D3DTS_VIEW, &view_matrix);
 pDevice->GetTransform(D3DTS_PROJECTION, &proj_matrix);
 pDevice->GetTransform(D3DTS_WORLD, &world_matrix);
 D3DXVec3Project(screen_pos, world_pos, &vp, 
     &proj_matrix, &view_matrix, &world_matrix);
}

 

 

  1. 행렬을 이용한 좌표 변환
  • 좌표 수동 계산 必 → 좌표 변환에 필요한 값 직접 찾아야 함
  • 위에 값 얻을 수 있으면 externel 구현 가능

2.1. View Matrix를 이용한 좌표 변환

  • View Matrix 값 찾아야함 (변환행렬, 월드좌표*View Matrix = 카메라 좌표계)
  • Matrix 변환에 필요한 값 이미 있음 → 계산 간단
  • 카메라 위치값 안구해도 됨

그렇다면 View Matrix 어떻게 구하는가? → D3D의 경우, D3DXMatrixLookAtLH() 후킹 or 메모리 스캔

구하고 나면 곱해서 카메라 좌표를 알 수 있는데 이때 xyz뿐만이 아니라 w도 뜬다,

w는 동차 좌표계로 좌표의 차원을 1차원 증가해서 표현하는 방식

x, y, z -> x, y, z, 1 // 일반 좌표계 -> 동차 좌표계
x, y, z, w -> x/w, y/w, z/w // 동차 좌표계 -> 일반 좌표계

편해서 씀 → 자세한건 컴퓨터 그래픽스 관련

이렇게 구한건 카메라 좌표계 → 다시 일반 좌표계(xyz)로 변환한 뒤에 원근법 적용해야됨

→ 삼각함수 계산(D3DXMatrixPerspectiveFovLH() 함수로 가능, 근데 시야각이랑 화면 가로세로 길이 필요, 이때 FOV가 리버싱해서 어디에 저장도는지 알아야한다.)

2.2. 카메라의 방향을 이용한 좌표 변환

  • 카메라의 방향(Angle)과 위치(Location)만으로 계산 가능
  • 계산 과정 비교적 복잡
  • 컴퓨터 그래픽스 기초 지식 要

물체를 어떻게 사용자에게 보여주는가?

화면상으로 변환한 좌표를 적적한 형태로.. 구현하려면?

→ GDI, DirectX, OpenGL etc..

 

DirectX

  • EndScene함수 후킹
    • 렌더링 함수임
    • 장정: D3D기능 활용해 편리하게 그릴수 있음
    • 단점: 게임 내 화면에 덮어 그려 게임 화면이 게임사에 모니터링 될 시 탐지 가능
  • Overlay
    • D3D 창 생성해 게임과 겹쳐보이도록 하는 기법
    • D3D이용하지만 투명 창 생성하는걸로 앞에랑 차이
    • 별도 디바이스로 그려지니 모니터링 회피 가능
hWnd = CreateWindowEx(WS_EX_LAYERED | WS_EX_TRANSPARENT, wc.lpszClassName, "", WS_POPUP, rc.left, rc.top, s_width, s_height, NULL, NULL, wc.hInstance, NULL);
SetLayeredWindowAttributes(hWnd, RGB(0, 0, 0), 0, ULW_COLORKEY);
SetLayeredWindowAttributes(hWnd, 0, 255, LWA_ALPHA);

이런식으로 생성하는데 속성값 바꿔서 투명하게 만들 수 있음

창 생성 이후에 D3D로 그리면 되는데 이때 BackBufferFormat 를 투명도를 지원하는 포맷으로 설정

Bone ESP

3D모델 뼈 형태 보여주는 거

보통 ESP가 점이나 박스형태라 3D 환경에서 충동 구현이 어려우니 뼈모양으로 보이게 하는 것

보통 메모리 상에서 플레이서 구조체 근처에 뼈의 정보가 있을 확률 大

→ 뼈는 캐릭터 고정되어있어도 액션따라서 달라질 수 있어서 부위 식별해야됨

→ 뼈마다 고유 숫자 붙는데 이 숫자 뼈 위치에 그리는게 Bone Index Logger

→ 그거 가지고 뼈끼리 이어버리면 Bone ESP가 됨

반응형