ObRegisterCallbacks Bypass(2)

[0x00] Overview

이전 챕터에서 ObRegisterCallbacks 에 대해 깊게 알아봤습니다. 이번에는 소개한대로 ObRegisterCallbacks Bypass 에 대해 알아보겠습니다. 소스코드는 ControlDebugger 라는 이름으로 만들어집니다. ObRegisterCallbacks 를 포함한 커널 디버깅 방지 우회 기능까지 들어가 있습니다.

[0x01] ObRegisterCallbacks Bypass

자 이제 코드를 작성해보겠습니다. 이 드라이버는 앞으로 커널 디버깅 방지, 프로세스 디버깅 방지를 우회하기 위한 드라이버입니다.

하지만 우선 간단한 코드로 ObRegisterCallbacks 를 우회하는 방법을 알아보겠습니다.

[-] callbacks.h

이전 챕터에서 문서화되지 않은 구조체들에 대한 정의와 ObUnRegisterCallbacks의 파라미터로 사용 될 RegistrationHandle이 선언되어 있습니다.

#pragma once
#include <ntifs.h>

typedef struct _CALLBACK_ENTRY 
{
	INT16							Version;
	unsigned char					unknown[6];
	POB_OPERATION_REGISTRATION		RegistrationContext;
	UNICODE_STRING					Altitude;
} CALLBACK_ENTRY, *PCALLBACK_ENTRY;

typedef struct _CALLBACK_ENTRY_ITEM 
{
	LIST_ENTRY						EntryItemList;
	OB_OPERATION					Operations1;
	OB_OPERATION					Operations2;
	PCALLBACK_ENTRY					CallbackEntry;
	POBJECT_TYPE					ObjectType;
	POB_PRE_OPERATION_CALLBACK		PreOperation;
	POB_POST_OPERATION_CALLBACK		PostOperation;
} CALLBACK_ENTRY_ITEM, *PCALLBACK_ENTRY_ITEM;

typedef struct _OBJECT_TYPE
{
	LIST_ENTRY                 TypeList;
	UNICODE_STRING             Name;
	PVOID                      DefaultObject;
	ULONG                      Index;
	ULONG                      TotalNumberOfObjects;
	ULONG                      TotalNumberOfHandles;
	ULONG                      HighWaterNumberOfObjects;
	ULONG                      HighWaterNumberOfHandles;
	unsigned char			   TypeInfo[0x78];
	EX_PUSH_LOCK               TypeLock;
	ULONG                      Key;
	LIST_ENTRY                 CallbackList;
} OBJECT_TYPE, * POBJECT_TYPE;

PVOID RegistrationHandle = NULL;

[-] common.h

드라이버 진입점과 드라이버 언로드에 대한 함수 선언 헤더입니다.

#pragma once
#include "callbacks.h"

//============================================//
//======= DriverEntry & Unload Routine =======//
//============================================//

NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriver, IN PUNICODE_STRING pRegPath);
VOID UnloadDriver(IN PDRIVER_OBJECT pDriver);

[-] main.c

#include "common.h"

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pRegPath)
{
	UNREFERENCED_PARAMETER(pRegPath);
	pDriver->DriverUnload = UnloadDriver;
	POBJECT_TYPE *obType = PsProcessType;
	PCALLBACK_ENTRY_ITEM CallbackEntry = NULL;
	CallbackEntry = (*obType)->CallbackList.Flink;
	RegistrationHandle = CallbackEntry->CallbackEntry;
	ObUnRegisterCallbacks(RegistrationHandle);

	return STATUS_SUCCESS;
}

VOID UnloadDriver(PDRIVER_OBJECT pDriver)
{
	UNREFERENCED_PARAMETER(pDriver);

}

이전 챕터에서 학습한대로 OBJECT_TYPECallbackList를 이용하여 ObUnRegisterCallbacks를 호출하고 있습니다. 너무 간단하기도 하지만 문제가 없지는 않습니다.

[0x02] Constraint

  1. 내가 원하는 콜백 루틴만을 해제하는게 불가능하다. 물론 Altitude 값을 알고 있다면 가능할 것으로 보입니다.
  2. 드라이버 언로드 루틴에서 잘못된 핸들을 전달할 수 있다.

1번의 경우 제 짧은 지식으로는 Altitude 값으로 특정 드라이버의 루틴을 해제하는게 가능할 것으로 보입니다.

2번의 경우를 말해보자면, 현재 AntiKernelDebugging.sys 의 드라이버 언로드 루틴을 확인해보면 이해할 수 있습니다.

VOID UnloadDriver(IN PDRIVER_OBJECT pDriver)
{
	UNREFERENCED_PARAMETER(pDriver);
	
	PsRemoveLoadImageNotifyRoutine(&LoadImageNotifyRoutine);
	if (hRegistration)	// 콜백 등록에 실패할 경우 예외 처리
	{
		ObUnRegisterCallbacks(hRegistration);
	}
	
	DbgPrintEx(DPFLTR_ACPI_ID, 3, "[INFO] Driver unload success\n");
}

현재 hRegistration에 값이 있는 경우, ObUnRegisterCallbacks 함수를 호출합니다. 그렇기 때문에 현재 우회 드라이버는 아무 문제없이 동작합니다.

그러나 혹여 예외처리가 되어있지 않다면, 잘못된 핸들 전달로 인한 블루 스크린을 맞이하게 됩니다.

[0x03] Solution

main.c 부분만 아래와 같이 바꿔 진행해봅니다.

#include "common.h"

VOID Dummy()
{
	
}

NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pRegPath)
{
	UNREFERENCED_PARAMETER(pRegPath);
	pDriver->DriverUnload = UnloadDriver;
	POBJECT_TYPE *obType = PsProcessType;
	PCALLBACK_ENTRY_ITEM CallbackEntry = NULL;
	CallbackEntry = (*obType)->CallbackList.Flink;
	CallbackEntry->PreOperation = &Dummy;
	/*RegistrationHandle = CallbackEntry->CallbackEntry;
	ObUnRegisterCallbacks(RegistrationHandle);*/

	return STATUS_SUCCESS;
}

VOID UnloadDriver(PDRIVER_OBJECT pDriver)
{
	UNREFERENCED_PARAMETER(pDriver);
}

Dummy 라는 이름으로 더미 함수를 하나 만들었습니다. 그리고 CallbackEntryPreOperationDummy 함수로 바꿨습니다.

끝입니다. 이제 ObRegisterCallbacks 로 등록된 콜백 루틴에서 벗어났습니다.

[0x04] Proof Of Concept

영상을 확인해보면, 프로세스 디버깅 방지를 회피하여 디버깅이 가능한 것을 확인할 수 있습니다. 하지만 여전히 디버그 로그에서는 커널 디버깅을 탐지하고 있습니다.

[0x05] Conclusion

이제 커널 디버깅 방지를 우회하는 일만 남았습니다. 어서 또 다른 프로젝트도 작성해야 하는데 많은 내용을 넣으려 하니 계속 길어지고 있습니다.

어쨋든 이제 마지막 관문만이 남았습니다.