In this blog, we will discuss two ways to stop most drivers from loading if the workstation is restarted.
20 September 2024 | Prepared by: Elliot Xu, Offensive Consultant
Red team engagements usually span a long time, so there’s no need to hurry anything and get busted. If we can disable any Endpoint Detection and Response (EDR) drivers from loading, we should be patient and take this opportunity.
In this blog, we will discuss two ways to stop most drivers from loading if the host can be restarted, including those of famous EDRs.
To do this, we must first compromise an account with admin privileges, be it a service account or any local admin account. We can then write to “system32” directory and “HKLM” registry.
With these powers, we will utilise what is called a Windows Native Application to either change the file content of the driver or change the driver settings in the registry, both preventing the driver from loading.
If we have our persistence mechanism set in the target host correctly, all we need to do is wait for the host to reboot. The next time we get a connection back, it’s clean without unwanted drivers loaded. Let’s dive in.
In one sentence, a native application is a kind of application that can be invoked by the Windows system during boot up time. Additionally, we’ll share one of the many good resources from Pavel Yosifovich about native applications.
Simply put, what we care about is that native applications start very early in the system boot process, and because all native applications are going to be launched by Session Manager Subsystem (smss.exe), which is one of the very first processes spawned during system startup, native applications are going to run under “SYSTEM” privilege. That being said, one can instantly smell the juicy (ab)use of this kind of application.
Now, a couple of things we must first clear up:
To unveil the first mystery, we have to know that native applications are configured in the following registry key:
HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\BootExecute, one application per line.
By default, we can see “autochk.exe” is another native application that belongs to the native subsystem and is responsible for performing file system consistently checks and repairs during the system startup process.
Autochk Native Application
Therefore, one of the steps we must do is to add the name of our native application to this "BootExecute" registry key.
For example, suppose our native application is called “native_app.exe”, then we append the filename on the second line of the registry key, without the extension. Just like this:
Add Our Own Native Application
As for the second question, on system boot, smss will look at the “BootExecute” key, then try to find each file under the “system32” directory.
That’s the other thing we must remember: to put the native application in the “systerm32” directory. Next, let’s get to the core of this whole thing.
The logic should be apparent, as mentioned at the beginning of this blog.
In the first method, we will overwrite the target driver with another file using a native application. Easy right? Not entirely.
Here comes another piece of uniqueness about the native application, which is that it only depends on “ntdll.dll”.
This means we cannot use any APIs from DLLs other than “ntdll.dll”, and there are no standard libraries, so there are no functions like “printf”, “malloc”, etc.
But that shouldn’t pose any big problems because most APIs from “ntdll” are well documented. And we can always refer to the following links for extra references:
http://undocumented.ntinternals.net/
https://www.geoffchappell.com/
Let’s get started with the first option - overwrite the target driver.
As discussed, only “Nt” APIs are allowed here; some of the APIs we’re going to use are:
NtCreateFile
NtReadFile
NtWriteFile
NtAllocateVirtualMemory
NtFreeVirtualMemory
So basically, open the source file, which can be any file, then open the destination file, that’s the target driver file that you want to overwrite; finally, read from source file and overwrite the destination file.
Compile and drop the binary in “system32” directory, then if the host ever reboots, the driver file will be overwritten, and it won’t load.
Let’s take the original procexp.sys file (from procexp64.exe) for testing. It’s currently loaded into the system and is configured to start automatically on system boot.
Driver Set to Auto Start
This is how it looks like under hex editor. Note that the highlighted offset points to the end of the file.
Target Driver Hex View
We are going to overwrite the procexp driver with our own driver (not Microsoft signed), called “my_driver.sys”. Also note the highlighted offset in the screenshot.
Our Own Driver Hex View
We can see the size of our driver is much smaller than procexp. This is when the host reboots, and our native application fires. The information printed on the screen is for demonstration purposes only.
Successfully Overwrite Target Driver
This is how it looks like after the original procexp.sys is overwritten by our own driver. First, the digital signature tab in properties is gone, indicating that the file has been tampered with, and Microsoft Authenticode just failed the signature.
Target Driver with Bad Signature
Second, using a hex editor to compare two files shows the following, indicating that the smaller file has been copied into procexp.sys.
Hex View of Our Own Driver Copied into Target Driver
And because procexp.sys is essentially corrupted, the driver service cannot start.
Target Driver Stopped Working
As powerful as that. Bear in mind this technique will potentially not work on those file system drivers (type 2), because they are loaded earlier than native applications; as such, even if we were able to overwrite the content of those driver files, it’s of no help because they have already been loaded into memory.
Now, let’s look at another way to achieve the same result.
The second method is to use “Nt” APIs to change the registry settings of the target service, thus disabling the driver from loading.
This could be modifying the “ImagePath” key to point to a file that doesn’t exist or changing the “Start” key to “manual start (3)”, so the driver won’t load automatically on startup.
Enough said, some of the APIs we are going to use are:
NtOpenKey
NtSetValueKey
I chose to modify the “Start” key to value “3” (manual start) instead of “2” (auto start); then, if the host reboots, the driver won’t be loaded automatically.
This is the original registry setting for procexp.sys driver.
Target Driver Registry Settings
After the host reboots, our native application successfully modified the key value.
Target Driver Registry Settings Modified
And this is the modified registry value, resulting in the driver not being loaded automatically on system startup.
New Target Driver Registry Settings Confirmed
Target Driver Stopped Working
During testing, some of the things to bear in mind when you do this:
1. You cannot overwrite the target driver file with any file content, like “ntdll.dll”, because “ntdll.dll” is a valid PE and is Microsoft signed, it has a valid signature, and Service Manager will load it and try to execute it. However, because Service Manager loads it into kernel memory space and when execution reaches a non-executable memory page, you’ll get an instant Blue Screen of Death (BSOD), FOREVER!
2. To address the above problem, you can use other drivers, even the ones you build, to overwrite it, so there will be no access violation issue.
3. If your driver is smaller than the target driver, then error “193” will occur when trying to start the driver service because when you overwrite the target driver, it will result in garbage data trailing the target driver file after your driver content length.
4. If your driver is larger than the target driver, remember that if it’s not signed correctly, it won’t be loaded because of signature failure. But anyway, the goal is attained.
All that being said, the desirable OPSEC way to do this is to use a larger and legitimate Microsoft signed driver, to overwrite the target driver; then it will load normally, but the original function is still disabled.
The features of native applications makes them a desirable way to carry out malicious activities. Other than what we’ve talked about here, there are a lot of different things we can do with it. Just use your imagination.