Disclaimer: All experiments and development were performed on Windows 10 x64 (Version 1703, Build 15063.540). Any attempt to replicate results on a different version or operating system may yield inconsistent results.
—–
It’s a pretty common objective in the game hacking community to load and attempt to hide their kernel modules from prying eyes. The majority of methods leave artifacts, or cause bugchecks at random times which makes for an unreliable evasion of an anti-cheat.
Let’s cover the details of driver loading, initialization, and what happens underneath the hood. That way when we detail the techniques used to remove entries from protected resources, hide initialization, and bypass certain verification flags it all makes sense.
Flow of NtLoadDriver
Important NT Globals
PsLoadedModuleList
A global array of pointers to currently loaded kernel modules on the given system. This list is protected by PatchGuard and cannot be directly modified without triggering a bugcheck. All entries in this list are of type _KLDR_DATA_TABLE_ENTRY.
PspNotifyEnableMask
This is a 32-bit integer mask that is used to determine what types of callbacks will be called depending on bits set in the lower byte of the mask. Bits 0, 1, and 3 determine if CreateProcess, CreateThread, and LoadImage callbacks are fired. This variable is not protected by PatchGuard and is subject to modification.
MmVerifierData
Global array of driver information stored as _MI_VERIFIER_DRIVER_ENTRY by the routine MiApplyDriverVerifier. This data is stored so that NTOS can verify that all drivers are valid and able to load. This is a simplified explanation of it’s use because it’s all we’ll need to know for what we’ll be doing.
Abusing Globals
There’s a simple way to allow process notification callbacks to be registered, but never fire. I’m sure after reading the purpose of the mask above and the bits which determine functionality you’re aware of what to do now.
A simple zero’ing of the PspNotifyEnableMask will prevent all process notification callbacks from firing without tampering with the callbacks themselves.
*( ULONG * )( PspNotifyEnableMask ) = 0; // Game over for process notification callbacks
The verifier data array will require you to traverse the array and find your drivers base name, once you’ve found the index of the entry in MmVerifierData, remove it, shift all entries down an index, and move along to the next global commonly accessed for driver information: PsLoadedModuleList.
There are various ways to remove loaded driver information from globals, the most effective approach I’ve used was simply taking advantage of an already existing routine in the kernel, MiProcessLoaderEntry. This routine is called as part of MmLoadSystemImageEx and is responsible for inserting the loader entry (or removing it) from the PsLoadedModuleList. If a developer’s objective is to remove the entry from the global array then all he/she has to do is call MiProcessLoaderEntry the following way:
MiProcessLoaderEntry( ( PKLDR_DATA_TABLE_ENTRY )( DriverLdrEntry ), FALSE );
The second parameter of MiProcessLoaderEntry determines if the entry will be added or removed from the PsLoadedModuleList.
There are other areas that may contain artifacts of driver loading or initialization. To completely hide it would be wise to seek them out and remove any traces. However, most kernel AC’s are not that advanced and only utilize the common kernel structures that are supposed to be unmodifiable. To hide in a more complete manner simply destroy driver object features by simply NULL’ing the following DriverObject fields:
- DriveSection
- DriverStart
- DriverUnload
- DriverInit
- DeviceObject
Note: NULL’ing specific driver object fields can result in system instability. Primarily zeroing the DriverSection field because it will cause an exception handling issue as well as prevent loads of system routines from working.
There are plenty of other exploits that I’ll be writing about since I’ve picked back up on research and development. There’s a new one that seems to be present on all editions from Windows 7 and up, it allows for a specific override of an otherwise important field for all drivers on a machine allowing modification of other drivers at any level. I’m excited to write an article on it and hope it will prove useful to other driver authors.
Bonus: MmUnloadedDrivers
Though not new or unheard of at this point since it was publicized on a forum less than a week ago I had been removing entries from the global MmUnloadedDrivers which is an array of base driver names that have since been unloaded from the system. In the event you have unloaded a driver from your system that may trigger an anti-cheats system integrity checks (TDL, specifically) you iterate the unloaded drivers array and perform a search for the target driver name, remove the entry at the specific index, and shift all entries down an index which makes it appear as if it was never there.
for (i = 0; i < MI_UNLOADED_DRIVERS; i += 1) { if (Index >= MI_UNLOADED_DRIVERS) { Index = MI_UNLOADED_DRIVERS - 1; } Entry = &MmUnloadedDrivers[ Index ]; if ( !wcscmp( Entry->Name.Buffer, MmUnloadedDrivers[ Index ].Buffer ) ) { // remove entry from MmUnloadedDrivers } } }
Other Detection Vectors
There are tons of other detection vectors (though unreliable) that may be used by kernel anti-cheats. The following list is a few I’ve thought of and will likely do testing on in the future.
- ObjectDirectory
- ServiceDatabase
- Registry Service Database
- Signatures
- Pool-tag scanning
- High entropy symbol blocks
As always leave a comment, question, or feedback! I’m always interested in new ideas or thoughts on a topic.
9 thoughts on “Hiding Drivers on Windows 10”
hi, this leaves a trace in the object_directory, is this still scanned by PG because DKOM could erase this. the main other threat is pool tag scanning which can be nulled.
or just: ExAllocatePoolWithTag(NonPagedPool, 4096, GET_POOL_TAG());
how i can get all pool tags with tag "Driv"?
In cmd(admin) – verifier /reset and MmVerifierData will be nulled
Yes because that solves everything… lol
don’t forget about PiDDBCache
It wasn’t used back when this article was written. You also need to be aware of ExpCovUnloadedDrivers as well now.
hello,
Could I translate your posts to Chinese and post them on my blog?I will post with original url and your name. I have translated this article and posted. Here is the url: https://darc.pro/archives/hide-driver-on-windows10.html . If your don’t want me to do that, I will delete it ASAP. BTW, thanks your article, they are really awesome : ).
And, I can’t found any information about ExpCovUnloadedDrivers on Google, could you please to tell me where can I found it? Thank you in advance.
Please don’t angry about my poor English qwq.
Sure, as long as you credit back to the original posts I don’t have an issue with it 🙂
As for ExpCovUnloadedDrivers – it’s undocumented. It can be found in NTOSKRNL.