본문 바로가기
리버스 엔지니어링/AssaultCube 게임 해킹

[1편] 게임 핵의 종류와 기초 (AssaultCube)

by leedg 2022. 12. 4.
반응형

해당 글은 C++과 치트엔진에 대한 기본적인 지식이 없으면 이해하기 어려울 수 있습니다.

치트엔진 튜토리얼 포스팅 보러가기

추후 글에서 사용될 오픈소스 게임인 Assaultcube의 버전은 1.2.0.2  입니다.

AssaultCube v1.2.0.2 다운로드


게임 핵(HACK) & 게임 치트(CHEAT)

게임 내 해킹 프로그램을 이르는 말으로, 흔히 핵이라고 불립니다. 흔히 말하는 맵핵, 스피드핵, 에임봇, 매크로 등이 해킹 프로그램 범주에 포함됩니다. 핵 개발에 사용되는 언어로는 C++이 주로 사용되며 간단한 매크로인 경우는 AutoHotKey와 Lua등이 사용됩니다.

게임 해킹 프로그램에 대한 나무위키

 


 

내부(INTERNAL) & 외부(EXTERNAL)

 

기본적인 개념

핵에 필요한 게임의 여러가지 메모리값을 리버스 엔지니어링을 통해 얻고나서 핵을 직접 개발할 때, 프로세스의 메모리에 접근하는 방식에 따라 내부(Internal)와 외부(External)로 방식이 나뉩니다. 더 자세한 설명은 아래에서 진행하겠습니다.

 

내부 (Internal)

내부 핵은 게임 프로세스에 DLL(Dynamic Link Library)을 삽입하여 생성됩니다. 말 그대로 게임 프로세스에 삽입하는것이기 때문에, 게임 프로세스의 메모리에 직접(direct) 접근을 할 수 있어 정말 매우 빠르게 메모리에 Read/Write(읽기/쓰기)가 가능합니다. 따라서 C의 포인터라는 개념을 사용하여 오브젝트에 대한 포인터를 만들고 이를 캐스팅한 후 메모리의 오브젝트를 가리킵니다. 이렇게 하게되면 포인터를 통해서 오브젝트의 변수에 정말 빠르고 쉽게 접근할 수 있습니다. 체력(Health)를 가져오는 코드를 내부의 방식으로 표현하면 아래처럼 코드를 작성할 수 있습니다.

uintptr_t* Object = (uintptr_t*)(0x123456); // Object에 대한 포인터를 만들어 캐스팅함
int* Health = (int*)(*Object + 0x100); // *Object 로 오브젝트의 변수에 쉽게 접근 후 오프셋연결
*Health = 999; // Health 값 변경

std::cout << *Health << std::endl; // Health 값 출력 -> 999

 

외부 (External)

외부 핵은 Windows API(win32api)를 이용해서 게임 프로세스의 메모리와 상호작용을 합니다. 내부에서는 포인터와 캐스팅을 통해 프로세스에 Read/Write(읽기/쓰기)를 했다면, 외부에서는 Windows API의 WriteProcessMemory()함수와 ReadProcessMemory()함수를 통해 게임 프로세스의 메모리를 Read/Write(읽기/쓰기) 하는 방식입니다. 함수 사용을 위해서 윈도우즈 운영체제의 커널(Kernel)에 OpenProcess() 함수를 사용하여 필요한 프로세스의 접근권한을 요청해야합니다. 접근권한은 보통 Windows API에서 사용되는 HANDLE이라는 개념이 사용되는데, OpenProcess()를 사용하면 접근권한에 대한 핸들을 반환(Return) 해줍니다. 외부핵은 내부 핵과는 다르게 포인터로 직접 캐스팅하여 값을 빠르게 가져오는 것이 아닌 함수호출을 하여 함수가 수행하는 동안의 처리 시간이 있기 때문에 내부핵보다 속도가 느리다는 단점이 있습니다. 이때까지 말한 내용을 통해 임의의 체력(Health) 주소를 가져와서 Read/Write하는 코드로 간략하게 표현하면 아래처럼 코드를 작성할 수 있습니다.

DWORD procId = 123; // 게임의 프로세스ID
HANDLE handleProc = OpenProcess(PROCESS_ALL_ACCESS, NULL, procId); // 커널에서 프로세스 접근 권한 받기

uintptr_t moduleBaseAddress = 0x40000; // 게임 시작주소 (ModuleBase Address)
uintptr_t pLocalPlayer = moduleBaseAddress + 0x11FFF; // 플레이어의 포인터

ReadProcessMemory(handleProc, (LPCVOID)pLoaclPlayer, &pLocalPlayer, sizeof(uintptr_t), NULL);
uintptr_t HealthAddress = pLocalPlayer + 0x4; // 오프셋 연결 (0x4가 체력의 오프셋이라고 가정하였을 때)

/* 변경전 Health 값 출력 */
int Health;
ReadProcessMemory(handleProc, (LPCVOID)HealthAddress, &Health, sizeof(int), NULL); // 읽기

std::cout << "Health Value : " << std::dec << Health << std::endl; // 변경전 Health의 값 출력 -> 100

/* 변경후 Health 값 출력 */
int newHealth = 999; //Health 메모리의 값을 999로 변경하기 위한 변수 선언
WriteProcessMemory(handleProc, (BYTE*)HealthAddress, &newHealth, sizeof(int), NULL); // 쓰기
ReadProcessMemory(handleProc, (LPCVOID)HealthAddress, &Health, sizeof(int), NULL); // 읽기

std::cout << "Health Value : " << std::dec << Health << std::endl; // Health의 값 출력 -> 999

이처럼 내부와 외부의 차이는 코드만 보면 내부 핵은 코드가 간결하지만 외부 핵은 복잡하다는 차이가 있습니다.

 

 


 

정리

외부 핵은 처음 게임해킹을 처음 입문하는 사람에게 추천하는 방식입니다. ESP를 띄우기위해 내부에서는 프로세스의 그래픽 라이브러리를 후킹해야하는데, 외부의 방식으로 해킹을 하게 되면 후킹을 하지 않고 바로 Windows API와 DirectX 라이브러리를 활용해 게임 프로세스 윈도우와 같은 크기로 투명 창을 띄워 매우쉽게 ESP를 만들기 위한 기반을 마련할 수 있습니다. 또한 내부핵과는 다르게 커널과 통신하는 면에서 안티치트가 치트를 발각할 수 있는 시스템상의 물증들이 많기때문에 만약 온라인 게임에서 사용하게 되면 이 시스템에 쉽게 검출(발견)되어집니다. 마지막으로 느리다는 단점이 있습니다.

내부 핵은 프로세스에 직접 접근을 하기 때문에 굉장히 빠르고 포인터의 개념을 알고있는 사람이라면 외부핵보다 코드가 간결하여 프로그래밍하기가 쉽습니다. 단 보통의 핵에서는 ESP라는 적의 위치를 화면상에 그려 표시하는 행위를 위해 게임 내의 그래픽 라이브러리를 후킹해야하기 때문에 단순하게 프로세스의 메모리를 읽고쓰는게 아니라면, 초보자에게는 어려울 수 있습니다. 또 이러한 이유로 내부핵은 게임 내에서 정상적으로 동작하는 취급이기 때문에 전문가가 내부핵의 원리를 이용해 올바른 방법으로 만들게 되면, 안티치트 시스템에 거의 탐지되지 않습니다.

 

 

 

 

다음 포스팅 부터는 게임, Visual Studio 2022 설치를 같이 진행할 예정입니다.

댓글