RegeditSometimes it can be useful to determine what registry key belongs to a registry key handle. And exampling is when hooking RegQueryValue so you can determine the registry key that a value belongs to instead of having to track the registry key when it's opened.

To obtain the registry key we can pass a handle to the NtQueryKey API with which has the following signature:

text Download
NTSYSAPI NTSTATUS NtQueryKey(
  [in]            HANDLE                KeyHandle,
  [in]            KEY_INFORMATION_CLASS KeyInformationClass,
  [out, optional] PVOID                 KeyInformation,
  [in]            ULONG                 Length,
  [out]           PULONG                ResultLength
);
text Download
[DllImport("ntdll.dll", EntryPoint="NtQueryKey", SetLastError=false)]
private static extern UInt32 NtQueryKey(IntPtr KeyHandle, KEY_INFORMATION_CLASS KeyInformationClass, IntPtr KeyInformation, UInt32 Length, ref UInt32 ResultLength);

To get the keyname we need to pass the KeyNameInformation enum and we can set the ReturnLength parameter to 0 to obtain the required buffer size:

text Download
UInt32 nts;
UInt32 resLen = 0;
IntPtr buffer = IntPtr.Zero;
nts = NtQueryKey(KeyHandle, KEY_INFORMATION_CLASS.KeyNameInformation, IntPtr.Zero, 0, ref resLen);

Then we call NtQueryKey again passing an allocated buffer (of type KEY_NAME_INFORMATION). However in a managed language like C# there is not really good way to define an "ANYSIZE" array in a struct. So let's just pass a pointer:

text Download
// allocate memory for the buffer
buffer = Marshal.AllocHGlobal((int)resLen);
nts = NtQueryKey(KeyHandle, KEY_INFORMATION_CLASS.KeyNameInformation, buffer, resLen, ref resLen);

And extract the NameLength and Name string like this:

text Download
// read name length from the buffer
int nameLen = Marshal.ReadInt32(buffer);
// name is an c-style "ANYSIZE" char array, as we don't know the size in advance we cannot use
// PtrToStructure, so we get an IntPtr pointing to the char array
IntPtr name = new IntPtr(buffer.ToInt64() + sizeof(UInt32));
// and Marshal it to string
result = Marshal.PtrToStringUni(name, nameLen / sizeof(char));

However the returned names are in a different format than what you might expect. HKLM keys are returned as \REGISTRY\MACHINE\KEY and HKCU as \REGISTRY\USER\SID\KEY so we use the following logic to alter the string into "normal" format:

text Download
if (result.Contains("\\REGISTRY\\MACHINE"))
{
   result = result.Replace("\\REGISTRY\\MACHINE", "HKLM");
}
else
{
   using (WindowsIdentity wi = WindowsIdentity.GetCurrent())
   {
      if (result.Contains(wi.User.Value))
      {
         result = result.Replace("\\REGISTRY\\USER\\" + wi.User.Value, "HKCU");
      }
   }
}

The complete code and a sample how to use can be found on my github repo.