Driver aka Kernel Mode
Important:
To start off, this thread are for people who are just trying to learn more about ring 0 bypasses. i will provide basic information about drivers and how they exactly work. after reading this thread, you should get a better idea on how ring 0 bypass works and how to make a simple driver. For people who already are good in kernel, then feel free to correct me, any way you like. i am just a person who had a chance to play around with drivers and develop my own Anti Cheat to test and see how normal Anti Cheats works. one more thing, without daax, i dont think i will where i am at. He really taught me alot of things which will be useful.
What Are Drivers?
To start off, one major difference between Drivers and normal programs is that Drivers cannot display any types of overlay or user interfaces. To have a overlay, you will need to make your own overlay in usermode and communicate with the driver . Drivers also cannot be loaded if the user isnt admin and if the driver doesnt have a cert ( test mode is the only way or disable patch guard ). The other thing about drivers is that, if it crashes, thats it, your PC will crash with it. Drivers also ran in Kernel mode rather then usermode like regular programs, giving access to kernel APIs. Drivers also have full access to the system, meaning you can do pretty much anything with no-one really stopping you. Anti viruses do have measures against this and you cannot load a driver without test mode or disabling patch guard, so making a virus is pretty much dumb. Cool features that Drivers can do is hiding processes from pretty much everything, Drivers also can monitor callbacks such as creating Handles and so on. Driver also have full access to pretty much everything
What Is PatchGurad?
Patch Guard is a feature made by windows themselves. The reason why we have PatchGuard is to not allow any untrusted Drivers ( Drivers without cert ). This feature is only on 64 Bit. Patch Guard also does not allow Drivers to do some actions such as Modifying system service tables, Modifying the interrupt descriptor table, Modifying the global descriptor table, Using kernel stacks not allocated by the kernel and Modifying or patching code contained within the kernel itself,or the HAL or NDIS kernel libraries. There are ways to remove this feature, but some Anti Cheat like EAC will see that Patch Guard has been removed and will take actions against it.
How Does Anti Cheats Use Their Drivers?
Anti Cheats uses ObRegisterCallback which basically allows the Anti Cheat to monitor any Handle creations, they also have two actions against this, Pre (before) and Post (after). Normally what they do is strip handle permissions to their game in their Pre function, stripping handles is as easy as these lines:
OB_PREOP_CALLBACK_STATUS PreCallback(PVOID RegistrationContext, POB_PRE_OPERATION_INFORMATION OperationInformation)
{
UNREFERENCED_PARAMETER(RegistrationContext);
PEPROCESS OpenedProcess = (PEPROCESS)OperationInformation->Object,
CurrentProcess = PsGetCurrentProcess();
PsLookupProcessByProcessId(ProtectedProcess, &ProtectedProcessProcess); // Getting the PEPROCESS using the PID
PsLookupProcessByProcessId(Lsass, &LsassProcess); // Getting the PEPROCESS using the PID
PsLookupProcessByProcessId(Csrss1, &Csrss1Process); // Getting the PEPROCESS using the PID
PsLookupProcessByProcessId(Csrss2, &Csrss2Process); // Getting the PEPROCESS using the PID
if (OpenedProcess == Csrss1Process) // Making sure to not strip csrss's Handle, will cause BSOD
return OB_PREOP_SUCCESS;
if (OpenedProcess == Csrss2Process) // Making sure to not strip csrss's Handle, will cause BSOD
return OB_PREOP_SUCCESS;
if (OpenedProcess == CurrentProcess) // make sure the driver isnt getting stripped ( even though we have a second check )
return OB_PREOP_SUCCESS;
if (OpenedProcess == ProtectedProcess) // Making sure that the game can open a process handle to itself
return OB_PREOP_SUCCESS;
if (OperationInformation->KernelHandle) // allow drivers to get a handle
return OB_PREOP_SUCCESS;
// PsGetProcessId((PEPROCESS)OperationInformation->Object) equals to the created handle's PID, so if the created Handle equals to the protected process's PID, strip
if (PsGetProcessId((PEPROCESS)OperationInformation->Object) == ProtectedProcess)
{
if (OperationInformation->Operation == OB_OPERATION_HANDLE_CREATE) // striping handle
{
OperationInformation->Parameters->CreateHandleInformation.DesiredAccess = (SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION);
}
else
{
OperationInformation->Parameters->DuplicateHandleInformation.DesiredAccess = (SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION);
}
return OB_PREOP_SUCCESS;
}
}
These lines of code will replace every Handle created ( not including lsass, csrss, the game itself and the current process ) permissions by PROCESS_QUERY_LIMITED_INFORMATION and SYNCHRONIZE. This will not allow any programs to RPM and WPM unless they abuse lsass or other white-listed programs. This function also strips handle permissions even before they are really created, therefor being named PreCallback. In their PostCallback, they might most likely log what happened and so on.
Bypass Against ObRegisterCallbacks?
There are afew ways to basically bypass this, things like unregistering the callback. You do have to think about afew things before doing this. Anti Cheat can check if their callback is unreigstered by just registering their callback again, two things can happen if they do that, one is that they get a STATUS_FLT_INSTANCE_ALTITUDE_COLLISION, meaning that a collision would indicate that the callback was still in place. However the other thing that can happen is returning success when registering again, this means that the callback was unregistered. Anti Cheat can do somethings about this. The other way to bypass would be just abusing the programs they white-listed. Programs such as lsass, csrss and others. One note to take is that lsass may not always be free to use, in my time of making an Anti Cheat to better learn, i have noticed that you can strip handle permissions from lsass without BSOD. Meaning, dont always thing lsass is a easy bypass. The other downside to this is that, the Anti Cheat can monitor the white-listed programs, Anti Cheats such as Battleye monitors their white-listed programs, which makes it harder to really use this bypass.
Coding A Driver?
Well, coding a Driver isn’t hard. Alot of people who thinks about kernel usually thinks its hard, well its not. When i first started on this project, i thought it was extremely hard. A simple Kernel hack. The fact is that, its not too hard to make a simple program which reads and modifies memory. Follow along if you like.
What You Need:
MicroSoft Visual Studio
Windows Driver Kit
C++ Knowledge
Driver’s Main:
Drivers also has a Main function like every other programs, but its slightly different i would say. Driver’s Main Function is called “DriverEntry”, this function will be basically your entry point of your driver.
#include
#include
DRIVER_INITIALIZE DriverEntry;
NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath)
{
return STATUS_SUCCESS;
}
Identifying The Driver:
Drivers also need a SymbolName and a Name. This can easily done by afew lines of code. By using IoCreateDevice and IoCreateSymbolicLink.
UNICODE_STRING DriverName, SymbolName;
PDEVICE_OBJECT pDeviceObj;
RtlInitUnicodeString(&DriverName, L"\\Device\\Driver1"); // Giving the driver a name
RtlInitUnicodeString(&SymbolName, L"\\DosDevices\\Driver1"); // Giving the driver a symbol
UNICODE_STRING deviceNameUnicodeString, deviceSymLinkUnicodeString;
NTSTATUS NtRet2 = IoCreateDevice(pDriverObject, 0, &DriverName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &pDeviceObj);
pDeviceObject->Flags |= DO_DIRECT_IO;
pDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
This would Name the driver “Driver1” and give a Symbol Name of “Driver1”.
Unload Driver:
You will need a function for Unloading. Without This function, the Driver will have no way to unload, which would be quite annoying. We will be using two simple fucntions which just deletes the Driver’s Symbol Name and The Driver Itself.
VOID DriverUnload(PDRIVER_OBJECT pDriverObject)
{
IoDeleteSymbolicLink(&SymbolName);
IoDeleteDevice(pDriverObject->DeviceObject);
}
Now once this is done, you will have to tell the Driver what the Unload function is.
pDriverObject->DriverUnload = DriverUnload;
This would be basically called when unloading the driver.
Communication With Usermode Program:
To start off, we will need a Create Call and a Close Call Functions, these functions will not do much for now, but its a good practice to always do it.
NTSTATUS CreateCall(PDEVICE_OBJECT DeviceObject, PIRP irp)
{
irp->IoStatus.Status = STATUS_SUCCESS;
irp->IoStatus.Information = 0;
IoCompleteRequest(irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS CloseCall(PDEVICE_OBJECT DeviceObject, PIRP irp)
{
irp->IoStatus.Status = STATUS_SUCCESS;
irp->IoStatus.Information = 0;
IoCompleteRequest(irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
Now after that is done, we need to somewhat tell the driver these are our close and create functions.
pDriverObject->MajorFunction[IRP_MJ_CREATE] = CreateCall;
pDriverObject->MajorFunction[IRP_MJ_CLOSE] = CloseCall;
Alrite, now moving on to the main function to communicate with the driver and back.
Lets make a function for it and lets name it “IoControl”. This will control all inputs and outputs. Lets make it empty for now and tell the Driver that this is our Input and Output Control functions.
NTSTATUS IoControl(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
NTSTATUS Status = STATUS_SUCCESS;
return Status;
}
Telling the Driver
pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IoControl;
Alrite once this is done, lets move on to filling in the IOControl. We will be basically using a Code for each communication. Lets say, you want the Driver to write some part of memory, then you can use a code which will be sent to the Driver and the Driver would know what you want. So if i set the Write Request code to 0x0814, then in the usermode i will have to use that code if i wanted the Driver to write. Same for Read and so on.
Firstly lets define the codes
// Write 0x812
// Read 0x813
// Request Code To Read
#define IO_READ_REQUEST CTL_CODE(FILE_DEVICE_UNKNOWN, 0x812, METHOD_BUFFERED, FILE_SPECIAL_ACCESS)
// Request Code To Write
#define IO_WRITE_REQUEST CTL_CODE(FILE_DEVICE_UNKNOWN, 0x813, METHOD_BUFFERED, FILE_SPECIAL_ACCESS)
One more note, you can change the code if you like. I will just use 0x812 and 0x813 for this example.
Alrite once that is done, lets store the received code in a variable for the Driver to check the request and take actions.
PIO_STACK_LOCATION StackLocation= IoGetCurrentIrpStackLocation(Irp);
// Code received from user space
ULONG CodeRequest = StackLocation->Parameters.DeviceIoControl.IoControlCode;
This will store the Code received from usermode.
Lets setup the Requestions And Actions, this is quite easy, just setting up if and else statements.
if (ControlCode == IO_READ_REQUEST)
{
Status = STATUS_SUCCESS;
}
else if (ControlCode == IO_WRITE_REQUEST)
{
Status = STATUS_SUCCESS;
}
Once this is setup, Lets move on to the Usermode program to setup their communication.
To start off, lets get a handle to the Driver.
HANDLE hDriver = CreateFileA("\\\\.\\Driver1", GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
Other thing you can do is checking if the Handle is valid or not and if its NULL then doing something about it.
You also need to Define the request codes in the usermode program, this is also quite easy.
// Write 0x812
// Read 0x813
// Request Code To Read
#define IO_READ_REQUEST CTL_CODE(FILE_DEVICE_UNKNOWN, 0x812, METHOD_BUFFERED, FILE_SPECIAL_ACCESS)
// Request Code To Write
#define IO_WRITE_REQUEST CTL_CODE(FILE_DEVICE_UNKNOWN, 0x813, METHOD_BUFFERED, FILE_SPECIAL_ACCESS)
Once done, you can pretty much send requests and so on. The way you can send information from usermode is by using a function called DeviceIoControl
Its simple as this
DeviceIoControl(hDriver, IO_READ_REQUEST, &ReadRequest,
sizeof(ReadRequest), &ReturnBytes, sizeof(ReadRequest), 0, 0)
You can make afew other request such as Process ID, where you can send the driver the game’s process ID as there is not documented way of getting a game’s processID using the name. Unless you loop through the entire Process list and find the name you are looking for.
Debugging:
This is one of the worst moments i went through with this, just because i dont really have a spare PC to use for testing. Nevertheless there are ways to debug with only one PC.
Alrite, lets start off by saying that when you are trying to see whats wrong with your Driver or why is it causing BSOD, the minidump file is important. A minidump is created everytime you get a BSOD. It is usually located under C:\Windows\Minidump. You might ask, well how do i open these files. You will need a program called WinDbg. This program allows you to debug Drivers and view the minidumps. This can be helpful as you will know where the problem is. You are able to view the problem in detail online. The common problem people have are PAGE FAULT IN NON PAGE AREA and CRITICAL_STRUCTURE_CORRUPTION. CRITICAL_STRUCTURE_CORRUPTION is caused by Patch Guard most of the time and PAGE FAULT IN NON PAGE AREA is when you fuck up your Unload.
Other ways to debug your Driver is by using DbgPrintEx or DbgPrint. This together with DebugView can help you out. This will not work if you Driver just gives you BSOD, you will need to use the method above, this method is for people who dont really understand what is happening inside their Driver. You can use this method to view what your variables are storing or what some of the function returns.
The last method you can use is downloading a VMWare or a virtual machine. You can setup your virtual machine, by downloading softwares like VMWare VMBox and other few softwares. From there, you can use
WinDbg and setup a debugger there.
Other Functions:
This is just Extra functions that you might want to add or use. Functions such as shutting down programs in usermode and so on.
Terminating Programs
NTSTATUS TerminatingProcess(ULONG targetPid)
{
NTSTATUS NtRet = STATUS_SUCCESS;
PEPROCESS PeProc = { 0 };
NtRet = PsLookupProcessByProcessId(targetPid, &PeProc);
if (NtRet != STATUS_SUCCESS)
{
return NtRet;
}
HANDLE ProcessHandle;
NtRet = ObOpenObjectByPointer(PeProc, NULL, NULL, 25, *PsProcessType, KernelMode, &ProcessHandle);
if (NtRet != STATUS_SUCCESS)
{
return NtRet;
}
ZwTerminateProcess(ProcessHandle, 0);
ZwClose(ProcessHandle);
return NtRet;
}
Hiding A Program:
void RemoveTheLinks(PLIST_ENTRY Current)
{
PLIST_ENTRY Previous, Next;
Previous = (Current->Blink);
Next = (Current->Flink);
// Loop over self (connect previous with next)
Previous->Flink = Next;
Next->Blink = Previous;
// Re-write the current LIST_ENTRY to point to itself (avoiding BSOD)
Current->Blink = (PLIST_ENTRY)&Current->Flink;
Current->Flink = (PLIST_ENTRY)&Current->Flink;
return;
}
ULONG LookForProcessOffsets()
{
ULONG pid_ofs = 0; // The offset we're looking for
int idx = 0; // Index
ULONG pids[3]; // List of PIDs for our 3 processes
PEPROCESS eprocs[3]; // Process list, will contain 3 processes
//Select 3 process PIDs and get their EPROCESS Pointer
for (int i = 16; idx<3; i += 4)
{
if (NT_SUCCESS(PsLookupProcessByProcessId((HANDLE)i, &eprocs[idx])))
{
pids[idx] = i;
idx++;
}
}
/*
Go through the EPROCESS structure and look for the PID
we can start at 0x20 because UniqueProcessId should
not be in the first 0x20 bytes,
also we should stop after 0x300 bytes with no success
*/
for (int i = 0x20; i<0x300; i += 4)
{
if ((*(ULONG *)((UCHAR *)eprocs[0] + i) == pids[0])
&& (*(ULONG *)((UCHAR *)eprocs[1] + i) == pids[1])
&& (*(ULONG *)((UCHAR *)eprocs[2] + i) == pids[2]))
{
pid_ofs = i;
break;
}
}
ObDereferenceObject(eprocs[0]);
ObDereferenceObject(eprocs[1]);
ObDereferenceObject(eprocs[2]);
return pid_ofs;
}
// hides any process from the task bar and everything. only works for about 10 - 30 mins before patch guard is triggered and BSOD happens
PCHAR GhostProcess(UINT32 pid)
{
LPSTR result = ExAllocatePool(NonPagedPool, sizeof(ULONG) + 20);;
// Get PID offset nt!_EPROCESS.UniqueProcessId
ULONG PID_OFFSET = LookForProcessOffsets();
// Check if offset discovery was successful if (PID_OFFSET == 0) {
return (PCHAR)"Could not find PID offset!";
}
// Get LIST_ENTRY offset nt!_EPROCESS.ActiveProcessLinks
ULONG LIST_OFFSET = PID_OFFSET;
// Check Architecture using pointer size
INT_PTR ptr;
// Ptr size 8 if compiled for a 64-bit machine, 4 if compiled for 32-bit machine
LIST_OFFSET += sizeof(ptr);
// Get current process
PEPROCESS CurrentEPROCESS = PsGetCurrentProcess();
// Initialize other variables
PLIST_ENTRY CurrentList = (PLIST_ENTRY)((ULONG_PTR)CurrentEPROCESS + LIST_OFFSET);
PUINT32 CurrentPID = (PUINT32)((ULONG_PTR)CurrentEPROCESS + PID_OFFSET);
// Check self if (*(UINT32 *)CurrentPID == pid) {
RemoveTheLinks(CurrentList);
return (PCHAR)result;
}
// Record the starting position
PEPROCESS StartProcess = CurrentEPROCESS;
// Move to next item
CurrentEPROCESS = (PEPROCESS)((ULONG_PTR)CurrentList->Flink - LIST_OFFSET);
CurrentPID = (PUINT32)((ULONG_PTR)CurrentEPROCESS + PID_OFFSET);
CurrentList = (PLIST_ENTRY)((ULONG_PTR)CurrentEPROCESS + LIST_OFFSET);
// Loop until we find the right process to remove
// Or until we circle back
while ((ULONG_PTR)StartProcess != (ULONG_PTR)CurrentEPROCESS)
{
// Check item
if (*(UINT32 *)CurrentPID == pid) {
RemoveTheLinks(CurrentList);
return (PCHAR)result;
}
// Move to next item
CurrentEPROCESS = (PEPROCESS)((ULONG_PTR)CurrentList->Flink - LIST_OFFSET);
CurrentPID = (PUINT32)((ULONG_PTR)CurrentEPROCESS + PID_OFFSET);
CurrentList = (PLIST_ENTRY)((ULONG_PTR)CurrentEPROCESS + LIST_OFFSET);
}
return (PCHAR)result;
}
Finding ThreadID Using Thread Handle:
typedef struct _THREAD_BASIC_INFORMATION {
NTSTATUS ExitStatus;
PVOID TebBaseAddress;
CLIENT_ID ClientId;
ULONG AffinityMask;
ULONG Priority;
ULONG BasePriority;
} THREAD_BASIC_INFORMATION, *PTHREAD_BASIC_INFORMATION;
ULONG ThreadIDFromHandle(HANDLE hThread)
{
THREAD_BASIC_INFORMATION teb;
if (hThread)
if (NT_SUCCESS(ZwQueryInformationThread(hThread, 0, &teb, sizeof(teb), NULL)))
return (ULONG)teb.ClientId.UniqueThread;
return 0;
}
Finding ProcessID From Thread Handle:
typedef struct _THREAD_BASIC_INFORMATION {
NTSTATUS ExitStatus;
PVOID TebBaseAddress;
CLIENT_ID ClientId;
ULONG AffinityMask;
ULONG Priority;
ULONG BasePriority;
} THREAD_BASIC_INFORMATION, *PTHREAD_BASIC_INFORMATION;
ULONG getPIDByThreadHandle(HANDLE hThread)
{
THREAD_BASIC_INFORMATION teb;
if(hThread)
if(NT_SUCCESS(ZwQueryInformationThread(hThread, 0, &teb, sizeof(teb), NULL)))
return (ULONG)teb.ClientId.UniqueProcess;
return 0;
}
Getting Process Name By PID:
#define BUF_POOL_TAG 'bufP'
NTSTATUS getProcNameByPID(ULONG pid, PUNICODE_STRING procName)
{
NTSTATUS status;
HANDLE hProcess;
PEPROCESS eProcess = NULL;
ULONG returnedLength;
UNICODE_STRING func;
PVOID buffer = NULL;
PUNICODE_STRING imageName = NULL;
if (pid == 0 || procName == NULL)
return STATUS_INVALID_PARAMETER;
if (pid == 4)
{
RtlInitUnicodeString(&func, L"System");
RtlCopyUnicodeString(procName, &func);
return STATUS_SUCCESS;
}
status = PsLookupProcessByProcessId((HANDLE)pid, &eProcess);
if (!NT_SUCCESS(status))
return status;
status = ObOpenObjectByPointer(eProcess, 0, NULL, 0, 0, KernelMode, &hProcess);
if (!NT_SUCCESS(status))
return status;
ObDereferenceObject(eProcess);
ZwQueryInformationProcess(hProcess, ProcessImageFileName, NULL, 0, &returnedLength);
buffer = ExAllocatePoolWithTag(PagedPool, returnedLength, BUF_POOL_TAG);
if (!buffer)
return STATUS_NO_MEMORY;
status = ZwQueryInformationProcess(hProcess, ProcessImageFileName, buffer, returnedLength, &returnedLength);
if (NT_SUCCESS(status))
{
imageName = (PUNICODE_STRING)buffer;
if (procName->MaximumLength > imageName->Length)
RtlCopyUnicodeString(procName, imageName);
else
status = STATUS_BUFFER_TOO_SMALL;
}
ExFreePool(buffer);
return status;
}
Wanted To Test Your Cheats?
Well, you can, i will somewhat share my anti cheat which is extremely simple. All it does it strip handles to CSGO. Use this to see exactly how ObRegisterCallback works.
Driver.c
#include
#include
DRIVER_INITIALIZE DriverEntry;
#pragma alloc_text(INIT, DriverEntry)
#define PROCESS_QUERY_LIMITED_INFORMATION 0x1000
typedef struct _OB_REG_CONTEXT {
USHORT Version;
UNICODE_STRING Altitude;
USHORT ulIndex;
OB_OPERATION_REGISTRATION *OperationRegistration;
} REG_CONTEXT, *PREG_CONTEXT;
UNICODE_STRING SACDriverName, SACSymbolName;
PVOID ObHandle = NULL;
//ULONG ProtectedProcess1 = 516; // The Usermode Anti Cheat PID
ULONG ProtectedProcess = 0; // Your game's PID
ULONG Lsass = 0;
ULONG Csrss1 = 0;
ULONG Csrss2 = 0;
ULONG ProtectedThreadID = 0;
// This function will be called when a handle is about to be created
OB_PREOP_CALLBACK_STATUS PreCallback(PVOID RegistrationContext, POB_PRE_OPERATION_INFORMATION OperationInformation)
{
UNREFERENCED_PARAMETER(RegistrationContext);
if (ProtectedProcess == 0)
return OB_PREOP_SUCCESS;
if (Lsass == 0)
return OB_PREOP_SUCCESS;
if (Csrss1 == 0)
return OB_PREOP_SUCCESS;
if (Csrss2 == 0)
return OB_PREOP_SUCCESS;
PEPROCESS LsassProcess;
PEPROCESS Csrss1Process;
PEPROCESS Csrss2Process;
PEPROCESS ProtectedProcessProcess;
PEPROCESS OpenedProcess = (PEPROCESS)OperationInformation->Object,
CurrentProcess = PsGetCurrentProcess();
PsLookupProcessByProcessId(ProtectedProcess, &ProtectedProcessProcess); // Getting the PEPROCESS using the PID
PsLookupProcessByProcessId(Lsass, &LsassProcess); // Getting the PEPROCESS using the PID
PsLookupProcessByProcessId(Csrss1, &Csrss1Process); // Getting the PEPROCESS using the PID
PsLookupProcessByProcessId(Csrss2, &Csrss2Process); // Getting the PEPROCESS using the PID
if (OpenedProcess == Csrss1Process) // Making sure to not strip csrss's Handle, will cause BSOD
return OB_PREOP_SUCCESS;
if (OpenedProcess == Csrss2Process) // Making sure to not strip csrss's Handle, will cause BSOD
return OB_PREOP_SUCCESS;
if (OpenedProcess == CurrentProcess) // make sure the driver isnt getting stripped ( even though we have a second check )
return OB_PREOP_SUCCESS;
if (OpenedProcess == ProtectedProcess) // Making sure that the game can open a process handle to itself
return OB_PREOP_SUCCESS;
if (OperationInformation->KernelHandle) // allow drivers to get a handle
return OB_PREOP_SUCCESS;
// PsGetProcessId((PEPROCESS)OperationInformation->Object) equals to the created handle's PID, so if the created Handle equals to the protected process's PID, strip
if (PsGetProcessId((PEPROCESS)OperationInformation->Object) == ProtectedProcess)
{
if (OperationInformation->Operation == OB_OPERATION_HANDLE_CREATE) // striping handle
{
OperationInformation->Parameters->CreateHandleInformation.DesiredAccess = (SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION);
}
else
{
OperationInformation->Parameters->DuplicateHandleInformation.DesiredAccess = (SYNCHRONIZE | PROCESS_QUERY_LIMITED_INFORMATION);
}
return OB_PREOP_SUCCESS;
}
}
// This happens after everything.
VOID PostCallBack(PVOID RegistrationContext, POB_POST_OPERATION_INFORMATION OperationInformation)
{
UNREFERENCED_PARAMETER(RegistrationContext);
UNREFERENCED_PARAMETER(OperationInformation);
}
// Unregistering the callback.
VOID UnRegister()
{
ObUnRegisterCallbacks(ObHandle); // unregistering the callback
ObHandle = NULL;
}
// Terminating a process of your choice using the PID, usefull if the cheat is also using a driver to strip it's handles and therefore you can forcefully close it using the driver
NTSTATUS TerminatingProcess(ULONG targetPid)
{
NTSTATUS NtRet = STATUS_SUCCESS;
PEPROCESS PeProc = { 0 };
NtRet = PsLookupProcessByProcessId(targetPid, &PeProc);
if (NtRet != STATUS_SUCCESS)
{
return NtRet;
}
HANDLE ProcessHandle;
NtRet = ObOpenObjectByPointer(PeProc, NULL, NULL, 25, *PsProcessType, KernelMode, &ProcessHandle);
if (NtRet != STATUS_SUCCESS)
{
return NtRet;
}
ZwTerminateProcess(ProcessHandle, 0);
ZwClose(ProcessHandle);
return NtRet;
}
NTSTATUS DriverDispatchRoutine(PDEVICE_OBJECT pDeviceObject, PIRP pIrp)
{
PVOID buffer;
NTSTATUS NtStatus = STATUS_SUCCESS;
PIO_STACK_LOCATION pIo;
pIo = IoGetCurrentIrpStackLocation(pIrp);
pIrp->IoStatus.Information = 0;
switch (pIo->MajorFunction)
{
case IRP_MJ_CREATE:
NtStatus = STATUS_SUCCESS;
break;
case IRP_MJ_READ:
NtStatus = STATUS_SUCCESS;
break;
case IRP_MJ_WRITE:
break;
case IRP_MJ_CLOSE:
NtStatus = STATUS_SUCCESS;
break;
default:
NtStatus = STATUS_INVALID_DEVICE_REQUEST;
break;
}
pIrp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return NtStatus;
}
// This will be called, if the driver is unloaded or just returns something
VOID DriverUnload(PDRIVER_OBJECT pDriverObject)
{
UnRegister();
IoDeleteSymbolicLink(&SACSymbolName);
IoDeleteDevice(pDriverObject->DeviceObject);
//DbgPrint("Unload called!");
}
void RemoveTheLinks(PLIST_ENTRY Current)
{
PLIST_ENTRY Previous, Next;
Previous = (Current->Blink);
Next = (Current->Flink);
// Loop over self (connect previous with next)
Previous->Flink = Next;
Next->Blink = Previous;
// Re-write the current LIST_ENTRY to point to itself (avoiding BSOD)
Current->Blink = (PLIST_ENTRY)&Current->Flink;
Current->Flink = (PLIST_ENTRY)&Current->Flink;
return;
}
ULONG LookForProcessOffsets()
{
ULONG pid_ofs = 0; // The offset we're looking for
int idx = 0; // Index
ULONG pids[3]; // List of PIDs for our 3 processes
PEPROCESS eprocs[3]; // Process list, will contain 3 processes
//Select 3 process PIDs and get their EPROCESS Pointer
for (int i = 16; idx<3; i += 4)
{
if (NT_SUCCESS(PsLookupProcessByProcessId((HANDLE)i, &eprocs[idx])))
{
pids[idx] = i;
idx++;
}
}
/*
Go through the EPROCESS structure and look for the PID
we can start at 0x20 because UniqueProcessId should
not be in the first 0x20 bytes,
also we should stop after 0x300 bytes with no success
*/
for (int i = 0x20; i<0x300; i += 4)
{
if ((*(ULONG *)((UCHAR *)eprocs[0] + i) == pids[0])
&& (*(ULONG *)((UCHAR *)eprocs[1] + i) == pids[1])
&& (*(ULONG *)((UCHAR *)eprocs[2] + i) == pids[2]))
{
pid_ofs = i;
break;
}
}
ObDereferenceObject(eprocs[0]);
ObDereferenceObject(eprocs[1]);
ObDereferenceObject(eprocs[2]);
return pid_ofs;
}
// hides any process from the task bar and everything. only works for about 10 - 30 mins before patch guard is triggered and BSOD happens
PCHAR GhostProcess(UINT32 pid)
{
LPSTR result = ExAllocatePool(NonPagedPool, sizeof(ULONG) + 20);;
// Get PID offset nt!_EPROCESS.UniqueProcessId
ULONG PID_OFFSET = LookForProcessOffsets();
// Check if offset discovery was successful if (PID_OFFSET == 0) {
return (PCHAR)"Could not find PID offset!";
}
// Get LIST_ENTRY offset nt!_EPROCESS.ActiveProcessLinks
ULONG LIST_OFFSET = PID_OFFSET;
// Check Architecture using pointer size
INT_PTR ptr;
// Ptr size 8 if compiled for a 64-bit machine, 4 if compiled for 32-bit machine
LIST_OFFSET += sizeof(ptr);
// Get current process
PEPROCESS CurrentEPROCESS = PsGetCurrentProcess();
// Initialize other variables
PLIST_ENTRY CurrentList = (PLIST_ENTRY)((ULONG_PTR)CurrentEPROCESS + LIST_OFFSET);
PUINT32 CurrentPID = (PUINT32)((ULONG_PTR)CurrentEPROCESS + PID_OFFSET);
// Check self if (*(UINT32 *)CurrentPID == pid) {
RemoveTheLinks(CurrentList);
return (PCHAR)result;
}
// Record the starting position
PEPROCESS StartProcess = CurrentEPROCESS;
// Move to next item
CurrentEPROCESS = (PEPROCESS)((ULONG_PTR)CurrentList->Flink - LIST_OFFSET);
CurrentPID = (PUINT32)((ULONG_PTR)CurrentEPROCESS + PID_OFFSET);
CurrentList = (PLIST_ENTRY)((ULONG_PTR)CurrentEPROCESS + LIST_OFFSET);
// Loop until we find the right process to remove
// Or until we circle back
while ((ULONG_PTR)StartProcess != (ULONG_PTR)CurrentEPROCESS)
{
// Check item
if (*(UINT32 *)CurrentPID == pid) {
RemoveTheLinks(CurrentList);
return (PCHAR)result;
}
// Move to next item
CurrentEPROCESS = (PEPROCESS)((ULONG_PTR)CurrentList->Flink - LIST_OFFSET);
CurrentPID = (PUINT32)((ULONG_PTR)CurrentEPROCESS + PID_OFFSET);
CurrentList = (PLIST_ENTRY)((ULONG_PTR)CurrentEPROCESS + LIST_OFFSET);
}
return (PCHAR)result;
}
PDEVICE_OBJECT g_MyDevice; // Global pointer to our device object
NTSTATUS Create(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS Close(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
// Request to read from usermode
#define IO_READ_REQUEST CTL_CODE(FILE_DEVICE_UNKNOWN, 0x0701 /* Our Custom Code */, METHOD_BUFFERED, FILE_SPECIAL_ACCESS)
// Request to write virtual user memory (memory of a program) from kernel space
#define IO_UNLOADDRIVER_REQUEST CTL_CODE(FILE_DEVICE_UNKNOWN, 0x0702 /* Our Custom Code */, METHOD_BUFFERED, FILE_SPECIAL_ACCESS)
// datatype for read request
typedef struct _KERNEL_READ_REQUEST
{
ULONG CSGO;
ULONG LSASS;
ULONG CSRSS;
ULONG CSRSS2;
ULONG ProtectedThread;
ULONG TerminatePrograms;
} KERNEL_READ_REQUEST, *PKERNEL_READ_REQUEST;
// database for unload details
typedef struct _KERNEL_UNLOADDRIVER
{
ULONG UnloadDriver;
} KERNEL_UNLOADDRIVER, *PKERNEL_UNLOADDRIVER;
ULONG TerminateProcess = 0;
BOOLEAN UnloadDriver = FALSE;
NTSTATUS IoControl(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
NTSTATUS Status;
ULONG BytesIO = 0;
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
// Code received from user space
ULONG ControlCode = stack->Parameters.DeviceIoControl.IoControlCode;
if (ControlCode == IO_UNLOADDRIVER_REQUEST)
{
PKERNEL_UNLOADDRIVER ReadInput = (PKERNEL_UNLOADDRIVER)Irp->AssociatedIrp.SystemBuffer;
if (ReadInput->UnloadDriver)
{
UnloadDriver = TRUE;
}
}
if (ControlCode == IO_READ_REQUEST)
{
// Get the input buffer & format it to our struct
PKERNEL_READ_REQUEST ReadInput = (PKERNEL_READ_REQUEST)Irp->AssociatedIrp.SystemBuffer;
if (ReadInput->ProtectedThread != 0)
{
ProtectedThreadID = ReadInput->ProtectedThread;
}
if (ReadInput->CSGO != 0)
{
ProtectedProcess = ReadInput->CSGO;
}
if (ReadInput->LSASS != 0)
{
Lsass = ReadInput->LSASS;
}
if (ReadInput->CSRSS != 0)
{
Csrss1 = ReadInput->CSRSS;
}
if (ReadInput->CSRSS2 != 0)
{
Csrss2 = ReadInput->CSRSS2;
}
if (ReadInput->TerminatePrograms != 0)
{
TerminateProcess = ReadInput->TerminatePrograms;
}
Status = STATUS_SUCCESS;
BytesIO = sizeof(KERNEL_READ_REQUEST);
}
else
{
// if the code is unknown
Status = STATUS_INVALID_PARAMETER;
BytesIO = 0;
}
// Complete the request
Irp->IoStatus.Status = Status;
Irp->IoStatus.Information = BytesIO;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return Status;
}
NTSTATUS CallBackHandle = STATUS_SUCCESS;
// Enabling the callback,
VOID EnableCallBack()
{
OB_OPERATION_REGISTRATION OBOperationRegistration;
OB_CALLBACK_REGISTRATION OBOCallbackRegistration;
REG_CONTEXT regContext;
UNICODE_STRING usAltitude;
memset(&OBOperationRegistration, 0, sizeof(OB_OPERATION_REGISTRATION));
memset(&OBOCallbackRegistration, 0, sizeof(OB_CALLBACK_REGISTRATION));
memset(®Context, 0, sizeof(REG_CONTEXT));
regContext.ulIndex = 1;
regContext.Version = 120;
RtlInitUnicodeString(&usAltitude, L"1000");
if ((USHORT)ObGetFilterVersion() == OB_FLT_REGISTRATION_VERSION)
{
OBOperationRegistration.ObjectType = PsProcessType; // Use To Strip Handle Permissions For Threads PsThreadType
OBOperationRegistration.Operations = OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE;
OBOperationRegistration.PostOperation = PostCallBack; // Giving the function which happens after creating
OBOperationRegistration.PreOperation = PreCallback; // Giving the function which happens before creating
// Setting the altitude of the driver
OBOCallbackRegistration.Altitude = usAltitude;
OBOCallbackRegistration.OperationRegistration = &OBOperationRegistration;
OBOCallbackRegistration.RegistrationContext = ®Context;
OBOCallbackRegistration.Version = OB_FLT_REGISTRATION_VERSION;
OBOCallbackRegistration.OperationRegistrationCount = (USHORT)1;
CallBackHandle = ObRegisterCallbacks(&OBOCallbackRegistration, &ObHandle); // Register The CallBack
}
if (TerminateProcess != 0)
{
TerminatingProcess(TerminateProcess);
TerminateProcess = 0;
}
}
VOID MyLoadImageNotifyRoutine(
IN PUNICODE_STRING FullImageName,
IN HANDLE ProcessID,
IN PIMAGE_INFO iMAGEiNFO)
{
DbgPrintEx("Loaded Modules. ProcessID = %d, ThreadID = %d, Full ImageInfo = %d \n",
ProcessID, iMAGEiNFO, iMAGEiNFO);
}
VOID CreateThreadNotifyRoutine(
IN HANDLE ProcessId,
IN HANDLE ThreadId,
IN BOOLEAN Create
)
{
if (Create)
{
DbgPrintEx("Create Thread. ProcessID = %d, ThreadID = %d \n",
ProcessId, ThreadId);
}
else
{
DbgPrintEx("Delete Thread. ProcessID = %d, ThreadID = %d \n",
ProcessId, ThreadId);
}
}
// Driver's Main function. This will be called and looped through till returned, or unloaded.
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pUniStr)
{
DbgPrint("Loaded");
NTSTATUS NtRet = STATUS_SUCCESS;
PDEVICE_OBJECT pDeviceObj;
RtlInitUnicodeString(&SACDriverName, L"\\Device\\SACDriver1"); // Giving the driver a name
RtlInitUnicodeString(&SACSymbolName, L"\\DosDevices\\SACDriver1"); // Giving the driver a symbol
UNICODE_STRING deviceNameUnicodeString, deviceSymLinkUnicodeString;
NTSTATUS NtRet2 = IoCreateDevice(pDriverObject, 0, &SACDriverName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &pDeviceObj);
if (NtRet2 == STATUS_SUCCESS)
{
ULONG i;
for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
{
pDriverObject->MajorFunction[i] = DriverDispatchRoutine;
}
IoCreateSymbolicLink(&SACSymbolName, &SACDriverName);
pDriverObject->MajorFunction[IRP_MJ_CREATE] = Create;
pDriverObject->MajorFunction[IRP_MJ_CLOSE] = Close;
pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IoControl;
pDeviceObj->Flags |= DO_DIRECT_IO;
pDeviceObj->Flags &= (~DO_DEVICE_INITIALIZING);
}
else
{
return STATUS_UNSUCCESSFUL;
}
pDriverObject->DriverUnload = DriverUnload; // Telling the driver, this is the unload function
EnableCallBack();
NTSTATUS ThreadChecking;
ThreadChecking = PsSetCreateThreadNotifyRoutine(*CreateThreadNotifyRoutine);
NTSTATUS LoadChecking;
LoadChecking = PsSetLoadImageNotifyRoutine(*MyLoadImageNotifyRoutine);
return NtRet;
}
Usermode.cpp
vector GetPIDs(wstring targetProcessName)
{
vector pids;
if (targetProcessName == L"")
return pids;
HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32W entry;
entry.dwSize = sizeof entry;
if (!Process32FirstW(snap, &entry))
return pids;
do {
if (wstring(entry.szExeFile) == targetProcessName) {
pids.emplace_back(entry.th32ProcessID);
}
} while (Process32NextW(snap, &entry));
return pids;
}
// datatype for read request
typedef struct _KERNEL_READ_REQUEST
{
ULONG CSGO;
ULONG LSASS;
ULONG CSRSS;
ULONG CSRSS2;
ULONG UsermodeProgram;
ULONG TerminatePrograms;
} KERNEL_READ_REQUEST, *PKERNEL_READ_REQUEST;
// database for unload details
typedef struct _KERNEL_UNLOADDRIVER
{
ULONG UnloadDriver;
} KERNEL_UNLOADDRIVER, *PKERNEL_UNLOADDRIVER;
// Request to write to kernel mode
#define IO_READ_REQUEST CTL_CODE(FILE_DEVICE_UNKNOWN, 0x0701 /* Our Custom Code */, METHOD_BUFFERED, FILE_SPECIAL_ACCESS)
// Request to write virtual user memory (memory of a program) from kernel space
#define IO_UNLOADDRIVER_REQUEST CTL_CODE(FILE_DEVICE_UNKNOWN, 0x0702 /* Our Custom Code */, METHOD_BUFFERED, FILE_SPECIAL_ACCESS)
HANDLE hDriver;
bool SendProcessIDs(ULONG CSGO, ULONG LSASS, ULONG CSRSS, ULONG CSRSS2, ULONG USERMODEANTICHEAT, ULONG TerminatePrograms)
{
if (hDriver == INVALID_HANDLE_VALUE)
return false;
DWORD Return, Bytes;
KERNEL_READ_REQUEST ReadRequest;
ReadRequest.CSGO = CSGO;
ReadRequest.LSASS = LSASS;
ReadRequest.CSRSS = CSRSS;
ReadRequest.CSRSS2 = CSRSS2;
ReadRequest.UsermodeProgram = USERMODEANTICHEAT;
ReadRequest.TerminatePrograms = TerminatePrograms;
// send code to our driver with the arguments
if (DeviceIoControl(hDriver, IO_READ_REQUEST, &ReadRequest,
sizeof(ReadRequest), &ReadRequest, sizeof(ReadRequest), &Bytes, NULL))
{
return true;
}
else
{
return false;
}
}
#define MAX_PROCESSES 1024
DWORD FindProcessId(__in_z LPCTSTR lpcszFileName)
{
LPDWORD lpdwProcessIds;
LPTSTR lpszBaseName;
HANDLE hProcess;
DWORD i, cdwProcesses, dwProcessId = 0;
lpdwProcessIds = (LPDWORD)HeapAlloc(GetProcessHeap(), 0, MAX_PROCESSES * sizeof(DWORD));
if (lpdwProcessIds != NULL)
{
if (EnumProcesses(lpdwProcessIds, MAX_PROCESSES * sizeof(DWORD), &cdwProcesses))
{
lpszBaseName = (LPTSTR)HeapAlloc(GetProcessHeap(), 0, MAX_PATH * sizeof(TCHAR));
if (lpszBaseName != NULL)
{
cdwProcesses /= sizeof(DWORD);
for (i = 0; i < cdwProcesses; i++)
{
hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, lpdwProcessIds[i]);
if (hProcess != NULL)
{
if (GetModuleBaseName(hProcess, NULL, lpszBaseName, MAX_PATH) > 0)
{
if (!lstrcmpi(lpszBaseName, lpcszFileName))
{
dwProcessId = lpdwProcessIds[i];
CloseHandle(hProcess);
break;
}
}
CloseHandle(hProcess);
}
}
HeapFree(GetProcessHeap(), 0, (LPVOID)lpszBaseName);
}
}
HeapFree(GetProcessHeap(), 0, (LPVOID)lpdwProcessIds);
}
return dwProcessId;
}
int main(int argc, char *argv[])
{
HANDLE hDevice;
DWORD dwReturn;
DWORD ProcessId, write;
hDriver = CreateFileA("\\\\.\\SACDriver", GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
DWORD csrss1 = NULL;
DWORD csrss2 = NULL;
wstring we1 = L"";
wstring lsassNoStr1 = we1 +L'c' + L's' + L'r' + L's' + L's' + L'.' + L'e' + L'x' + L'e';
vector pidsLsass1 = GetPIDs(lsassNoStr1);
if (pidsLsass1.empty())
cout << "Not Found" << endl;
sort(pidsLsass1.begin(), pidsLsass1.end()); // In case there is several lsass.exe running (?) take the first one (based on PID)
csrss1 = pidsLsass1[0];
csrss2 = pidsLsass1[1];
if (!csrss1)
cout << "Not Found" << endl;
if (!csrss2)
cout << "Not Found" << endl;
DWORD pivotPID = NULL;
wstring we = L"";
wstring lsassNoStr = we + L'l' + L's' + L'a' + L's' + L's' + L'.' + L'e' + L'x' + L'e';
vector pidsLsass = GetPIDs(lsassNoStr);
if (pidsLsass.empty())
cout << "Not Found" << endl;
sort(pidsLsass.begin(), pidsLsass.end()); // In case there is several lsass.exe running (?) take the first one (based on PID)
pivotPID = pidsLsass[0];
if (!pivotPID)
cout << "Not Found" << endl;
if (SendProcessIDs(FindProcessId("csgo.exe"), pivotPID, csrss1, csrss2, (ULONG)GetCurrentProcessId(), 0)) {
cout << "Sent Data" << endl;
}
else
{
cout << "False" << endl;
}
}
|