Termination and Injection Self Defense on Windows >= Vista SP1


On a previous post I’ve talked about how to perform API hooking at kernel level on 32bit Windows systems to prevent a process from being terminated.
Today I’m gonna talk about OBR and callbacks, mainly to show how to achieve the same result on 64bit systems starting from Vista SP1 and later.

Why should I prevent a process from being killed?

If you try to search for some code samples or documentation about this topic, you will find that almost anyone will say that denying a computer administrator to terminate a process is wrong, so process self-protection should be avoided.
This is true 99% of the cases, the remaining 1% are those cases when a process is vital for the system infrastructure security such as anti malwares, IPS and IDS. Those kind of softwares have to protect themself from malicious software trying to terminate their services and processes or inject arbitrary code into their executable address space, so that’s when an appropriate protection is vital.

The user mode curse

No matter how hard you try, there’s really no 100% safe way to do this in user mode … you could try API hooking, passive monitoring, process sandboxing, whatever, there’s always a way to bypass a user mode protection. API hooks can be overwritten, monitoring can be eluded with straight calls to ntdll and obfuscation, sandboxing can be detected.
That’s why kernel patching was the most used technique on pre Vista systems, once you are in the kernel you have full power and the lowest level possible vision of what is happening on a computer, moreover you don’t need to handle all the abstractions a user mode environment implies.

Drivers and Ob Callbacks

When you talk about working in the Windows kernel, you talk about developing a driver, that’s why Microsoft implemented a set of brand new API to intercept and eventually filter events and actions on object handles before they are actually executed by the kernel.
Enough talking, let’s see how the ObRegisterCallbacks is defined:

1
2
3
4
NTSTATUS ObRegisterCallbacks(
_In_ POB_CALLBACK_REGISTRATION CallBackRegistration,
_Out_ PVOID *RegistrationHandle
);

This function accepts an input structure pointer that defines what object handles you want to monitor and which actions on them and gives you back a RegistrationHandle i.e. a global object we will use from now on to work with those callbacks.

The OB_CALLBACK_REGISTRATION structure content:

1
2
3
4
5
6
7
typedef struct _OB_CALLBACK_REGISTRATION {
USHORT Version;
USHORT OperationRegistrationCount;
UNICODE_STRING Altitude;
PVOID RegistrationContext;
OB_OPERATION_REGISTRATION *OperationRegistration;
} OB_CALLBACK_REGISTRATION, *POB_CALLBACK_REGISTRATION;

The Altitude field is basically the load order you want your driver to be loaded at, RegistrationContext is an arbitrary object you want to be passed down to your callbacks and finally the OperationRegistration field is a pointer to an array of OB_OPERATION_REGISTRATION structures which defines every detail of our callback. So let’s say we want to intercept every access to object handles of processes ( OpenProcess, etc ), we would declare:

1
2
3
4
5
6
OB_OPERATION_REGISTRATION opRegistrations[1] = { { 0 } };

opRegistrations[0].ObjectType = PsProcessType;
opRegistrations[0].Operations = OB_OPERATION_HANDLE_CREATE;
opRegistrations[0].PreOperation = YourPreCallback;
opRegistrations[0].PostOperation = YourPostCallback;

YourPreCallback and YourPostCallback are obviously two routines that will be called before the process handle is actually opened ( the pre callback ) and after the operation is completed ( post callback ).
For our purposes, the post callback can be declared just as:

1
2
3
4
5
VOID YourPostCallback( PVOID RegistrationContext, POB_POST_OPERATION_INFORMATION OperationInformation )
{
UNREFERENCED_PARAMETER(RegistrationContext);
UNREFERENCED_PARAMETER(OperationInformation);
}

While the pre callback will do all the dirty work for us ( we want to block an unauthorized access to our processes before they’re actually performed, right? )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
OB_PREOP_CALLBACK_STATUS YourPreCallback( PVOID RegistrationContext, POB_PRE_OPERATION_INFORMATION OperationInformation )
{
PEPROCESS OpenedProcess = (PEPROCESS)OperationInformation->Object,
CurrentProcess = PsGetCurrentProcess();
ULONG ulProcessId = PsGetProcessId(OpenedProcess);

UNREFERENCED_PARAMETER(RegistrationContext);

// Allow operations from the process itself
if( CurrentProcess == OpenedProcess )
goto done;

// Allow operations from within the kernel
else if( OperationInformation->KernelHandle == 1 )
goto done;

/*
* PUT YOUR PROTECTED PROCESS ID HERE
*/
else if( ulProcessId != 1234 )
goto done;

// Remove access bits from open access mask.
else if( OperationInformation->Operation == OB_OPERATION_HANDLE_CREATE )
{
if( ( OperationInformation->Parameters->CreateHandleInformation.OriginalDesiredAccess & PROCESS_TERMINATE ) == PROCESS_TERMINATE )
{
DbgPrintEx( DPFLTR_IHVDRIVER_ID, DPFLTR_WARNING_LEVEL, "Requested protected process termination.\n" );

OperationInformation->Parameters->CreateHandleInformation.DesiredAccess &= ~PROCESS_TERMINATE;
}

if( ( OperationInformation->Parameters->CreateHandleInformation.OriginalDesiredAccess & PROCESS_VM_OPERATION ) == PROCESS_VM_OPERATION )
{
OperationInformation->Parameters->CreateHandleInformation.DesiredAccess &= ~PROCESS_VM_OPERATION;
}

if( ( OperationInformation->Parameters->CreateHandleInformation.OriginalDesiredAccess & ~PROCESS_VM_READ ) == PROCESS_VM_READ )
{
OperationInformation->Parameters->CreateHandleInformation.DesiredAccess &= ~PROCESS_VM_READ;
}

if( ( OperationInformation->Parameters->CreateHandleInformation.OriginalDesiredAccess & PROCESS_VM_WRITE ) == PROCESS_VM_WRITE )
{
DbgPrintEx( DPFLTR_IHVDRIVER_ID, DPFLTR_WARNING_LEVEL, "Requested protected process virtual memory write access ( INJECTION! ).\n" );

OperationInformation->Parameters->CreateHandleInformation.DesiredAccess &= ~PROCESS_VM_WRITE;
}
}

done:

return OB_PREOP_SUCCESS;
}

What this callback is doing, is preventing process with PID 1234 from being accessed with PROCESS_TERMINATE, PROCESS_VM_OPERATION, PROCESS_VM_READ or PROCESS_VM_WRITE privileges, so any malicious software that will try to terminate it or to call API such as (Write|Read)ProcessMemory will inevitably fail with the good old 0x00000005 error ( ACCESS_DENIED ).

When your driver unloads, don’t forget to call ObUnRegisterCallbacks on the RegistrationHandle you’ve previously saved on a global object to correctly inform the kernel that you are not going to intercept those operations anymore.

Well, easy peasy, this is how AV and security softwares are being protected nowdays on 64bit systems from malicious termination, code injection, credential theft from memory (well you are just an idiot if you keep sensible data as clear text in memory -.-) and any kind of manipulation which could lead to the system damages.

Become a Patron!