Subscribing to Process Creation, Thread Creation and Image Load Notifications from a Kernel Driver
This is a quick lab to play with some of the interesting notifications that kernel drivers can subscribe to:

PsSetCreateProcessNotifyRoutine

PsSetCreateProcessNotifyRoutine takes two parameters:
1
NTSTATUS PsSetCreateProcessNotifyRoutine(
2
// pointer to a function to be called when a process is spawned or terminated
3
PCREATE_PROCESS_NOTIFY_ROUTINE NotifyRoutine,
4
// specifies whether to subscribe or unsubscribe from this event
5
BOOLEAN Remove
6
);
Copied!
Below is a snippet that shows how the routine sCreateProcessNotifyRoutine (line 2) gets registered for new/terminated process notifications on line 24:
1
// handle incoming notifications about new/terminated processes
2
void sCreateProcessNotifyRoutine(HANDLE ppid, HANDLE pid, BOOLEAN create)
3
{
4
if (create)
5
{
6
PEPROCESS process = NULL;
7
PUNICODE_STRING parentProcessName = NULL, processName = NULL;
8
9
PsLookupProcessByProcessId(ppid, &process);
10
SeLocateProcessImageName(process, &parentProcessName);
11
12
PsLookupProcessByProcessId(pid, &process);
13
SeLocateProcessImageName(process, &processName);
14
15
DbgPrint("%d %wZ\n\t\t%d %wZ", ppid, parentProcessName, pid, processName);
16
}
17
else
18
{
19
DbgPrint("Process %d lost child %d", ppid, pid);
20
}
21
}
22
23
// register sCreateProcessNotifyRoutine function to receive notifications about new/terminated processes
24
PsSetCreateProcessNotifyRoutine(sCreateProcessNotifyRoutine, FALSE);
Copied!
Below shows how the routine sCreateProcessNotifyRoutine gets executed when a new process hostname.exe (PID 2892) is spawned by powershell (PID 7176). Additionally, it shows that the process 7176 (hostname) terminated:

PsSetLoadImageNotifyRoutine

PsSetLoadImageNotifyRoutine only takes one parameter - a pointer to a function that will handle notifications about DLLs that processes running on the system loaded:
1
NTSTATUS PsSetLoadImageNotifyRoutine(
2
PLOAD_IMAGE_NOTIFY_ROUTINE NotifyRoutine
3
);
Copied!
Below indicates that the routine sLoadImageNotifyRoutine is going to handle our notifications as registered with PsSetLoadImageNotifyRoutine on line 14:
1
// handle incoming notifications about module loads
2
void sLoadImageNotifyRoutine(PUNICODE_STRING imageName, HANDLE pid, PIMAGE_INFO imageInfo)
3
{
4
UNREFERENCED_PARAMETER(imageInfo);
5
PEPROCESS process = NULL;
6
PUNICODE_STRING processName = NULL;
7
PsLookupProcessByProcessId(pid, &process);
8
SeLocateProcessImageName(process, &processName);
9
10
DbgPrint("%wZ (%d) loaded %wZ", processName, pid, imageName);
11
}
12
13
// register sLoadImageNotifyRoutinefunction to receive notifications new DLLs being loaded to processes
14
PsSetLoadImageNotifyRoutine(sLoadImageNotifyRoutine);
Copied!
Testing the driver - once we open a notepad.exe, our driver gets notified about all the modules that notepad.exe loaded:

PsSetCreateThreadNotifyRoutine

PsSetCreateThreadNotifyRoutine only takes one parameter - a pointer to a function that will handle notifications about new or killed threads across all the system processes:
1
NTSTATUS PsSetCreateThreadNotifyRoutine(
2
PCREATE_THREAD_NOTIFY_ROUTINE NotifyRoutine
3
);
Copied!
Below indicates that the routine sCreateThreadNotifyRoutine is going to handle our notifications as registered with PsSetCreateThreadNotifyRoutine on line 15:
1
// handle incoming notifications about new/terminated processes
2
void sCreateThreadNotifyRoutine(HANDLE pid, HANDLE tid, BOOLEAN create)
3
{
4
if (create)
5
{
6
DbgPrint("%d created thread %d", pid, tid);
7
}
8
else
9
{
10
DbgPrint("Thread %d of process %d exited", tid, pid);
11
}
12
}
13
14
// register sCreateThreadNotifyRoutine to receive notifications about thread creation / termination
15
PsSetCreateThreadNotifyRoutine(sCreateThreadNotifyRoutine);
Copied!
Testing the driver now, we can see we are indeed geting notified about new and terminated threads across processes on our system:

PsSetCreateProcessNotifyRoutineEx

PsSetCreateProcessNotifyRoutineEx takes two arguments:
1
NTSTATUS PsSetCreateProcessNotifyRoutineEx(
2
// pointer to a function to be called when a process is spawned
3
PCREATE_PROCESS_NOTIFY_ROUTINE_EX NotifyRoutine,
4
// specifies whether to subscribe or unsubscribe from this event
5
BOOLEAN Remove
6
);
Copied!
Below is a snippet that shows how the routine sCreateProcessNotifyRoutineEx (line 3) gets registered for new process notifications on line 19. Processes with commandline containing notepad in them will be killed by setting the createInfo.reationStatus member to STATUS_ACCESS_DENIED (line 13):
1
// handle incoming notifications about new/terminated processes and kill
2
// processes that have "notepad" in their commandline arguments
3
void sCreateProcessNotifyRoutineEx(PEPROCESS process, HANDLE pid, PPS_CREATE_NOTIFY_INFO createInfo)
4
{
5
UNREFERENCED_PARAMETER(process);
6
UNREFERENCED_PARAMETER(pid);
7
8
if (createInfo != NULL)
9
{
10
if (wcsstr(createInfo->CommandLine->Buffer, L"notepad") != NULL)
11
{
12
DbgPrint("[!] Access to launch notepad.exe was denied!");
13
createInfo->CreationStatus = STATUS_ACCESS_DENIED;
14
}
15
}
16
}
17
18
// subscribe sCreateProcessNotifyRoutineEx to new / terminated process notifications
19
PsSetCreateProcessNotifyRoutineEx(sCreateProcessNotifyRoutineEx, FALSE);
Copied!
If PsSetCreateProcessNotifyRoutineEx is not working in your driver, you will need to add a /integritycheck switch in your linker configuration
Below shows how an attempt to spawn notepad.exe is blocked by our driver:

Code

Belos is the full working driver code that registers all the callback routines mentioned above:
1
#include <Ntifs.h>
2
#include <ntddk.h>
3
#include <wdm.h>
4
5
DRIVER_DISPATCH HandleCustomIOCTL;
6
#define IOCTL_SPOTLESS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x2049, METHOD_BUFFERED, FILE_ANY_ACCESS)
7
UNICODE_STRING DEVICE_NAME = RTL_CONSTANT_STRING(L"\\Device\\SpotlessDevice");
8
UNICODE_STRING DEVICE_SYMBOLIC_NAME = RTL_CONSTANT_STRING(L"\\??\\SpotlessDeviceLink");
9
10
void sCreateProcessNotifyRoutine(HANDLE ppid, HANDLE pid, BOOLEAN create)
11
{
12
if (create)
13
{
14
PEPROCESS process = NULL;
15
PUNICODE_STRING parentProcessName = NULL, processName = NULL;
16
17
PsLookupProcessByProcessId(ppid, &process);
18
SeLocateProcessImageName(process, &parentProcessName);
19
20
PsLookupProcessByProcessId(pid, &process);
21
SeLocateProcessImageName(process, &processName);
22
23
DbgPrint("%d %wZ\n\t\t%d %wZ", ppid, parentProcessName, pid, processName);
24
}
25
else
26
{
27
DbgPrint("Process %d lost child %d", ppid, pid);
28
}
29
}
30
31
void sCreateProcessNotifyRoutineEx(PEPROCESS process, HANDLE pid, PPS_CREATE_NOTIFY_INFO createInfo)
32
{
33
UNREFERENCED_PARAMETER(process);
34
UNREFERENCED_PARAMETER(pid);
35
36
if (createInfo != NULL)
37
{
38
if (wcsstr(createInfo->CommandLine->Buffer, L"notepad") != NULL)
39
{
40
DbgPrint("[!] Access to launch notepad.exe was denied!");
41
createInfo->CreationStatus = STATUS_ACCESS_DENIED;
42
}
43
}
44
}
45
46
void sLoadImageNotifyRoutine(PUNICODE_STRING imageName, HANDLE pid, PIMAGE_INFO imageInfo)
47
{
48
UNREFERENCED_PARAMETER(imageInfo);
49
PEPROCESS process = NULL;
50
PUNICODE_STRING processName = NULL;
51
PsLookupProcessByProcessId(pid, &process);
52
SeLocateProcessImageName(process, &processName);
53
54
DbgPrint("%wZ (%d) loaded %wZ", processName, pid, imageName);
55
}
56
57
void sCreateThreadNotifyRoutine(HANDLE pid, HANDLE tid, BOOLEAN create)
58
{
59
if (create)
60
{
61
DbgPrint("%d created thread %d", pid, tid);
62
}
63
else
64
{
65
DbgPrint("Thread %d of process %d exited", tid, pid);
66
}
67
}
68
69
void DriverUnload(PDRIVER_OBJECT dob)
70
{
71
DbgPrint("Driver unloaded, deleting symbolic links and devices");
72
IoDeleteDevice(dob->DeviceObject);
73
IoDeleteSymbolicLink(&DEVICE_SYMBOLIC_NAME);
74
PsSetCreateProcessNotifyRoutine(sCreateProcessNotifyRoutine, TRUE);
75
PsRemoveLoadImageNotifyRoutine(sLoadImageNotifyRoutine);
76
PsRemoveCreateThreadNotifyRoutine(sCreateThreadNotifyRoutine);
77
PsSetCreateProcessNotifyRoutineEx(sCreateProcessNotifyRoutineEx, TRUE);
78
}
79
80
NTSTATUS HandleCustomIOCTL(PDEVICE_OBJECT DeviceObject, PIRP Irp)
81
{
82
UNREFERENCED_PARAMETER(DeviceObject);
83
PIO_STACK_LOCATION stackLocation = NULL;
84
CHAR *messageFromKernel = "ohai from them kernelz";
85
86
stackLocation = IoGetCurrentIrpStackLocation(Irp);
87
88
if (stackLocation->Parameters.DeviceIoControl.IoControlCode == IOCTL_SPOTLESS)
89
{
90
DbgPrint("IOCTL_SPOTLESS (0x%x) issued", stackLocation->Parameters.DeviceIoControl.IoControlCode);
91
DbgPrint("Input received from userland: %s", (char*)Irp->AssociatedIrp.SystemBuffer);
92
}
93
94
Irp->IoStatus.Information = strlen(messageFromKernel);
95
Irp->IoStatus.Status = STATUS_SUCCESS;
96
97
DbgPrint("Sending to userland: %s", messageFromKernel);
98
RtlCopyMemory(Irp->AssociatedIrp.SystemBuffer, messageFromKernel, strlen(Irp->AssociatedIrp.SystemBuffer));
99
100
IoCompleteRequest(Irp, IO_NO_INCREMENT);
101
102
return STATUS_SUCCESS;
103
}
104
105
NTSTATUS MajorFunctions(PDEVICE_OBJECT DeviceObject, PIRP Irp)
106
{
107
UNREFERENCED_PARAMETER(DeviceObject);
108
109
PIO_STACK_LOCATION stackLocation = NULL;
110
stackLocation = IoGetCurrentIrpStackLocation(Irp);
111
112
switch (stackLocation->MajorFunction)
113
{
114
case IRP_MJ_CREATE: DbgPrint("Handle to symbolink link %wZ opened", DEVICE_SYMBOLIC_NAME); break; case IRP_MJ_CLOSE: DbgPrint("Handle to symbolink link %wZ closed", DEVICE_SYMBOLIC_NAME); break; default: break;
115
}
116
117
Irp->IoStatus.Information = 0;
118
Irp->IoStatus.Status = STATUS_SUCCESS;
119
IoCompleteRequest(Irp, IO_NO_INCREMENT);
120
121
return STATUS_SUCCESS;
122
}
123
124
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
125
{
126
UNREFERENCED_PARAMETER(DriverObject);
127
UNREFERENCED_PARAMETER(RegistryPath);
128
129
NTSTATUS status = 0;
130
131
// routine that will execute when our driver is unloaded/service is stopped
132
DriverObject->DriverUnload = DriverUnload;
133
134
// routine for handling IO requests from userland
135
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = HandleCustomIOCTL;
136
137
// routines that will execute once a handle to our device's symbolik link is opened/closed
138
DriverObject->MajorFunction[IRP_MJ_CREATE] = MajorFunctions;
139
DriverObject->MajorFunction[IRP_MJ_CLOSE] = MajorFunctions;
140
141
DbgPrint("Driver loaded");
142
143
// subscribe to notifications
144
PsSetCreateProcessNotifyRoutine(sCreateProcessNotifyRoutine, FALSE);
145
PsSetLoadImageNotifyRoutine(sLoadImageNotifyRoutine);
146
PsSetCreateThreadNotifyRoutine(sCreateThreadNotifyRoutine);
147
PsSetCreateProcessNotifyRoutineEx(sCreateProcessNotifyRoutineEx, FALSE);
148
DbgPrint("Listeners isntalled..");
149
150
IoCreateDevice(DriverObject, 0, &DEVICE_NAME, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &DriverObject->DeviceObject);
151
if (!NT_SUCCESS(status))
152
{
153
DbgPrint("Could not create device %wZ", DEVICE_NAME);
154
}
155
else
156
{
157
DbgPrint("Device %wZ created", DEVICE_NAME);
158
}
159
160
status = IoCreateSymbolicLink(&DEVICE_SYMBOLIC_NAME, &DEVICE_NAME);
161
if (NT_SUCCESS(status))
162
{
163
DbgPrint("Symbolic link %wZ created", DEVICE_SYMBOLIC_NAME);
164
}
165
else
166
{
167
DbgPrint("Error creating symbolic link %wZ", DEVICE_SYMBOLIC_NAME);
168
}
169
170
return STATUS_SUCCESS;
171
}
Copied!

References

PsSetCreateProcessNotifyRoutine function (ntddk.h) - Windows drivers
docsmsft
PsSetLoadImageNotifyRoutine function (ntddk.h) - Windows drivers
docsmsft
PsSetCreateThreadNotifyRoutine function (ntddk.h) - Windows drivers
docsmsft
Last modified 1yr ago