Friday 14 July 2017

Locking Your Registry Keys for Fun and, Well, Just Fun I Guess

Let's assume you have some super important registry keys that you don't want anyone to modify or delete, how might you do it? One way is to change the security descriptor of the registry key to prevent others modifying it. However when combined with a kernel driver (such as AV) or an admin with sufficient privilege this can be trivially bypassed. Another common trick is to embed NUL characters into the key or value names you want to protect. That trick tends to break Win32 API users such as typical registry editors as the Win32 APIs use NUL terminated strings where as the kernel APIs do not. However that won't stop someone more persistent. The final trick I can think of would be to write a kernel driver which uses the "Registry Filter Callback" APIs to block access to the keys, however Microsoft are making it as hard as possible to run arbitrary kernel code (well legit kernel code anyway) so writing a driver sounds like an unnecessary extravagance.

So what can we do? Turns out there is an API in the kernel called NtLockRegistryKey. Though unsurprisingly it isn't documented. It's pretty obvious what the parameters are though based on 2 seconds of RE, it looks like the following:

NTSTATUS NtLockRegistryKey(HANDLE KeyHandle);

All the system call takes is a registry key handle, adding it to my NtObjectManager Powershell module we can call it and see what we happens:

Apparently we don't have a privilege enabled to lock the key. Turns out no matter what privilege you enable it still doesn't work, something odd is going on. Let's look back at the kernel to see if we can find the privilege we require:

NTSTATUS NtLockRegistryKey(HANDLE Handle) { if (KeGetCurrentThread()->PreviousMode != KernelMode) return STATUS_PRIVILEGE_NOT_HELD; .... }

Apparently from this code the privilege we need is to be running in the kernel! If we need that then we might as well just write that registry filter driver. Still all is not necessarily lost, perhaps there's some code in the kernel which calls this function to lock existing registry keys we can abuse? You might also assume this could also be called by a kernel driver, but neither the Zw or Nt versions of the system call are exported by NTOSKRNL, making it very unlikely any legitimate driver will call it. A quick XREF check in IDA shows exactly one caller, NtLockProductActivationKeys. Why am I not surprised it's used for DRM purposes?

Anyway, let's do a quick bit of RE on the kernel function to work out what it's doing with registry keys.

NTSTATUS NtLockProductActivationKeys() { HANDLE RootKeyHandle; UNICODE_STRING RootKey; OBJECT_ATTRIBUTES ObjectAttributes; // Initial path is obfuscated in the kernel. RtlInitUnicodeString(&RootKey, L"\\Registry\\Machine\\System\\WPA"); InitializeObjectAttributes(&ObjectAttributes, &RootKey, ...); NTSTATUS status ZwOpenKey(&RootKeyHandle, KEY_READ, &ObjectAttributes); if (NT_SUCCESS(status)) { ULONG KeyIndex = 0; PKEY_BASIC_INFORMATION KeyInfo = // Allocate a buffer... while(ZwEnumerateKey(KeyHandle, KeyIndex, KeyBasicInformation, KeyInfo) != STATUS_NO_MORE_ENTRIES) { HANDLE SubKeyHandle = OpenKey(RootKeyHandle, KeyInfo->Name); if (!IsRegistryKeyLocked(SubKeyHandle)) { ZwLockRegistryKey(SubKeyHandle); } ZwClose(Handle); ++KeyIndex; } } ZwClose(KeyHandle); return last_status; }

All this code does is open a root key, the "HKLM\System\WPA" key, although if you look at the actual code the key name is "obfuscated" in the binary. Thanks DRM you so crazy. It then enumerates and opens any sub-keys and calls ZwLockRegistryKey on each key handle. There seems to be no other checks being done for privileges so this should be callable from user mode, and because the Zw version of the lock key system call is used that will set the previous mode to Kernel satisfying the security check requirements.

At this point we don't even know if locking a registry key does anything useful. However we now know that sub-keys under the WPA registry key are locked, let's try and write a value to one of the sub-keys of the WPA key as an administrator.

So that's pretty much what we expected, even though we've got SetValue access to the key trying to set a value fails with STATUS_ACCESS_DENIED. If you look back at how NtLockProductActivationKeys works you might notice that the kernel doesn't lock the WPA key, but only enumerated sub-keys. Therefore there's nothing stopping us creating a new sub-key under WPA, re-running NtLockProductActivationKeys and getting a locked key.

So the fun thing about this locking function is it seems to not just block user mode but also kernel mode callers from modifying the key. There doesn't seem to be a public way of unlocking the key again (DRM remember). There's no ZwUnlockRegistryKey function and the only way of doing anything would be to grovel in the innards of the Key Control Block to unlock it which Microsoft have been trying desperately to discourage. The lock prevents any modification and also deleting the key itself (however the function doesn't automatically lock sub-keys). The only way to clear the locked state is unload the hive (tricky if a system hive) or reboot. However the kernel calls NtLockProductActivationKeys very early in the boot process *sigh* DRM *sigh* so it's pretty tricky to find a time when you can just delete the key.

While writing to the WPA location might be okay just to store some configuration, probably what you want is persistence in another location. Fortunately if you check out the implementation you'll find that it doesn't take into account registry key symbolic links. So to protect other parts of the registry just create a sub-key which actually is a symbolic link which points to a key you want to protect. The re-run NtLockProductActivationKeys and it will now be locked. If you stick to the SYSTEM  hive (such as a service registration) this should even get protected at boot time. Of course the weakness of this is the symbolic link key in WPA doesn't actually get protected itself, so you can delete the symbolic link and on next boot the protection will go away. Of course this doesn't work in most registry tools which blindly follow symbolic links ;-)

Anyway, this is a fun, if somewhat silly feature. I can understand why it's not exposed to user-mode callers but I've literally no idea why it's a system call at all. The strange things DRM does to peoples' minds.