The team I work with has recently been faced with the challenge of making our software compatible with a third party peice of virtualization software. This software uses a kernel driver to perform hooking of the Windows native registry API (ZwCreateKey, etc). It works by hooking the calls in Ntdll. Our software is also fairly low level and in some contexts requires access to the real registry without being hooked.
We’re exploring the possibility of using our own kernel driver to call ZwCreateKey, etc, on our behalf circumventing their hooking. This essentially has meant creating an NT Legacy driver and a user mode library which provides our own native registry functions. The library and driver are very simple, we simply use an IOCTL to pass all the parameters for ZwCreateKey, etc into our driver then we call the kernel version of the call and return the results.
The approach has worked well, and we appear to now have a system of reading/writing to the real registry when virtualized. The only problem is that our new system appears to circument Windows security on registry objects.
ZwCreateKey takes an access mask like so:
NTSTATUS ZwCreateKey(
__out PHANDLE KeyHandle,
__in ACCESS_MASK DesiredAccess,
__in POBJECT_ATTRIBUTES ObjectAttributes,
__reserved ULONG TitleIndex,
__in_opt PUNICODE_STRING Class,
__in ULONG CreateOptions,
__out_opt PULONG Disposition
);
My understanding was that although we were now running in kernel mode, we still had the context of the user’s token. This should mean that the kernel version of ZwCreateKey will fail just as the user one would have if the access mask test fails. What’s actually happening is that even with a limited token, when our driver is called, it’s able to create keys in restricted parts HKLM when invoked by a limited user. What gives? Should we be performing the ACL checks ourselves? Do we need to do something to limit our own privileges in kernel mode? Any help much appreciated.
Check this for explanation. Basically Nt/Zw in User-mode (ntdll) are the same thing – they first perform extensive checks before actually performing the action. Where as when calling Zw functions from kernel-mode ( as is the case with a device driver) those checks are ommitted because it is assumed information coming from kernel-mode component (e.g. a driver) is to be trusted by default