Callbacks Initialization & Identifying in Windows Kernel and Remove Callbacks with Windbg

Arrays and Structures Related to Callbacks

  • PspCreateProcessNotifyRoutine
  • PspCreateThreadNotifyRoutine
  • PspLoadImageNotifyRoutine
C++
EX_CALLBACK PspCreateProcessNotifyRoutine[PSP_MAX_CREATE_PROCESS_NOTIFY];
EX_CALLBACK PspCreateThreadNotifyRoutine[PSP_MAX_CREATE_THREAD_NOTIFY];
C++
typedef struct _EX_CALLBACK {
    EX_FAST_REF RoutineBlock;
} EX_CALLBACK, *PEX_CALLBACK;
C++
typedef struct _EX_FAST_REF {
    union {
        PVOID Object;
#if defined (_WIN64)
        ULONG_PTR RefCnt : 4;
#else
        ULONG_PTR RefCnt : 3;
#endif
        ULONG_PTR Value;
    };
} EX_FAST_REF, *PEX_FAST_REF;

EX_FAST_REF Structure

Preview of windbg Command

Diving Into Kernel Source Code

Callback Registration for Processes

Function call order:

C++
typedef struct _EX_CALLBACK_ROUTINE_BLOCK {
  EX_RUNDOWN_REF        RundownProtect;
  PEX_CALLBACK_FUNCTION Function;
  PVOID                 Context;
} EX_CALLBACK_ROUTINE_BLOCK, *PEX_CALLBACK_ROUTINE_BLOCK;

Callback Initialization

PsSetCreateProcessNotifyRoutine():

C++
//
// The functions for this article have been shortened for better understanding and less clutter.
// Please refer to the original source.
//
NTSTATUS PsSetCreateProcessNotifyRoutine(
    IN PCREATE_PROCESS_NOTIFY_ROUTINE NotifyRoutine,
    IN BOOLEAN Remove
) {
    PEX_CALLBACK_ROUTINE_BLOCK CallBack;
    PAGED_CODE();

    if (Remove)
    {...
    }
    else
    {
        /* Allocate a callback */
        CallBack = ExAllocateCallBack((PVOID)NotifyRoutine, NULL);
        if (!CallBack) return STATUS_INSUFFICIENT_RESOURCES;

        /* Loop all callbacks */
        for (ULONG i = 0; i < PSP_MAX_CREATE_PROCESS_NOTIFY; i++) {
            /* Add this routine if it's an empty slot */
            if (ExCompareExchangeCallBack(&PspCreateProcessNotifyRoutine[i], CallBack, NULL)) {
                /* Found and inserted into an empty slot, return */
                InterlockedIncrement((PLONG)&PspCreateProcessNotifyRoutineCount);
                return STATUS_SUCCESS;
            }
        }
        ExFreeCallBack(CallBack);
        return STATUS_INVALID_PARAMETER;
    }
}

ExAllocateCallBack():

C++
PEX_CALLBACK_ROUTINE_BLOCK ExAllocateCallBack(
    IN PEX_CALLBACK_FUNCTION Function,
    IN PVOID Context
) {
    PEX_CALLBACK_ROUTINE_BLOCK CallbackBlock;
    
    /* Allocate a callback */
    CallbackBlock = ExAllocatePoolWithTag(PagedPool, sizeof(EX_CALLBACK_ROUTINE_BLOCK), TAG_CALLBACK_ROUTINE_BLOCK);
    if (CallbackBlock) {
        /* Initialize it */
        CallbackBlock->Function = Function;
        CallbackBlock->Context = Context;
        ExInitializeRundownProtection(&CallbackBlock->RundownProtect);
    }
    return CallbackBlock;
}

ExCompareExchangeCallBack():

C++
//
// ALL CODE HAS CHANGED AND YOU SHOULD NOT RELY ON THIS CODE FOR DETAILS. 
// THIS CODE IS SHOW ONLY FOR BETTER UNDERSTANDING ABOUT INITIALIZATION VALUE IN EX_FAST_REF.
//
BOOLEAN ExCompareExchangeCallBack(IN OUT PEX_CALLBACK CallBack, 
                                  IN PEX_CALLBACK_ROUTINE_BLOCK NewBlock, 
                                  IN PEX_CALLBACK_ROUTINE_BLOCK OldBlock)
{
    EX_FAST_REF OldValue;
    PEX_CALLBACK_ROUTINE_BLOCK CallbackBlock;
    ULONG Count;

    /* Check that we have a new block */
    if (NewBlock)
    {...
    }
    /* Do the swap */
    OldValue = ExCompareSwapFastReference(&CallBack->RoutineBlock, NewBlock, OldBlock);

    /* Get the routine block */
    CallbackBlock = ExGetObjectFastReference(OldValue);
    Count = ExGetCountFastReference(OldValue);

    /* Make sure the swap worked */
    if (CallbackBlock == OldBlock)
    {...
    }
    else
    {...
    }

ExCompareSwapFastReference():

C++
FORCEINLINE EX_FAST_REF ExCompareSwapFastReference(IN PEX_FAST_REF FastRef, 
						                                       IN PVOID Object, 
						                                       IN PVOID OldObject) 
{
    EX_FAST_REF OldValue, NewValue;

    /* Sanity check and start swap loop */
    ASSERT(!(((ULONG_PTR)Object) & MAX_FAST_REFS));
    for (;;) {
        /* Get the current value */
        OldValue = *FastRef;

        /* Make sure there's enough references to swap */
        if (!((OldValue.Value ^ (ULONG_PTR)OldObject) <= MAX_FAST_REFS)) break;

        /* Check if we have an object to swap */
        if (Object) {
            /* Set up the value with maximum fast references */   // <---------------------------------------------
            NewValue.Value = (ULONG_PTR)Object | MAX_FAST_REFS; // #define MAX_FAST_REFS 0xF
        
        else
        {
            /* Write the object address itself (which is empty) */
            NewValue.Value = (ULONG_PTR)Object;
        }

        /* Do the actual compare exchange */
        NewValue.Object = ExpChangePushlock(&FastRef->Object, NewValue.Object, OldValue.Object);
        if (NewValue.Object != OldValue.Object) continue;
        break;
    }
    return OldValue;
}
C
#include <ntifs.h>

typedef struct _EX_FAST_REF {
	union {
		PVOID Object;
#if defined (_WIN64)
		ULONG_PTR RefCnt : 4;
#else
		ULONG_PTR RefCnt : 3;
#endif
		ULONG_PTR Value;
	};
} EX_FAST_REF, * PEX_FAST_REF;

typedef struct _EX_CALLBACK_ROUTINE_BLOCK {
	EX_RUNDOWN_REF        RundownProtect;
	PEX_CALLBACK_FUNCTION Function;
	PVOID                 Context;
} EX_CALLBACK_ROUTINE_BLOCK, * PEX_CALLBACK_ROUTINE_BLOCK;

typedef struct _EX_CALLBACK
{
	EX_FAST_REF RoutineBlock;
} EX_CALLBACK, * PEX_CALLBACK;

NTSTATUS IrpCompleteRequest(PIRP Irp, ULONG_PTR info, NTSTATUS status) {
	Irp->IoStatus.Status = status;
	Irp->IoStatus.Information = info;
	IoCompleteRequest(Irp, IO_NO_INCREMENT);
	return status;
}

void StructWindbgUnload(PDRIVER_OBJECT DriverObject) {
	IoDeleteDevice(DriverObject->DeviceObject);
	UNICODE_STRING symbolName = RTL_CONSTANT_STRING(L"\\??\\StructWindbg");
	IoDeleteSymbolicLink(&symbolName);
	DbgPrint("[-] StructWindbg Driver Unload Successfuly\n");
	return;
}

NTSTATUS StructWindbgCreateClose(PIRP Irp) {
	return IrpCompleteRequest(Irp, 0, STATUS_SUCCESS);
}

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {
	UNREFERENCED_PARAMETER(RegistryPath);
	DbgPrint("[+] StructWindbg Driver Load Successfuly\n");
	NTSTATUS status = STATUS_SUCCESS;
	DriverObject->DriverUnload = StructWindbgUnload;
	DriverObject->MajorFunction[IRP_MJ_CREATE] = DriverObject->MajorFunction[IRP_MJ_CLOSE] = StructWindbgCreateClose;

	UNICODE_STRING devName = RTL_CONSTANT_STRING(L"\\Device\\StructWindbg");
	UNICODE_STRING symName = RTL_CONSTANT_STRING(L"\\??\\StructWindbg");
	PDEVICE_OBJECT DeviceObject = NULL;
	do {
		status = IoCreateDevice(DriverObject, 0, &devName, FILE_DEVICE_UNKNOWN, 0, FALSE, &DeviceObject);
		if (!NT_SUCCESS(status))
			break;
		DbgPrint("[+] StructWindbg Driver IoCreateDevice Successfuly\n");

		status = IoCreateSymbolicLink(&symName, &devName);
		if (!NT_SUCCESS(status)) {
			IoDeleteDevice(&DeviceObject);
			break;
		}
		DbgPrint("[+] StructWindbg Driver IoCreateSymbolicLink Successfuly\n");
	} while (FALSE);

	EX_CALLBACK _callbackEX = { 0 };
	EX_CALLBACK_ROUTINE_BLOCK _callbackEX1 = { 0 };
	EX_FAST_REF _callbackEX2 = { 0 };

	return status;
}

Let’s go back to Windbg:

Remove Callbacks

References:

reactos/reactos: A free Windows-compatible Operating System

mic101/windows: windows泄露源码

Yarden ShafirWinDbg — the Fun Way: Part 2​

Thanks to:

Twitter Yarden Shafir (@yarden_shafir) on X​

TwitterAlex Ionescu (@aionescu) on X​

.

.