Unfortunately, this week my steady assault on the EDR drivers came to a grinding halt when I had to take a couple days off.
1. Error 404 - Intern not found
The past few weeks I spent a lot of time getting acquainted with the windows kernel and the inner workings of an EDR/AV product. I also covered the two main methods of attacking the EDR/AV drivers, namely kernel callback patching and IRP MajorFunction hooking. I’ve been working on my own driver called Interceptor, which will implement both these techniques as well as a method to load itself into kernel memory bypassing Driver Signing Enforcement (DSE).
I’m of the opinion that when writing tools or exploits, the author should know exactly what each part of his/her code is responsible for, how it works and avoid copy pasting code from similar projects without fully understanding it. With that said, I’m writing Interceptor based on numerous other projects, so I’m taking my time to go through their associated blogposts and understand their working and purpose.
Interceptor currently supports IRP hooking/unhooking drivers by name or by index based on loaded modules.
Using the -l
option, Interceptor will list all the currently loaded modules on the system and assign them an index. This index can be used to hook the module with the -h
option.
Using the -lh
option, Interceptor will list all the currently hooked modules with their corresponding index in the global hooked drivers array. Interceptor currently supports hooking up to 64 drivers. The index can be used with the -u
option to unhook the module.
Once a module is hooked, Interceptor’s InterceptGenericDispatch()
function will be called whenever an IRP is received. The current function notifies a call was intercepted via a debug message and then call the original completion routine. I’m currently working on a method to inspect and modify the IRPs before passing them to their completion routine.
NTSTATUS InterceptGenericDispatch(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
UNREFERENCED_PARAMETER(DeviceObject);
auto stack = IoGetCurrentIrpStackLocation(Irp);
auto status = STATUS_UNSUCCESSFUL;
KdPrint((DRIVER_PREFIX "GenericDispatch: call intercepted\n"));
//inspect IRP
if(isTargetIrp(Irp)) {
//modify IRP
status = ModifyIrp(Irp);
//call original
for (int i = 0; i < MaxIntercept; i++) {
if (globals.Drivers[i].DriverObject == DeviceObject->DriverObject) {
auto CompletionRoutine = globals.Drivers[i].MajorFunction[stack->MajorFunction];
return CompletionRoutine(DeviceObject, Irp);
}
}
}
else if (isDiscardIrp(Irp)) {
//call own completion routine
status = STATUS_INVALID_DEVICE_REQUEST;
return CompleteRequest(Irp, status, 0);
}
else {
//call original
for (int i = 0; i < MaxIntercept; i++) {
if (globals.Drivers[i].DriverObject == DeviceObject->DriverObject) {
auto CompletionRoutine = globals.Drivers[i].MajorFunction[stack->MajorFunction];
return CompletionRoutine(DeviceObject, Irp);
}
}
}
return CompleteRequest(Irp, status, 0);
}
I’m also working on a module that supports patching kernel callbacks. The difficulty here is locating the different callback arrays by enumerating their calling functions and looking for certain opcode patterns, which change between different versions of Windows.
As mentioned in one of my previous blogposts, locating the callback arrays for PsSetCreateprocessNotifyRoutine()
and PsSetCreateThreadNotifyRoutine()
is done by looking for a CALL
instruction to PspSetCreateProcessNotifyRoutine()
and PspSetCreateThreadNotifyRoutine()
respectively, followed by looking for a LEA
instruction.
Finding the callback array for PsSetLoadImageNotifyRoutine()
is slightly different as the function first jumps to PsSetLoadImageNotifyRoutineEx()
. Next, we skip looking for the CALL
instruction and go straight for the LEA
instruction instead, which puts the callback array address into RCX
.
Interceptor’s callback module currently implements patching functionality for Process and Thread callbacks.
The registered callbacks on the system and their patch status can be listed using the -lc
command.
2. Conclusion
In the previous blogpost of this series, we combined the functionality of two drivers, Evil and Interceptor, to partially bypass ESET Internet Security. In this post we took a closer look at Interceptor’s capabilities and future features that are in development. In the upcoming blogposts we’ll see how Interceptor as a fully standalone driver is able to conquer not just ESET Internet Security, but other EDR products as well.
(Un)fortunately, I received my first COVID-19 vaccination shot in the middle of the week, which has knocked me out quite good, so I had to take a couple days off.