게임해킹/게임해킹 ,c++

기본적인 게임해킹 방법과 최근 게임해킹 방법 (안티치트 우회, 드라이버 해킹)

cp_good: 2022. 11. 15. 01:59
반응형


게임 해킹이 어떻게 이루어지는지 과정입니다.
내용 없이 설명만 하지 않고 직접 해볼 수 있게 작성할 예정입니다.
게임 해킹 처음 접할 때 보시면 좋습니다.

 


게임 해킹 순서

 

  • 메모리 스캔으로 원하는 값 찾기 (+덤퍼)
  • 베이스 주소(imagebase) 가져오기
  • 메모리 읽기, 쓰기
  • 원하는 작업
  • 안티 치트로 보호돼있으면? (최근 게임 해킹 방법)

 


 

1. 메모리 스캔

 

메모리 스캔은 치트 엔진 같은 툴로 원하는 값을 찾는 겁니다. (체력, 돈, 위치, 뷰 메트릭스 등)
원하는 값은 다 메모리에 있으니까요.
어떻게 값을 찾는지는 치트 엔진 카테고리에 예시들이 있습니다.

언리얼 엔진 게임이라면 언리언 덤퍼를 사용하면 오프셋을 찾기 정말 쉬워집니다.
덤퍼를 사용하면 게임에서 사용하는 변수(체력, 위치, 아이템, 오브젝트 등)
함수(현재 위치 가져오기, 스킨 바꾸기 등) 오프셋을 자동으로 찾아줍니다.

언리언 엔진은 체력, 스피드 등이 일정한 패턴으로 메모리에 올라오니
저희는 그냥 가져온 오프셋 사용하면 됩니다.

오프셋 설명은 아래쪽에 있습니다.


 

2. 베이스 주소 가져오기

 

먼저 모든 게임 해킹의 기본은 메모리 읽기, 쓰기입니다.
메모리를 읽어 적이 어디 있는지 계산하고, 메모리 쓰기를 통해 체력, 돈, 위치 등을 조작할 수 있겠죠.

위 작업을 하기 위해 먼저 베이스 주소를 얻어와야 합니다.
프로그램은 메모리에 올라갈 때 특정 기준 주소부터 올라가는데
특정 기준 주소가 베이스 주소, 그 기준점에서 값(체력, 위치)이 얼마만큼 떨어져 있냐를 오프셋(offset)이라 합니다.


베이스 주소는 프로그램을 재시작할 때마다 바뀝니다.
그러니 오프셋을 알고 있으면 베이스 주소가 바뀌어도 원하는 값을 가져올 수 있겠죠.

치트 엔진으로 아무 프로세스나 잡고 메모리 뷰를 확인해보면
박스 친 곳 "notepad.exe + 23F3E"중 "notepad.exe"가 베이스 주소 "23F3E"는 오프셋이라 합니다.
(베이스 주소가 문자열로 돼있지만 원래 16진수입니다. 치트 엔진 기능으로 문자열로 보이는 겁니다.)

 

베이스 주소와 오프셋

 
베이스는 어떻게 얻어오냐

 

베이스 주소는 프로그램을 재시작할 때마다 바뀌니
편하게 알아서 얻어오게 프로그램을 만들어야겠죠. 일반적으로
1. CreateToolhelp32Snapshot 함수를 사용하거나
2. PEB 구조체에서 얻어 올 수 있습니다.
 
프로세스아이디 가져오기도 필요할겁니다.

오프셋은?

 

아시겠지만 치트 엔진에서 포인터 스캔했을 때 나오는 게 오프셋입니다.
"ac_client.exe"+0017E0A8 여기서 베이스 주소 기준으로 찾은 오프셋(0017E0A8)은
캐릭터 베이스가 할당되는 정적 주소 포인터입니다.
정적 주소는 재시작해도 캐릭터 베이스 주소가 고정으로 할당되고

캐릭터 베이스 주소에서 0xEC 만큼 떨어진 곳이, 체력이 할당되는 곳입니다.
그러니 오프셋을 알고 있으면 프로그램 재시작을 해도 값을 찾을 수 있겠죠.
(캐릭터 베이스 주소와 베이스 주소는 다른 겁니다.)

 

어썰트 큐브 게임의 체력 오프셋

 


 

3. 메모리 읽기, 쓰기

 

먼저 핸들을 알아야 하는데 예제를 보면 핸들이 많이 등장합니다.(창 핸들, 프로세스 핸들, 스냅샷 핸들)
핸들은 프로세스를 구분을 하기 위해 운영체제가 만든 정수 값입니다.

프로세스를 구분해야 원하는 프로세스에 작업을 할 수 있겠죠.
프로세스에 접근할 수 있는 권한이기도 하고요.
저희는 프로세스 핸들을 얻기 위해 OpenProcess 함수를 사용합니다.

HANDLE OpenProcess(
  [in] DWORD dwDesiredAccess,
  [in] BOOL  bInheritHandle,
  [in] DWORD dwProcessId
);
  • _In_ DWORD dwDesiredAccess
  • 프로세스에 대한 액세스 권한
  • _In_ BOOL bInheritHandle
  • 핸들을 상속 여부
  • _In_ DWORD dwProcessId
  • 프로세스의 식별자 (프로세스 아이디입니다.)
  •  


ReadProcessMemory

BOOL ReadProcessMemory(
  [in]  HANDLE  hProcess,
  [in]  LPCVOID lpBaseAddress,
  [out] LPVOID  lpBuffer,
  [in]  SIZE_T  nSize,
  [out] SIZE_T  *lpNumberOfBytesRead
);
  • _In_ HANDLE hProcess
  • 프로세스 핸들
  • _In_ LPCVOID lpBaseAddress
  • 읽기 할 주소
  • _Out_ LPVOID lpBuffer
  • 읽어온 값을 저장할 변수 (함수 인자로 Buffer가 있으면 저장 or 참조할 변수가 필요하다는 뜻입니다.)
  • _In_ SIZE_T nSize
  • 읽기 할 크기 (게임에 체력, 좌표, 주소 등 할당한 변수의 크기는 다릅니다. 각 크기에 맞게 읽어와야 원하는 값을 읽어 올 수 있으니 메모리 크기는 중요합니다.)
  • _Out_ SIZE_T *lpNumberOfBytesRead
  • 읽어온 크기를 저장할 변수

 

메모리 읽기 예제 코드

#include <iostream>
#include <Windows.h>
#include <tlhelp32.h>

uintptr_t GetBase(int, LPCSTR);

int main()
{
	HWND hwnd = FindWindow(0, "AssaultCube");
	DWORD pid;
	GetWindowThreadProcessId(hwnd, &pid);
	uintptr_t base;
	base = GetBase(pid, "ac_client.exe");
	// 핸들을 저장합니다.
	HANDLE handle = OpenProcess(PROCESS_ALL_ACCESS, 0, pid);

	int hp;
	uintptr_t hp_base;
	// 오프셋을 이용해 체력값을 읽어오기
	ReadProcessMemory(handle, (LPCVOID)(base + 0x17E0A8), &hp_base, sizeof(hp_base), 0);
	ReadProcessMemory(handle, (LPCVOID)(hp_base + 0xEC), &hp, sizeof(hp), 0);
	printf("체력 : %d", hp);

	return 0;
}

위 스크린샷에서 봤듯이 오프셋을 이용해
"base + 0x17E0A8"로 캐릭터 베이스에 접근 후 "hp_base + 0xEC"로 체력 값을 읽어 오는 예제 코드입니다.

 

읽기 성공


반대로 쓰기는 이렇게 하면 됩니다. WriteProcessMemory

BOOL WriteProcessMemory(
  [in]  HANDLE  hProcess,
  [in]  LPVOID  lpBaseAddress,
  [in]  LPCVOID lpBuffer,
  [in]  SIZE_T  nSize,
  [out] SIZE_T  *lpNumberOfBytesWritten
);
  • _In_ HANDLE hProcess
  • 프로세스 핸들
  • _In_ LPCVOID lpBaseAddress
  • 읽기 할 주소
  • _In_ LPVOID lpBuffer
  • 읽어온 값을 저장할 변수 (함수 인자로 Buffer가 있으면 저장 or 참조할 변수가 필요하다는 뜻입니다.)
  • _In_ SIZE_T nSize
  • 쓰기 할 크기 (게임에 체력, 좌표, 주소 등 할당한 변수의 크기는 다릅니다. 각 크기에 맞게 쓰기 해야 오류가 없으니 메모리 크기는 중요합니다.)
  • _Out_ SIZE_T *lpNumberOfBytesWritten
  • 쓰기 한 크기를 저장할 변수

 

전체 소스코드

#include <iostream>
#include <Windows.h>
#include <tlhelp32.h>

uintptr_t GetBase(int, LPCSTR);

int main()
{
	HWND hwnd = FindWindow(0, "AssaultCube");
	DWORD pid;
	GetWindowThreadProcessId(hwnd, &pid);
	uintptr_t base;
	base = GetBase(pid, "ac_client.exe");
	HANDLE handle = OpenProcess(PROCESS_ALL_ACCESS, 0, pid);

	uintptr_t hp_base;
	// 오프셋을 따라 메모리를 읽어옵니다.
	ReadProcessMemory(handle, (LPCVOID)(base + 0x17E0A8), &hp_base, sizeof(hp_base), 0);
	int hp = 200; // 쓰기할 값을 초기화 시켜줍니다.
	// 메모리 쓰기를 합니다.
	WriteProcessMemory(handle, (LPVOID)(hp_base + 0xEC), &hp, sizeof(hp), 0);
	printf("체력 : %d", hp);

	return 0;
}

uintptr_t GetBase(int pid, LPCSTR procname) {
	uintptr_t modBaseAddr;
	HANDLE hPorcessShap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid);
	if (hPorcessShap == INVALID_HANDLE_VALUE) {
		printf("CreateToolhelp32Snapshot 실패\n");
		return 0;
	}
	MODULEENTRY32 pe32;
	pe32.dwSize = sizeof(MODULEENTRY32);
	if (Module32First(hPorcessShap, &pe32)) {
		do {
			if (strcmp(pe32.szModule, procname) == 0) {
				modBaseAddr = (uintptr_t)pe32.modBaseAddr;
				break;
			}
		} while (Module32Next(hPorcessShap, &pe32));
	}
	CloseHandle(hPorcessShap);
	return modBaseAddr;
}

 

쓰기 성공


 

4. 원하는 작업

 

원하는 작업은 ESP핵 만들기, 에임 핵 정도가 있습니다.

 

esp핵 만드는 방법 (과정, 필요한것) [게임해킹]

eps핵 만드는데 필요한 것, 과정 기록해놓은 겁니다. 보면 필요한 것들 링크 나옵니다. 필요한 것 월드 투 스크린 함수 (WorldToScreen Functions) 뷰 행렬 (View Matrix) 좌표 (적 좌표) 화면 크기 (해상도) 아

cpgood.tistory.com

에임 핵

 

[게임해킹] 에임봇 만드는 방법 원리 (시야각 쓰기 방법)

자동으로 적을 조준하는 핵으로 에임핵, 오토에임으로 부르기도 합니다. 적 위치 메모리를 읽는 방식과 이미지 서치방식이 있던데 이미지 서치는 나중에 포스팅하겠습니다. 조준방법 위치 메모

cpgood.tistory.com



여기까지가 일반적인 게임을 해킹하는 방법이고 정리해보겠습니다.
1. 치트 엔진으로 메모리 스캔하여 값을 얻어옵니다. (언리얼 엔진 게임이면 언리언 덤퍼를 사용)

 
2. 프로세스 베이스 주소를 얻어옵니다.

 
3. 프로세스 아이디를 가져와 OpenProcess() 함수로 메모리를 읽기, 쓰기 권한 핸들을 가져옵니다.
4. 원하는 메모리까지 오프셋을 참조해 메모리를 읽어옵니다.
5. 메모리 쓰기를 하던 오브젝트 위치를 읽어 ESP를 만들던 원하는 작업을 해줍니다.
6. 끝


 

5. 안티 치트로 보호돼있으면

 

결론 먼저 말하면 커널 안티치트는 커널에서 작업을 해야 합니다.

이지 안티 치트, 배틀 아이, 뱅가드 등 커널 안티 치트를 알려면 드라이버를 알아야 합니다.
 

드라이버란?

 

장치(Device)를 구동하는 프로그램, 장치란 주변기기들을 의미
드라이버는 기본적으로 하드웨어를 사용할 수 있게 해주는 프로그램으로 볼 수 있습니다.

응용프로그램은 하드웨어들을 사용할 수 있도록 드라이버를 요구하게 되고
(프로그램에서 바로 하드웨어 장치를 제어하는 것은 불가능)
드라이버는 하드웨어에 접근해 응용프로그램에게 서비스하는 게 목적입니다.

그리고 일반적인 프로그램에서는 할 수 없는 일을 할 때도 드라이버는 필요합니다. 예시(핸들 생성 모니터링)
이 글에서는 "일반적인 프로그램에서는 할 수 없는 일"이 핵심이겠죠.
 

드라이버의 특징

 

  • 드라이버는 인터페이스를 표시할 수 없고(오버레이 같은 것)
  • 사용자 모드와 드라이버가 통신으로 데이터 전달을 해야 합니다.
  • 드라이버는 인증서가 없으면 드라이버를 실행할 수 없습니다.
  • 드라이버가 충돌하면 PC도 함께 충동합니다.(블루스크린)
  • 드라이버는 커널 모드에서 실행되어 커널 API에 대한 액세스를 제공합니다.
  • 드라이버는 시스템에 대한 전체 액세스 권한을 가지고 있으므로 거의 모든 것을 할 수 있습니다.

 

Patch Guard와 DSE

 

드라이버는 전체 액세스 권한이 있으니 윈도우는 드라이버 형태의 바이러스에 대한 조치를 위해

커널 구조(SSDT, IDT 등)가 수정이 되면 BOSD 되는 Patch Guard기능과

인증서가 없으면 드라이버를 실행할 수 없게 DSE기능을 도입했습니다.
그러니 저희는 직접 만든 드라이버를 로드할 수 없고(인증서가 없으니) DSE 우회를 해야 합니다.

DSE우회 1, DSE우회 2 경우 치트 엔진 드라이버를 로드할 때 사용합니다.
driver manual mapper 경우 내가 만든 드라이버를 로드할 때 사용합니다.
 

안티 치트 우회 방법

 

게임을 보호해야 하는 안티 치트들은 드라이버를 사용해 핸들 생성 모니터링처럼 게임을 보호를 할 겁니다.
그러면 저희도 드라이버로 만들어야겠죠, 먼저 정리해보겠습니다.
 
1. 프로세스 핸들을 가져올 수 없으니 드라이버가 베이스 주소를 찾아 사용자 프로그램과 통신을 하여 전달해줍니다.

 
2. 메모리 읽기, 쓰기 역시 안되니 드라이버를 이용해 메모리를 가져옵니다. (마찬가지로 통신으로 전달)
3. 원하는 메모리까지 오프셋을 참조해서 원하는 작업을 합니다.
4. ESP 경우 드라이버와 통신하며 오버레이는 사용자 모드에서 띄우고 그리기를 수행합니다.
5. 드라이버 게임해킹 전체 소스코드도 올리겠습니다.
6. 끝
 
드라이버에서 메모리 읽기, 쓰기는 나중에 포스팅하겠습니다.


마무리

게임 해킹은 뭐든 메모리 읽기부터 시작합니다.
안티 치트가 막으려고 드라이버를 사용하면 저희는 똑같이 드라이버를 사용하면 됩니다.
서명 안된 드라이버를 막았다면 우회하는 도구가 존재하고요.
게임엔진에 따라 덤퍼가 존재하고 저희는 그걸 사용하면 됩니다. (언리얼 덤퍼, 유니티 덤퍼)
암호화가 돼있어 시작부터 막힐 수도 있습니다만
남들이 다연 구하고 소스코드도 공개했습니다. (깃허브, 검색)

음.. 결국한 건 남이 만들어놓은 거 가져다 쓴 거밖에 없지만
계속하다 보면 언젠가 내가 연구해서 공개할 수도 있겠죠^^
게임 해킹이 최종 목적이 되면 안 됩니다.

긴 글은 처음이라 오타와 추가 설명이 필요하면 계속 수정하겠습니다.

메일 : qmffhrm@protonmail.com

반응형