반응형
기본적인 게임해킹 중 커널 안티치트를 피하기 위해 필요한 과정으로
커널에서 베이스주소를 가져오는 방법입니다.
안티치트 우회이기 때문에 data ptr 후킹이 사용됩니다.(참고)
소스코드
아래 소스코드는 베이스주소를 가져오는데 필요한 헤더와 구조체 정의를 한 Includes.h입니다.
#include <ntdef.h>
#include <ntifs.h>
#include <windef.h>
extern "C" NTKERNELAPI
PPEB
PsGetProcessPeb(
IN PEPROCESS Process
);
typedef struct _PEB_LDR_DATA {
ULONG Length;
BOOLEAN Initialized;
PVOID SsHandle;
LIST_ENTRY ModuleListLoadOrder;
LIST_ENTRY ModuleListMemoryOrder;
LIST_ENTRY ModuleListInitOrder;
} PEB_LDR_DATA, * PPEB_LDR_DATA;
typedef struct _LDR_DATA_TABLE_ENTRY {
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
USHORT LoadCount;
USHORT TlsIndex;
LIST_ENTRY HashLinks;
PVOID SectionPointer;
ULONG CheckSum;
ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY;
typedef struct _RTL_USER_PROCESS_PARAMETERS {
BYTE Reserved1[16];
PVOID Reserved2[10];
UNICODE_STRING ImagePathName;
UNICODE_STRING CommandLine;
} RTL_USER_PROCESS_PARAMETERS, * PRTL_USER_PROCESS_PARAMETERS;
typedef void(__stdcall* PPS_POST_PROCESS_INIT_ROUTINE)(void);
typedef struct _PEB {
BYTE Reserved1[2];
BYTE BeingDebugged;
BYTE Reserved2[1];
PVOID Reserved3[2];
PPEB_LDR_DATA Ldr;
PRTL_USER_PROCESS_PARAMETERS ProcessParameters;
PVOID Reserved4[3];
PVOID AtlThunkSListPtr;
PVOID Reserved5;
ULONG Reserved6;
PVOID Reserved7;
ULONG Reserved8;
ULONG AtlThunkSListPtr32;
PVOID Reserved9[45];
BYTE Reserved10[96];
PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
BYTE Reserved11[128];
PVOID Reserved12[1];
ULONG SessionId;
} PEB, * PPEB;
간단히 과정을 정리하면
1. PsLookupProcessByProcessId() 함수로 EPROCESS를 얻어옵니다.
- 인자로 프로세스 아이디가 필요하니, 사용자모드 프로그램과 통신해서 프로세스 아이디를 넘겨줍니다.
- 드라이버 통신은 카테고리 참고하면 됩니다.
2. 얻어온 EPROCESS을 인자로 PsGetProcessPeb() 함수로 PEB를 가져옵니다.
3. KeStackAttachProcess() 함수로 베이스주소를 가져올 프로세스에 연결합니다.
- 연결해주지 않으면 블루스크린이 뜹니다.
4. Includes.h에 정의된 PEB 구조체의 "PPEB_LDR_DATA Ldr" 멤버를 참조해 모듈정보를 가져옵니다.
- _PEB는 각 프로세스마다 생성된다.
- Ldr은 _PEB_LDR_DATA 구조체를 가지고, 이 구조체는 현재 프로세스에 로드한 모듈 정보를 가지고 있다.
- 프로세스에 로딩되는 DLL마다 _LDR_DATA_TABLE_ENTRY 구조체가 생성되고 연결방법이 세 가지이다. LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList; - _PEB_LDR_DATA 구조체 ModuleListLoadOrder를 _LDR_DATA_TABLE_ENTRY 구조체로 참조하면 DLL의 정보를 가지고 있다.
5. for문으로 LIST_ENTRY를 순회하면서 원하는 모듈을 찾습니다. 찾으면 CONTAINING_RECORD() 매크로로 _LDR_DATA_TABLE_ENTRY. 구조체를 가져옵니다.
- LIST_ENTRY 연결 리스트 구조를 이용해 for문으로 처음 연결리스트와 같으면 for문을 빠져나옵니다.
- CONTAINING_RECORD()는 상위구초제를 가져오는 매크로 함수입니다, LIST_ENTRY형태로 돼있으니 _LDR_DATA_TABLE_ENTRY를 가져와야 BaseDllName을 찾을 수 있습니다.
6. 유니코드 스트링을 비교해 원하는 모듈이면 베이스주소를 가져와 KeUnstackDetachProcess() 후 리턴합니다.
아래 소스코드는 베이스주소를 얻어오는 함수와 DriverEntry입니다.
#include "Includes.h"
// 베이스주소 얻어오는 함수
ULONG64 get_module_base_x64(PEPROCESS proc, UNICODE_STRING module_name)
{
// PEB 가져오기
PPEB pPeb = PsGetProcessPeb(proc);
if (!pPeb)
{
return NULL;
}
KAPC_STATE state;
// 프로세스 연결(중요)
KeStackAttachProcess(proc, &state);
// Ldr 참조
PPEB_LDR_DATA pLdr = (PPEB_LDR_DATA)pPeb->Ldr;
if (!pLdr)
{
KeUnstackDetachProcess(&state);
return NULL;
}
// 연결리스트 순환
for (PLIST_ENTRY list = (PLIST_ENTRY)pLdr->ModuleListLoadOrder.Flink; list != &pLdr->ModuleListLoadOrder; list = (PLIST_ENTRY)list->Flink)
{
// 상위 구조체 가져오는 매크로로 LDR_DATA_TABLE_ENTRY 구조체 가져오기
PLDR_DATA_TABLE_ENTRY pEntry = CONTAINING_RECORD(list, LDR_DATA_TABLE_ENTRY, InLoadOrderModuleList);
// 문자열 비교
if (RtlCompareUnicodeString(&pEntry->BaseDllName, &module_name, TRUE) == NULL)
{
// 베이스주소 리턴
ULONG64 baseAddr = (ULONG64)pEntry->DllBase;
KeUnstackDetachProcess(&state);
return baseAddr;
}
}
KeUnstackDetachProcess(&state);
return NULL;
}
// 드라이버 엔트리
NTSTATUS DriverEntry(PVOID lpBaseAddress, DWORD32 dwSize) {
UNREFERENCED_PARAMETER(lpBaseAddress);
UNREFERENCED_PARAMETER(dwSize);
// 베이스주소를 가져올 프로세스 이름
// 유니코드 스트링으로 바꾸는 과정
RtlInitAnsiString(&AS, "notepad.exe");
RtlAnsiStringToUnicodeString(&ModuleName, &AS, TRUE);
PEPROCESS process;
// 프로세스 아이디로 EPROCESS가져오기
PsLookupProcessByProcessId((HANDLE)pid, &process);
ULONG64 base_address64 = NULL;
// EPROCESS와 모듈이름 넘기기
base_address64 = get_module_base_x64(process, ModuleName);
return 0;
}
data ptr 후킹은 링크 참조하세요.
data ptr 후킹이 이해되지 않으면 기본적인 게임해킹 방법 참고
유저모드 소스코드
#include <Windows.h>
#include <iostream>
// 요청이 뭔지 체크용
// 임의로 지정하면됨
#define Base 0x15881588
// 예시) Write는 메모리쓰기 요청, Read는 메모리읽기 요청, Base는 베이스주소 요청
// 게임해킹에는 메모리읽기, 쓰기, 베이스주소등 한가지 요청만 하는게 아니기 때문에
// 후킹한 함수에서 0x15881588 같이 지정된 숫자로 무슨요청인지 구분후
// 요청에 맞게 처리해줍니다.
// #define Write 0x15991599
// #define Read 0x16001600
// 만약 내가 만든프로그램에서 요청한것이 아니면 원본함수로 넘겨버릴수도 있겠죠
PVOID(__fastcall* FunctionPTR)(INT64, INT64, PVOID, INT, UINT, INT) = NULL;
uintptr_t base_address;
int main()
{
// data ptr 후킹한 함수를 호출하는 함수포인터 입니다.
FunctionPTR(0, 0, &base_address, pid, Base, 0);
// base_address : 베이스주소를 받아올 변수
// Base : 내가 만든 프로그램에서 무엇을 요청했는지 체크용 숫자
return 0;
}반응형
'게임해킹 > 커널 게임 해킹' 카테고리의 다른 글
| 안티 치트가 프로세스 핸들을 보호하는 방법 (치트엔진 검색 안 되는 이유) [게임해킹] (0) | 2022.10.08 |
|---|---|
| [게임 해킹] 드라이버 통신방법 text ptr hook 설명 (안티치트 우회) (0) | 2022.07.02 |
| [게임 해킹] 드라이버 통신 방법 data ptr hook 설명 (안티치트 우회) (6) | 2022.06.28 |
| [게임 해킹] data ptr hook 후킹 할 함수 찾는 방법 (안티치트 우회) (0) | 2022.06.28 |