Skip to content

Instantly share code, notes, and snippets.

@Little-Ki
Last active March 13, 2024 12:53
Show Gist options
  • Save Little-Ki/bf9bf104e54d8153c65194e088b2a716 to your computer and use it in GitHub Desktop.
Save Little-Ki/bf9bf104e54d8153c65194e088b2a716 to your computer and use it in GitHub Desktop.
[Code] [Kernel] ObRegisterCallbacks
// The ObRegisterCallbacks routine registers a list of callback routines for thread, process, and desktop handle operations.
// This function is a most public method used by anti cheat / anti virus software.
// Offical document:
// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-obregistercallbacks
// Function syntax:
// NTSTATUS ObRegisterCallbacks(
// POB_CALLBACK_REGISTRATION CallbackRegistration,
// PVOID *RegistrationHandle
// );
// _OB_CALLBACK_REGISTRATION struct:
// typedef struct _OB_CALLBACK_REGISTRATION {
// USHORT Version;
// Callback version, in kernel use OB_FLT_REGISTRATION_VERSION
// USHORT OperationRegistrationCount;
// UNICODE_STRING Altitude;
// Load order, any value, can not be NULL.
// PVOID RegistrationContext;
// When callback be called, it will pass to function, it's defined by ownself.
// OB_OPERATION_REGISTRATION *OperationRegistration;
// } OB_CALLBACK_REGISTRATION, *POB_CALLBACK_REGISTRATION;
// OB_OPERATION_REGISTRATION struct:
// typedef struct _OB_OPERATION_REGISTRATION {
// POBJECT_TYPE *ObjectType;
// Callback type, use one of PsProcessType / PsThreadType / ExDesktopObjectType.
// ExDesktopObjectType supported in win10, not earlier.
// OB_OPERATION Operations;
// Use one or more sign:
// OB_OPERATION_HANDLE_CREATE: A new process / thread / desktop handle has been opened or will be opened
// OB_OPERATION_HANDLE_DUPLICATE: A new process handle / thread handle / desktop handle has been duplicated or will be duplicated
// POB_PRE_OPERATION_CALLBACK PreOperation;
// Points to ObjectPreCallback, will be call before requested operation occurs.
// POB_POST_OPERATION_CALLBACK PostOperation;
// Points to ObjectPostCallback, will be call after requested operation occurs.
// } OB_OPERATION_REGISTRATION, *POB_OPERATION_REGISTRATION;
// PEPROCESS IoThreadToProcess ( _In_ PETHREAD Thread );
// Return the process whitch own the thread.
// HANDLE PsGetProcessId ( _In_ PEPROCESS Process );
// Return the process's id.
// The kernel driver who use this function must have certification
// But there has some way to bypass it.
// First way:
// By analyzed, windows use MmVerifyCallbackFunction to verify callback.
// This function just check if <DriverObject->DriverSection->Flags & 0x20> is true.
// nt!MmVerifyCallbackFunction+0x75:
// fffff800`01a66865 f6406820 test byte ptr [rax+68h],20h
// fffff800`01a66869 0f45fd cmovne edi,ebp
// Just set it to 0x20 will bypass it.
// DriverSection is a pointer to LDR_DATA_TABLE_ENTRY struct.
#ifndef _WIN64
#pragma pack(1)
#endif
typedef struct _LDR_DATA_TABLE_ENTRY
{
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
ULONG Flags;
USHORT LoadCount;
USHORT TlsIndex;
union
{
LIST_ENTRY HashLinks;
struct
{
PVOID SectionPointer;
ULONG CheckSum;
};
};
union
{
ULONG TimeDateStamp;
PVOID LoadedImports;
};
PVOID EntryPointActivationContext;
PVOID PatchInformation;
LIST_ENTRY ForwarderLinks;
LIST_ENTRY ServiceTagLinks;
LIST_ENTRY StaticLinks;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
#ifndef _WIN64
#pragma pack()
#endif
// Second way:
// In order to use this function, the IMAGE_OPTIONAL_HEADER.DllCharacterisitics must have IMAGE_DLLCHARACTERISITICS_FORCE_INTEGRITY attribute.
// In visual studio:
// 1. Right-click the project and select properties
// 2. Select the linker in the configuration properties and click the command line.
// 3. Enter in other options: /INTEGRITYCHECK means setting; /INTEGRITYCHECK:NO means not setting.
// Then, you should sign your driver.
BOOLEAN BypassCheckSign( PDRIVER_OBJECT pDriverObject )
{
#ifdef _WIN64
typedef struct _KLDR_DATA_TABLE_ENTRY
{
LIST_ENTRY listEntry;
ULONG64 __Undefined1;
ULONG64 __Undefined2;
ULONG64 __Undefined3;
ULONG64 NonPagedDebugInfo;
ULONG64 DllBase;
ULONG64 EntryPoint;
ULONG SizeOfImage;
UNICODE_STRING path;
UNICODE_STRING name;
ULONG Flags;
USHORT LoadCount;
USHORT __Undefined5;
ULONG64 __Undefined6;
ULONG CheckSum;
ULONG __padding1;
ULONG TimeDateStamp;
ULONG __padding2;
} KLDR_DATA_TABLE_ENTRY, *PKLDR_DATA_TABLE_ENTRY;
#else
typedef struct _KLDR_DATA_TABLE_ENTRY
{
LIST_ENTRY listEntry;
ULONG unknown1;
ULONG unknown2;
ULONG unknown3;
ULONG unknown4;
ULONG unknown5;
ULONG unknown6;
ULONG unknown7;
UNICODE_STRING path;
UNICODE_STRING name;
ULONG Flags;
} KLDR_DATA_TABLE_ENTRY, *PKLDR_DATA_TABLE_ENTRY;
#endif
PKLDR_DATA_TABLE_ENTRY pLdrData = (PKLDR_DATA_TABLE_ENTRY) pDriverObject->DriverSection;
pLdrData->Flags = pLdrData->Flags | 0x20;
return(TRUE);
}
// This is an example to show how to regist callbacks.
NTSTATUS SetProcessCallbacks()
{
NTSTATUS status = STATUS_SUCCESS;
OB_CALLBACK_REGISTRATION obCallbackReg = { 0 };
OB_OPERATION_REGISTRATION obOperationReg = { 0 };
RtlZeroMemory( &obCallbackReg, sizeof(OB_CALLBACK_REGISTRATION) );
RtlZeroMemory( &obOperationReg, sizeof(OB_OPERATION_REGISTRATION) );
/* set OB_CALLBACK_REGISTRATION */
obCallbackReg.Version = ObGetFilterVersion();
obCallbackReg.OperationRegistrationCount = 1;
obCallbackReg.RegistrationContext = NULL;
RtlInitUnicodeString( &obCallbackReg.Altitude, L"321000" );
obCallbackReg.OperationRegistration = &obOperationReg;
/*
* set OB_OPERATION_REGISTRATION
* difference between Thread and Process
*/
obOperationReg.ObjectType = PsProcessType;
obOperationReg.Operations = OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE;
/* difference between Thread and Process */
obOperationReg.PreOperation = (POB_PRE_OPERATION_CALLBACK) (&ProcessPreCall);
/* 注册回调函数 */
status = ObRegisterCallbacks( &obCallbackReg, &g_obProcessHandle );
if ( !NT_SUCCESS( status ) )
{
DbgPrint( "ObRegisterCallbacks Error[0x%X]\n", status );
return(status);
}
return(status);
}
NTSTATUS SetThreadCallbacks()
{
NTSTATUS status = STATUS_SUCCESS;
OB_CALLBACK_REGISTRATION obCallbackReg = { 0 };
OB_OPERATION_REGISTRATION obOperationReg = { 0 };
RtlZeroMemory( &obCallbackReg, sizeof(OB_CALLBACK_REGISTRATION) );
RtlZeroMemory( &obOperationReg, sizeof(OB_OPERATION_REGISTRATION) );
/* set OB_CALLBACK_REGISTRATION */
obCallbackReg.Version = ObGetFilterVersion();
obCallbackReg.OperationRegistrationCount = 1;
obCallbackReg.RegistrationContext = NULL;
RtlInitUnicodeString( &obCallbackReg.Altitude, L"321001" );
obCallbackReg.OperationRegistration = &obOperationReg;
/*
* set OB_OPERATION_REGISTRATION
* difference between Thread and Process
*/
obOperationReg.ObjectType = PsThreadType;
obOperationReg.Operations = OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE;
/* difference between Thread and Process */
obOperationReg.PreOperation = (POB_PRE_OPERATION_CALLBACK) (&ThreadPreCall);
/* regist call back */
status = ObRegisterCallbacks( &obCallbackReg, &g_obThreadHandle );
if ( !NT_SUCCESS( status ) )
{
DbgPrint( "ObRegisterCallbacks Error[0x%X]\n", status );
return(status);
}
return(status);
}
OB_PREOP_CALLBACK_STATUS ThreadPreCall( PVOID RegistrationContext, POB_PRE_OPERATION_INFORMATION pObPreOperationInfo )
{
PEPROCESS pEProcess = NULL;
/* check object type */
if ( *PsThreadType != pObPreOperationInfo->ObjectType )
{
return(OB_PREOP_SUCCESS);
}
/* get thread owner PEPROCESS */
pEProcess = IoThreadToProcess( (PETHREAD) pObPreOperationInfo->Object );
/* check if PID should be protected */
if ( IsProtectProcess( pEProcess ) )
{
/* operation type: create handle */
if ( OB_OPERATION_HANDLE_CREATE == pObPreOperationInfo->Operation )
{
if ( 1 == (1 & pObPreOperationInfo->Parameters->CreateHandleInformation.OriginalDesiredAccess) )
{
pObPreOperationInfo->Parameters->CreateHandleInformation.DesiredAccess = 0;
}
}
/* operation type: duplicate handle */
else if ( OB_OPERATION_HANDLE_DUPLICATE == pObPreOperationInfo->Operation )
{
if ( 1 == (1 & pObPreOperationInfo->Parameters->DuplicateHandleInformation.OriginalDesiredAccess) )
{
pObPreOperationInfo->Parameters->DuplicateHandleInformation.DesiredAccess = 0;
}
}
}
return(OB_PREOP_SUCCESS);
}
OB_PREOP_CALLBACK_STATUS ProcessPreCall( PVOID RegistrationContext, POB_PRE_OPERATION_INFORMATION pObPreOperationInfo )
{
PEPROCESS pEProcess = NULL;
/* check object type */
if ( *PsProcessType != pObPreOperationInfo->ObjectType )
{
return(OB_PREOP_SUCCESS);
}
/* get process object */
pEProcess = (PEPROCESS) pObPreOperationInfo->Object;
/* check if PID should be protected */
if ( IsProtectProcess( pEProcess ) )
{
/* operation type: create handle */
if ( OB_OPERATION_HANDLE_CREATE == pObPreOperationInfo->Operation )
{
if ( 1 == (1 & pObPreOperationInfo->Parameters->CreateHandleInformation.OriginalDesiredAccess) )
{
pObPreOperationInfo->Parameters->CreateHandleInformation.DesiredAccess = 0;
}
}
/* operation type: duplicate handle */
else if ( OB_OPERATION_HANDLE_DUPLICATE == pObPreOperationInfo->Operation )
{
if ( 1 == (1 & pObPreOperationInfo->Parameters->DuplicateHandleInformation.OriginalDesiredAccess) )
{
pObPreOperationInfo->Parameters->DuplicateHandleInformation.DesiredAccess = 0;
}
}
}
return(OB_PREOP_SUCCESS);
}
// 1. Modify the _OBJECT_TYPE_INITIALIZER.RetainAccess members of PsProcessType and PsThreadType to 0x1fffff
// 2. Modify ETHREAD.PreviousMode to kernelMode.
// 3. Enum EPROCESS->HandleTable then modify GransAccess to 0x1fffff.
// 4. Inject a proxy dll to csrss.exe / lsass.exe or other system process then use existed handle.
// 5. Enum callback list and modify it.
// About the 5th way:
// The operating system maintains a linked list, the link has stored all callback info, like their pre call / post call function pointer.
// Modify this linked list can bypass it.
// The header of the linked list is in OBJECT_TYPE struct, like:
// typedef struct OBJECT_TYPE {
// ...
// PLIST_ENTRY64 <header>;
// ...
// }
// In windbg:
/*
kd> dt nt!_OBJECT_TYPE
+0x000 TypeList : _LIST_ENTRY [ 0xffffcb82`dee6cf20 - 0xffffcb82`dee6cf20 ]
+0x010 Name : _UNICODE_STRING "Process"
+0x020 DefaultObject : (null)
+0x028 Index : 0x7 ''
+0x02c TotalNumberOfObjects : 0x26
+0x030 TotalNumberOfHandles : 0xe8
+0x034 HighWaterNumberOfObjects : 0x26
+0x038 HighWaterNumberOfHandles : 0xea
+0x040 TypeInfo : _OBJECT_TYPE_INITIALIZER
+0x0b8 TypeLock : _EX_PUSH_LOCK
+0x0c0 Key : 0x636f7250
+0x0c8 CallbackList : _LIST_ENTRY [ 0xffffa002`d31bacd0 - 0xffffa002`d35d2450 ]
*/
// CallbackList is CALLBACK_ENTRY_ITEM struct.
// typedef struct _CALLBACK_ENTRY_ITEM {
// LIST_ENTRY EntryItemList;
// OB_OPERATION Operations;
// CALLBACK_ENTRY* CallbackEntry;
// POBJECT_TYPE ObjectType;
// POB_PRE_OPERATION_CALLBACK PreOperation;
// POB_POST_OPERATION_CALLBACK PostOperation;
// __int64 unk;
// }CALLBACK_ENTRY_ITEM, *PCALLBACK_ENTRY_ITEM;
// The symbol PsProcessType and PsThreadType are exported, use
// PsProcessType + offset / PsThreadType + offset
// can get the first entry of linked list
// Note: this pointer points to inside of the entry, not head.
// This member's offset is not fixed, it's possibly different in different version of system.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment