게임해킹/커널 게임 해킹

안티 치트가 프로세스 핸들을 보호하는 방법 (치트엔진 검색 안 되는 이유) [게임해킹]

cp_good: 2022. 10. 8. 03:13
반응형

 

설명

커널 안티 치트로(뱅가드, 이지안티치트, 배틀아이)

보호된 게임을 치트 엔진으로 메모리 검색을 해보면, 검색이 안 되는 경우가 있는데

핸들 생성을 모니터링할 수 있게 해주는 ObRegisterCallbacks 사용해 핸들을 보호하기 때문입니다.

 

추가 설명

  • ObRegisterCallbacks : 프로세스, 스레드 핸들 조작을 위한 콜백 함수를 등록하는 함수
  • 요청된 작업 Pre(이전)과 Post(이후) 동작을 설정할 수 있습니다.
  • 콜백 함수 : 응용 프로그램이 제공하며, 특정 이벤트 발생 시 운영체제에게 호출당하는 함수

콜백 함수를 해석해보면

응용 프로그램이 제공 : 우리가 등록했기 때문이고

특정 이벤트 : 핸들 작업(open, duplicate)이고

호출당하는 함수 : 우리가 등록한 함수

즉, 게임 핸들을 보호하기 위해 콜백 함수를 등록하고, 핸들 작업(open, duplicate)이 일어날 때(Pre, Post에 따라) 콜백 함수가 호출되고 콜백 함수를 통해 핸들 권한을 조작하는 겁니다.


함수

NTSTATUS ObRegisterCallbacks(
  [in]  POB_CALLBACK_REGISTRATION CallbackRegistration,
  [out] PVOID                     *RegistrationHandle
);
  • CallbackRegistration : 콜백 루틴 및 기타 등록 정보의 목록을 지정하는 OB_CALLBACK_REGISTRATION 구조에 대한 포인터
  • RegistrationHandle : 등록된 콜백 루틴을 식별하는 값에 대한 포인터, ObUnRegisterCallbacks 루틴에 전달하여 콜백 루틴의 등록을 취소

 

typedef struct _OB_CALLBACK_REGISTRATION {
  USHORT                    Version;
  USHORT                    OperationRegistrationCount;
  UNICODE_STRING            Altitude;
  PVOID                     RegistrationContext;
  OB_OPERATION_REGISTRATION *OperationRegistration;
} OB_CALLBACK_REGISTRATION, *POB_CALLBACK_REGISTRATION;
  • Version : 요청된 Object Callback Registration의 버전, 드라이버는 OB_FLT_REGISTRATION_VERSION를 지정
  • OperationRegistrationCount : OperationRegistration 배열의 항목 수
  • Altitude : 드라이버의 고도(유니코드), MS에 등록되어 사용되며 로드 순서와도 관계가 있음
  • RegistrationContext : 콜백 루틴이 실행될 때 해당 값을 콜백 루틴으로 전달
  • OperationRegistration : OB_OPERATION_REGISTRATION의 포인터, ObjectPreCallback and ObjectPostCallback 루틴이 호출되는 작업 유형을 지정

 

typedef struct _OB_OPERATION_REGISTRATION {
  POBJECT_TYPE                *ObjectType;
  OB_OPERATION                Operations;
  POB_PRE_OPERATION_CALLBACK  PreOperation;
  POB_POST_OPERATION_CALLBACK PostOperation;
} OB_OPERATION_REGISTRATION, *POB_OPERATION_REGISTRATION;
  • ObjectType : 콜백 루틴을 동작시키는 오브젝트 유형에 대한 포인터, 다음 값 중 하나를 지정
    • PsProcessType : 프로세스 핸들 작업을 위한 유형
    • PsThreadType : 스레드 핸들 작업을 위한 유형
    • ExDesktopObjectType : 데스크톱 핸들 작업을 위한 유형
  • Operations : 다음 플래그 중 하나 이상을 지정
    • OB_OPERATION_HANDLE_CREATE : 새로운 핸들이 생성되거나 열었을 경우 동작
    • OB_OPERATION_HANDLE_DUPLICATE : 새로운 핸들을 복제하거나 복제된 경우 동작
  • PreOperation : OB_PRE_OPERATION_CALLBACK 포인터, 시스템은 요청된 작업이 발생하기 전에 해당 루틴을 호출
  • PostOperation : OB_POST_OPERATION_CALLBACK의 포인터, 시스템은 요청된 작업이 발생한 후에 해당 루틴을 호출

 

PreOperation만 사용할 거기 때문에 OB_PRE_OPERATION_CALLBACK만 가져왔습니다.

POB_PRE_OPERATION_CALLBACK PobPreOperationCallback;

OB_PREOP_CALLBACK_STATUS PobPreOperationCallback(
  [in] PVOID RegistrationContext,
  [in] POB_PRE_OPERATION_INFORMATION OperationInformation
)
{...}
  • RegistrationContext : ObRegisterCallbacks 루틴의 CallBackRegistration->RegistrationContext 매개 변수로 지정하는 콘텍스트
  • OperationInformation : 핸들 작업의 매개 변수를 지정하는 OB_PRE_OPERATION_INFORMATION 구조에 대한 포인터

 

typedef struct _OB_PRE_OPERATION_INFORMATION {
  OB_OPERATION                 Operation;
  union {
    ULONG Flags;
    struct {
      ULONG KernelHandle : 1;
      ULONG Reserved : 31;
    };
  };
  PVOID                        Object;
  POBJECT_TYPE                 ObjectType;
  PVOID                        CallContext;
  POB_PRE_OPERATION_PARAMETERS Parameters;
} OB_PRE_OPERATION_INFORMATION, *POB_PRE_OPERATION_INFORMATION;
  • Operation : Handle Operation의 유형, 다음 값 중 하나
    • OB_OPERATION_HANDLE_CREATE : 프로세스 또는 스레드 핸들 생성
    • OB_OPERATIOIN_HANDLE_DUPLICATE : 프로세스 또는 스레드의 핸들이 복제
  • Flags : 예약된 영역, KernelHandle 멤버를 사용
  • KernelHandle : 핸들이 커널 핸들인지를 지정하는 값. TRUE인 경우 커널 핸들
  • Reserved : 시스템 예약 영역
  • Object : 핸들 작업의 대상인 프로세스 또는 스레드 객체에 대한 포인터(EPROCESS, ETHREAD 등)
  • ObjectType : 프로세스의 경우 PsProcessType, 스레드의 경우 PsThreadType
  • CallContext : 드라이버의 특정 컨텍스트 정보에 대한 포인터
  • Parameters : OB_PRE_OPERATION_PARAMETERS에 대한 포인터

 

typedef union _OB_PRE_OPERATION_PARAMETERS {
  OB_PRE_CREATE_HANDLE_INFORMATION    CreateHandleInformation;
  OB_PRE_DUPLICATE_HANDLE_INFORMATION DuplicateHandleInformation;
} OB_PRE_OPERATION_PARAMETERS, *POB_PRE_OPERATION_PARAMETERS;

 

프로세스 핸들에 대한 구조체만 가져왔습니다.

typedef struct _OB_PRE_CREATE_HANDLE_INFORMATION {
  ACCESS_MASK DesiredAccess;
  ACCESS_MASK OriginalDesiredAccess;
} OB_PRE_CREATE_HANDLE_INFORMATION, *POB_PRE_CREATE_HANDLE_INFORMATION;
  • DesiredAccess : 핸들에 부여할 액세스 권한을 지정하는 ACCESS_MASK 값, 기본적으로 이 멤버는 OriginalDesiredAccess와 같지만 ObjectPreCallback 루틴에서 이 값을 수정하여 액세스를 제한할 수 있음
  • OriginalDesiredAccess : 핸들에 대해 요청된 원래 액세스를 지정하는 ACCESS_MASK 값

소스코드

블로그에선 지저분해 보이니 비주얼 스튜디오에 복사해서 보세요.

위 구초체랑 같이 보면 이해하기 쉬운 코드입니다.

실제로는 화이트리스트 프로그램은 접근 가능하게 필터 하겠네요.

반복해서 보면 이해됩니다.

#include <ntifs.h>

#define PROCESS_TERMINATE         0x0001  
#define PROCESS_VM_OPERATION      0x0008  
#define PROCESS_VM_READ           0x0010  
#define PROCESS_VM_WRITE          0x0020  

VOID DriverUnload(IN PDRIVER_OBJECT pDriverObj);
NTSTATUS ProtectProcess(BOOLEAN Enable);
OB_PREOP_CALLBACK_STATUS preCall(PVOID RegistrationContext, POB_PRE_OPERATION_INFORMATION pOperationInformation);
char* GetProcessImageNameByProcessID(ULONG ulProcessID);

PVOID obHandle; // 등록된 콜백 루틴을 식별하는 값을 받을 변수

UCHAR* PsGetProcessImageFileName( // 프로세스 이름 가져오는 함수
	__in PEPROCESS Process
);

NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObj, IN PUNICODE_STRING pRegistryString)
{
	pDriverObj->DriverUnload = DriverUnload;
	ProtectProcess(TRUE);

	return STATUS_SUCCESS;
}

// OB_CALLBACK_REGISTRATION, OPERATION_REGISTRATION 구조체 초기화
// ObRegisterCallbacks 를 이용해 콜백 루틴 등록
NTSTATUS ProtectProcess(BOOLEAN Enable)
{
	OB_CALLBACK_REGISTRATION obReg;
	memset(&obReg, 0, sizeof(obReg));
	obReg.Version = ObGetFilterVersion(); // OB_FLT_REGISTRATION_VERSION랑 값이 같음
	obReg.OperationRegistrationCount = 1; // OB_OPERATION_REGISTRATION count, obReg[2] 인 경우 2
	obReg.RegistrationContext = NULL;
	RtlInitUnicodeString(&obReg.Altitude, L"321000"); // 임의의 Altitude

	OB_OPERATION_REGISTRATION opReg;
	memset(&opReg, 0, sizeof(opReg)); 
	opReg.ObjectType = PsProcessType;
	
	// Create, Open, DUPLICATE시 동작
	opReg.Operations = OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE; 

	// PreOperation 등록
	opReg.PreOperation = (POB_PRE_OPERATION_CALLBACK)&preCall; 
	
	// OperationRegistration 등록
	obReg.OperationRegistration = &opReg; 
	
	// 콜백루틴등록, obHandle에 콜백 루틴을 식별하는 값 저장
	return ObRegisterCallbacks(&obReg, &obHandle); 
}

OB_PREOP_CALLBACK_STATUS preCall(PVOID RegistrationContext, POB_PRE_OPERATION_INFORMATION pOperationInformation)
{
	UNREFERENCED_PARAMETER(RegistrationContext);
	HANDLE pid = PsGetProcessId((PEPROCESS)pOperationInformation->Object);
	char szProcName[16] = { 0 };
	strcpy(szProcName, GetProcessImageNameByProcessID((ULONG)pid));

	if (!_stricmp(szProcName, "notepad.exe"))
	{
		// 핸들작업이 CREATE일 경우
		if (pOperationInformation->Operation == OB_OPERATION_HANDLE_CREATE)
		{
			// 종료일 경우
			if ((pOperationInformation->Parameters->CreateHandleInformation.OriginalDesiredAccess & PROCESS_TERMINATE) == PROCESS_TERMINATE)
			{
				// TerminateProcess 호출하는 등의 방법으로 프로세스를 종료할때
				// DesiredAccess조작으로 권한수정
				pOperationInformation->Parameters->CreateHandleInformation.DesiredAccess &= ~PROCESS_TERMINATE;
			}
			if ((pOperationInformation->Parameters->CreateHandleInformation.OriginalDesiredAccess & PROCESS_VM_OPERATION) == PROCESS_VM_OPERATION)
			{
				// WriteProcessMemory 및 VirtualProtectEx 함수를 호출하는 것과 같이 프로세스의 주소를 수정할때
				pOperationInformation->Parameters->CreateHandleInformation.DesiredAccess &= ~PROCESS_VM_OPERATION;
			}
			if ((pOperationInformation->Parameters->CreateHandleInformation.OriginalDesiredAccess & PROCESS_VM_READ) == PROCESS_VM_READ)
			{
				// ReadProcessMemory 함수를 호출하는 것과 같이 주소 공간을 읽을때
				pOperationInformation->Parameters->CreateHandleInformation.DesiredAccess &= ~PROCESS_VM_READ;
			}
			if ((pOperationInformation->Parameters->CreateHandleInformation.OriginalDesiredAccess & PROCESS_VM_WRITE) == PROCESS_VM_WRITE)
			{
				// WriteProcessMemory 함수를 호출하는 것과 같이 주소 공간을 쓸때
				pOperationInformation->Parameters->CreateHandleInformation.DesiredAccess &= ~PROCESS_VM_WRITE;
			}
		}
	}
	return OB_PREOP_SUCCESS;
}

char* GetProcessImageNameByProcessID(ULONG ulProcessID)
{
	NTSTATUS  Status;
	PEPROCESS  EProcess = NULL;
	Status = PsLookupProcessByProcessId((HANDLE)ulProcessID, &EProcess);

	if (!NT_SUCCESS(Status))
	{
		return FALSE;
	}
	ObDereferenceObject(EProcess);

	return (char*)PsGetProcessImageFileName(EProcess);
}

VOID DriverUnload(IN PDRIVER_OBJECT pDriverObj)
{
	UNREFERENCED_PARAMETER(pDriverObj);
	DbgPrint("driver unloading...\n");

	ObUnRegisterCallbacks(obHandle);
}

크게 보면

  1. DriverEntry부터 시작
  2. ProtectProcess() 함수 호출
  3. 콜백 루틴 및 기타 등록 정보 목록을 지정하는 OB_CALLBACK_REGISTRATION 구조체 초기화
  4. OB_OPERATION_REGISTRATION 구조체 초기화
  5. OB_OPERATION_REGISTRATION에 pre콜백 루틴 등록
  6. 콜백 루틴 목록에 등록

 

결과

메모리 읽기가 안 되는 모습, 쓰기, dll인젝션도 안됩니다.

 

보호된 메모장


메일 : qmffhrm@protonmail.com

반응형