21

Given a handle to a Windows Registry Key, such as the ones that are set by ::RegOpenKeyEx(), is it possible to determine the full path to that key?

I realize that in a simple application all you have to do is look up 5 or 10 lines and read... but in a complex app like the one I'm debugging, the key I'm interested in can be opened from a series of calls.

Brian Gillespie
  • 3,213
  • 5
  • 27
  • 37

6 Answers6

34

Use LoadLibrary and NtQueryKey exported function as in the following code snippet.

#include <windows.h>
#include <string>

typedef LONG NTSTATUS;

#ifndef STATUS_SUCCESS
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
#endif

#ifndef STATUS_BUFFER_TOO_SMALL
#define STATUS_BUFFER_TOO_SMALL ((NTSTATUS)0xC0000023L)
#endif

std::wstring GetKeyPathFromKKEY(HKEY key)
{
    std::wstring keyPath;
    if (key != NULL)
    {
        HMODULE dll = LoadLibrary(L"ntdll.dll");
        if (dll != NULL) {
            typedef DWORD (__stdcall *NtQueryKeyType)(
                HANDLE  KeyHandle,
                int KeyInformationClass,
                PVOID  KeyInformation,
                ULONG  Length,
                PULONG  ResultLength);

            NtQueryKeyType func = reinterpret_cast<NtQueryKeyType>(::GetProcAddress(dll, "NtQueryKey"));

            if (func != NULL) {
                DWORD size = 0;
                DWORD result = 0;
                result = func(key, 3, 0, 0, &size);
                if (result == STATUS_BUFFER_TOO_SMALL)
                {
                    size = size + 2;
                    wchar_t* buffer = new (std::nothrow) wchar_t[size/sizeof(wchar_t)]; // size is in bytes
                    if (buffer != NULL)
                    {
                        result = func(key, 3, buffer, size, &size);
                        if (result == STATUS_SUCCESS)
                        {
                            buffer[size / sizeof(wchar_t)] = L'\0';
                            keyPath = std::wstring(buffer + 2);
                        }

                        delete[] buffer;
                    }
                }
            }

            FreeLibrary(dll);
        }
    }
    return keyPath;
}

int _tmain(int argc, _TCHAR* argv[])
{
    HKEY key = NULL;
    LONG ret = ERROR_SUCCESS;

    ret = RegOpenKey(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft", &key);
    if (ret == ERROR_SUCCESS)
    {
        wprintf_s(L"Key path for %p is '%s'.", key, GetKeyPathFromKKEY(key).c_str());    
        RegCloseKey(key);
    }

    return 0;
}

This will print the key path on the console:

Key path for 00000FDC is '\REGISTRY\MACHINE\SOFTWARE\Microsoft'.

Naszta
  • 7,560
  • 2
  • 33
  • 49
Jorge Ferreira
  • 96,051
  • 25
  • 122
  • 132
  • Awesome, I was looking Exactly that! – Hernán Dec 09 '10 at 20:58
  • [ZwQueryKey in MSDN](http://msdn.microsoft.com/en-us/library/ff567060(v=vs.85).aspx). – Naszta Aug 09 '11 at 09:26
  • One more thing: in an application (in user mode) you should use `NtQueryKey` instead of `ZwQueryKey`. – Naszta Oct 11 '11 at 15:56
  • Since this is [tag:c++], you could make `buffer` a `std::vector` and avoid the verbose and error-prine `new[]/delete[]` pair. You can use `&buffer[0]` in the call to `func` to get a raw pointer to the beginning of the buffer. – Frerich Raabe Dec 16 '13 at 12:18
  • 1
    Sadly, running Windows 7, my ntdll.dll does not have an entry point for NtQueryKeyType. – Jesse Chisholm Dec 15 '14 at 22:19
  • @JesseChisholm `NtQueryKey` is the name of the export. The author of the above function chose `NtQueryKeyType` as the name for it's typedef. [I realize this comment is very old.] – Hydranix Sep 15 '16 at 17:50
  • @Hydranix Yes, typo on my part, `entry point` that can't be found is `NtQueryKey`. In any case, this code doesn't work with my Windows-7 `ntdll.dll`. – Jesse Chisholm Sep 17 '16 at 14:05
  • @JesseChisholm, late comment but what error do you get? I got it to work on Win7 (x64) – 244an Apr 11 '17 at 22:29
  • @244an - I no longer have a Win-7 system to retest on. I presume from the above comments about the edit to the code that it was the original typo in the call to `GetProcAddress` that I didn't notice at the time, and has since been corrected. – Jesse Chisholm Jul 07 '17 at 16:51
  • FWIW the code above works perfectly with Windows 10 FCU and a 64-bit build. Haven't tried anything else, but it did the job for some debugging. – Leo Davidson Nov 01 '17 at 17:51
  • 1
    it is not necessary to construct a std::wstring with null-terminated string, just from a buffer and the count of charactors. so ```size = size + 2;``` and ```buffer[size / sizeof(wchar_t)] = L'\0';``` may be removed. – xenophōn Aug 05 '22 at 06:55
2

I was excited to find this article and its well liked solution. Until I found that my system's NTDLL.DLL did not have NtQueryKeyType.

After some hunting around, I ran across ZwQueryKey in the DDK forums.

It is in C#, but here is the solution that works for me:

enum KEY_INFORMATION_CLASS
{
    KeyBasicInformation,            // A KEY_BASIC_INFORMATION structure is supplied.
    KeyNodeInformation,             // A KEY_NODE_INFORMATION structure is supplied.
    KeyFullInformation,             // A KEY_FULL_INFORMATION structure is supplied.
    KeyNameInformation,             // A KEY_NAME_INFORMATION structure is supplied.
    KeyCachedInformation,           // A KEY_CACHED_INFORMATION structure is supplied.
    KeyFlagsInformation,            // Reserved for system use.
    KeyVirtualizationInformation,   // A KEY_VIRTUALIZATION_INFORMATION structure is supplied.
    KeyHandleTagsInformation,       // Reserved for system use.
    MaxKeyInfoClass                 // The maximum value in this enumeration type.
}
[StructLayout(LayoutKind.Sequential)]
public struct KEY_NAME_INFORMATION
{
    public UInt32 NameLength;     // The size, in bytes, of the key name string in the Name array.
    public char[] Name;           // An array of wide characters that contains the name of the key.
                                  // This character string is not null-terminated.
                                  // Only the first element in this array is included in the
                                  //    KEY_NAME_INFORMATION structure definition.
                                  //    The storage for the remaining elements in the array immediately
                                  //    follows this element.
}

[DllImport("ntdll.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern int ZwQueryKey(IntPtr hKey, KEY_INFORMATION_CLASS KeyInformationClass, IntPtr lpKeyInformation, int Length, out int ResultLength);

public static String GetHKeyName(IntPtr hKey)
{
    String result = String.Empty;
    IntPtr pKNI = IntPtr.Zero;

    int needed = 0;
    int status = ZwQueryKey(hKey, KEY_INFORMATION_CLASS.KeyNameInformation, IntPtr.Zero, 0, out needed);
    if ((UInt32)status == 0xC0000023)   // STATUS_BUFFER_TOO_SMALL
    {
        pKNI = Marshal.AllocHGlobal(sizeof(UInt32) + needed + 4 /*paranoia*/);
        status = ZwQueryKey(hKey, KEY_INFORMATION_CLASS.KeyNameInformation, pKNI, needed, out needed);
        if (status == 0)    // STATUS_SUCCESS
        {
            char[] bytes = new char[2 + needed + 2];
            Marshal.Copy(pKNI, bytes, 0, needed);
            // startIndex == 2  skips the NameLength field of the structure (2 chars == 4 bytes)
            // needed/2         reduces value from bytes to chars
            //  needed/2 - 2    reduces length to not include the NameLength
            result = new String(bytes, 2, (needed/2)-2);
        }
    }
    Marshal.FreeHGlobal(pKNI);
    return result;
}

I've only ever tried it while running as Administrator, which may be required.

The result is a bit oddly formatted: \REGISTRY\MACHINE\SOFTWARE\company\product for example, instead of HKEY_LOCAL_MACHINE\SOFTWARE\company\product.

Jesse Chisholm
  • 3,857
  • 1
  • 35
  • 29
  • 1
    As one of the pre-defined `HKEY`s, `HKEY_LOCAL_MACHINE` is a virtual key handle (aka `HKEY`). The user mode API, like `RegOpenKey`, map the pre-defined `HKEY`s to the absolute registry key path in kernel mode, like `\REGISTRY\MACHINE`. See the registry path `HKLM\SYSTEM\CurrentControlSet\Control\hivelist` for a full list. – xenophōn Aug 20 '19 at 02:04
1

Since std::wstring allows to construct string from pointer and count of characters, and the kernel string always return the count of bytes, it is not necessary to terminated the string with NUL. I do not suggest that to add size or to offset the pointer by constant number directly, it's better to use the real data type like the structure types instead, and std::vector<UCHAR> instead of new for dynamic memory allocating. I modified the code from highly upvoted answer as the followings.

The legacy way, obtaining the function pointer from ntdll.dll dynamically:

#include <ntstatus.h>
#define WIN32_NO_STATUS
#include <windows.h>
#include <winternl.h>
#include <string>
#include <vector>

#define REG_KEY_PATH_LENGTH 1024

typedef enum _KEY_INFORMATION_CLASS {
    KeyBasicInformation,
    KeyNodeInformation,
    KeyFullInformation,
    KeyNameInformation,
    KeyCachedInformation,
    KeyFlagsInformation,
    KeyVirtualizationInformation,
    KeyHandleTagsInformation,
    KeyTrustInformation,
    KeyLayerInformation,
    MaxKeyInfoClass
} KEY_INFORMATION_CLASS;

typedef struct _KEY_NAME_INFORMATION {
    ULONG NameLength;
    WCHAR Name[1];
} KEY_NAME_INFORMATION, *PKEY_NAME_INFORMATION;

typedef NTSTATUS (NTAPI *PFN_NtQueryKey)(
    __in        HANDLE  /* KeyHandle */,
    __in        KEY_INFORMATION_CLASS /* KeyInformationClass */,
    __out_opt   PVOID   /* KeyInformation */,
    __in        ULONG   /* Length */,
    __out       ULONG * /* ResultLength */
);

std::wstring RegQueryKeyPath(HKEY hKey)
{
    std::wstring keyPath;

    if (hKey != NULL)
    {
        HMODULE hinstDLL = GetModuleHandleW(L"ntdll.dll");
        if (hinstDLL != NULL)
        {
            FARPROC pfn = GetProcAddress(hinstDLL, "NtQueryKey");
            if (pfn != NULL)
            {
                NTSTATUS Status;

                std::vector<UCHAR> Buffer(FIELD_OFFSET(KEY_NAME_INFORMATION, Name) + sizeof(WCHAR) * REG_KEY_PATH_LENGTH);
                KEY_NAME_INFORMATION *pkni;
                ULONG Length;

            TryAgain:
                Status = reinterpret_cast<PFN_NtQueryKey>(pfn)(hKey, KeyNameInformation, Buffer.data(), Buffer.size(), &Length);
                switch (Status) {
                case STATUS_BUFFER_TOO_SMALL:
                case STATUS_BUFFER_OVERFLOW:
                    Buffer.resize(Length);
                    goto TryAgain;
                case STATUS_SUCCESS:
                    pkni = reinterpret_cast<KEY_NAME_INFORMATION *>(Buffer.data());
                    keyPath.assign(pkni->Name, pkni->NameLength / sizeof(WCHAR));
                default:
                    break;
                }
            }
        }
    }

    return keyPath;
}

If you are using Visual Studio 2015 or above, ntdll.lib is included by default, so I suggest that linking to ntdll.dll statically:

#include <ntstatus.h>
#define WIN32_NO_STATUS
#include <windows.h>
#include <winternl.h>
#pragma comment(lib, "ntdll")
#include <string>
#include <vector>

#define REG_KEY_PATH_LENGTH 1024

typedef enum _KEY_INFORMATION_CLASS {
    KeyBasicInformation,
    KeyNodeInformation,
    KeyFullInformation,
    KeyNameInformation,
    KeyCachedInformation,
    KeyFlagsInformation,
    KeyVirtualizationInformation,
    KeyHandleTagsInformation,
    KeyTrustInformation,
    KeyLayerInformation,
    MaxKeyInfoClass
} KEY_INFORMATION_CLASS;

typedef struct _KEY_NAME_INFORMATION {
    ULONG NameLength;
    WCHAR Name[1];
} KEY_NAME_INFORMATION, *PKEY_NAME_INFORMATION;

EXTERN_C NTSYSAPI NTSTATUS NTAPI NtQueryKey(
    __in        HANDLE  /* KeyHandle */,
    __in        KEY_INFORMATION_CLASS /* KeyInformationClass */,
    __out_opt   PVOID   /* KeyInformation */,
    __in        ULONG   /* Length */,
    __out       ULONG * /* ResultLength */
);

std::wstring RegQueryKeyPath(HKEY hKey)
{
    std::wstring keyPath;

    NTSTATUS Status;

    std::vector<UCHAR> Buffer(FIELD_OFFSET(KEY_NAME_INFORMATION, Name) + sizeof(WCHAR) * REG_KEY_PATH_LENGTH);
    KEY_NAME_INFORMATION *pkni;
    ULONG Length;

TryAgain:
    Status = NtQueryKey(hKey, KeyNameInformation, Buffer.data(), Buffer.size(), &Length);
    switch (Status) {
    case STATUS_BUFFER_TOO_SMALL:
    case STATUS_BUFFER_OVERFLOW:
        Buffer.resize(Length);
        goto TryAgain;
    case STATUS_SUCCESS:
        pkni = reinterpret_cast<KEY_NAME_INFORMATION *>(Buffer.data());
        keyPath.assign(pkni->Name, pkni->NameLength / sizeof(WCHAR));
    default:
        break;
    }

    return keyPath;
}

Note that NtQueryKey returned STATUS_BUFFER_OVERFLOW but not STATUS_BUFFER_TOO_SMALL on Windows 10 if the supplied buffer is insufficient.

xenophōn
  • 211
  • 2
  • 7
  • After failing miserably trying to allocate memory for the Name, your solution with the vector worked on the first try! Thanks! – FranciscoNabas Oct 06 '22 at 19:25
1

Nominally no because it's just a handle and there is no API that I know of to let you do this in the normal Windows API's.

HOWEVER the Native API has lots of functions some of which can give you handles open for given files and the like so there maybe something similar for the Registry. That and RegMon by SysInternals may do something like this but you'll have to Google I'm afraid :/

Lloyd
  • 29,197
  • 4
  • 84
  • 98
1

You can use RegSaveKey and write it to a file, then look at the file.

Alternatively you can keep a global map of HKEYs to LPCWSTRs and add entries when you open them and do lookups whenever.

You may also be able to do something with the !reg command in WinDBG / NTSD, but you can't just give it the HKEY. You'll have to do some other trickery to get the info you want out of it.

i_am_jorf
  • 53,608
  • 15
  • 131
  • 222
0

For ntsd/windbg:

!handle yourhandle 4

Adri C.S.
  • 2,909
  • 5
  • 36
  • 63