1

I am attempting to RegOpenKeyEx, then RegEnumValue and finally RegQueryValueEx. I do get data returned, but not the data I am searching for.

HKCU\Microsoft\Windows\CurrentVersion\Run - The key I want to search data in. The code below is only part of the entire program, just for reading purposes.

I am guessing the issue lies where I am trying to use RegEnumValue and the value name doesn't exist therefore RegQueryValue doesn't even try to query it. I am thinking about using Ntopenkey, because even the Windows Registry can't read the key. Any thoughts?

Also, when looking at the program's events in proc mon, it does seem to find a value, but the error is NAME_NOT_FOUND and no lPdata is given. I know the name doesn't exist for the value, I just want to search its data.

if (cValues) // Enumerate the key values. 
{
    vector<BYTE> buffer(cbMaxValueData + 1);

    for (i = 0; i < cValues; ++i)
    {
        cchValue = MAX_VALUE_NAME;
        retCode = RegEnumValue(hKey, i, achValue, &cchValue, NULL, NULL, NULL, NULL);
        if (retCode == ERROR_SUCCESS)
        {
            DWORD dwType = REG_SZ;
            DWORD lpData = cbMaxValueData;
            retCode = RegQueryValueEx(hKey, achValue, 0, &dwType, &buffer[0], &lpData);
            if (retCode == ERROR_SUCCESS)
            {
                tstring str((TCHAR*)&buffer[0], lpData / sizeof(TCHAR));
                scanstartup = str.find(_T("data"));
                if (scanstartup != string::npos) {
                    _tprintf(TEXT("Value name: (%u) %s\n"), i + 1, achValue);
                } 
                else
                {
                    _tprintf(TEXT("Not here."));
                }
            }
        }
    }
}
DropItLikeItsHot
  • 139
  • 1
  • 14
  • Not really possible to know what's going on without knowing the contents of the variables here. But the biggest question I have after looking at this snippet is why are you still using TCHAR/tstring? – MrEricSir May 02 '16 at 19:59
  • From [RegQueryValueEx](https://msdn.microsoft.com/en-us/library/windows/desktop/ms724911.aspx): *"lpValueName: The name of the registry value. If lpValueName is NULL or an empty string, "", the function retrieves the type and data for the key's unnamed or default value, if any."* Other than that, I can't really understand most of your question, e.g. *"even the Windows Registry can't read the key"*. Are you perhaps looking in the wrong place (32-bit hive on a 64-bit system)? – IInspectable May 02 '16 at 20:00
  • Your code also seems to be ignoring the documentation for [RegEnumValue](https://msdn.microsoft.com/en-us/library/windows/desktop/ms724865.aspx): "*Return value: [...] If the lpData buffer is too small to receive the value, the function returns ERROR_MORE_DATA.*" You should really post a [mcve], otherwise this is going to be a guessing game, without winners. – IInspectable May 02 '16 at 20:07
  • 1
    If a registry value has a name consisting of a single NUL character (this **is** an ASCII character, btw.), you cannot query for it calling `RegQueryValueEx`. The *lpValueName* is taking a C-style string, which do not support embedded NUL characters. The API essentially sees a zero-sized string, and tries to return the default (unnamed) value, which apparently doesn't exist. If you want to read keys/values with illegal names, you cannot use the Windows API. [ZwQueryValueKey](https://msdn.microsoft.com/en-us/library/windows/hardware/ff567069.aspx) takes a **counted** `PUNICODE_STRING` that ... – IInspectable May 02 '16 at 21:45
  • ... does support embedded NUL characters. Using this native API call you should be able to read this value. – IInspectable May 02 '16 at 21:45
  • Reading the [UNICODE_STRING](https://msdn.microsoft.com/en-us/library/windows/desktop/aa380518.aspx) documentation, it appears as though some tool tried to write the default (unnamed) value, but the behavior changed starting with Server 2008 R2/7 SP1, and it wound up writing a NUL character instead on one of these (or later) systems. Maybe this is a warning, that the native API comes without guarantees, and is not meant to be a programming interface. – IInspectable May 02 '16 at 21:53
  • Do not vandalize your posts. – Nic Apr 16 '17 at 08:07
  • @Broda Then flag it as a duplicate. – DavidPostill Apr 16 '17 at 08:11
  • Possible duplicate of [How to read a value from the Windows registry](http://stackoverflow.com/questions/34065/how-to-read-a-value-from-the-windows-registry) – DropItLikeItsHot Apr 16 '17 at 08:13

2 Answers2

1

Going Native

Here is a self-contained chunk of Native code for enumerating Subkeys and Values. This demo shows values under all users' "Run" keys, as well as the HKLM\Software...\Run key, for both 32 and 64 bit Views.

Value names are stored in std::wstring objects, with embedded nulls intact. To display these strings, an "escaping" function is used which turns embedded nulls into "\0", so you might see something like "Value\0Name".

Key names are not displayed as "escaped" values (but wstring objects for Subkey names will still contain nulls, if present).

All required symbols, definitions, etc. are included here, inside the nt namespace, so you don't need to include any external files. Just make a new Console Project and paste this into its main .cpp file.

This works with Visual Studio 2013.

Update Now values' string data are included in the enumeration. The new version shows the data string as another "escaped" string after the value name.

Update Added example code which demonstrates how to look for null-containing patterns inside wstrings containing registry value data (original, not-escaped). Uncomment the first line in main() to run this demonstration.

Update Moved the StringSearchExample demo to its own code-box and added its output.

#include <iostream>
#include <string>
#include <vector>
#include <utility>

// For a quick build, create a new Console Application, without precompiled headers.
// Then delete stdafx.h, stdafx.cpp, targetver.h (keep your main .cpp file).
// Then paste this into your main .cpp file.
// This defines all types and values and function prototypes it needs to run,
// so you don't need to include <windows.h>.

namespace nt {
    typedef int BOOL;
    typedef unsigned short USHORT, WORD;
    typedef unsigned long NTSTATUS, ULONG, DWORD;
    typedef void *HANDLE;
    typedef struct HMODULE__ {int unused;}* HMODULE;
    static const NTSTATUS STATUS_SUCCESS = 0;
    enum { KEY_QUERY_VALUE=1, KEY_SET_VALUE=2, KEY_CREATE_SUB_KEY=4, KEY_ENUMERATE_SUB_KEYS=8, KEY_WOW64_64KEY=0x100, KEY_WOW64_32KEY=0x200 };
    static const ULONG OBJ_CASE_INSENSITIVE = 0x40;

    struct UNICODE_STRING {
        unsigned short   Length, MaximumLength;
        wchar_t*         Buffer;
    };

    struct OBJECT_ATTRIBUTES {
        ULONG           Length;
        HANDLE          RootDirectory;
        UNICODE_STRING* ObjectName;
        ULONG           Attributes;
        void            *SecurityDescriptor, *SecurityQualityOfService;
        OBJECT_ATTRIBUTES() {
            Length = sizeof(OBJECT_ATTRIBUTES);
            RootDirectory = 0;
            ObjectName = 0;
            Attributes = OBJ_CASE_INSENSITIVE;
            SecurityQualityOfService = SecurityDescriptor = 0;
        }
    };

    enum KEY_VALUE_INFORMATION_CLASS { KeyValueBasicInformation = 0, KeyValueFullInformation };
    enum KEY_INFORMATION_CLASS       { KeyBasicInformation = 0 };      // truncated version

                                                                       // These versions of KEY_VALUE_BASIC_INFORMATION and KEY_BASIC_INFORMATION
                                                                       // are not the standard C struct definitions.  These are defined as templates
                                                                       // for specifying the Name string's length.
    template <int max_length>
    struct KEY_VALUE_BASIC_INFORMATION {
        ULONG TitleIndex, Type, NameLength;
        wchar_t Name[max_length+1];
    };
    template <int max_name_and_data_length>
    struct KEY_VALUE_FULL_INFORMATION {
        ULONG TitleIndex, Type, DataOffset, DataLength, NameLength;
        wchar_t Name[max_name_and_data_length+2];
    };
    template <int max_length>
    struct KEY_BASIC_INFORMATION {
        long long  LastWriteTime;
        ULONG      TitleIndex, NameLength;
        wchar_t    Name[max_length+1];
    };

    extern "C" {
        // Kernel32 imports (assumed linked by default)
        typedef int (__stdcall *FARPROC)();
        HMODULE __stdcall LoadLibraryA(const char*);
        BOOL    __stdcall FreeLibrary(HMODULE);
        FARPROC __stdcall GetProcAddress(HMODULE, const char*);

        // Native NT Function Definitions
        typedef NTSTATUS (__stdcall RTLINITUNICODESTRING)(UNICODE_STRING*, wchar_t*);
        typedef NTSTATUS (__stdcall NTOPENKEY)(HANDLE*, ULONG DesiredAccess, OBJECT_ATTRIBUTES*);
        typedef NTSTATUS (__stdcall NTQUERYVALUEKEY)(HANDLE, UNICODE_STRING* ValueName, KEY_VALUE_INFORMATION_CLASS, void* KeyValueInformation, ULONG Length, ULONG* ResultLength);
        typedef NTSTATUS (__stdcall NTENUMERATEKEY)(HANDLE, ULONG Index, KEY_INFORMATION_CLASS, void* KeyInformation, ULONG KeyInformationLength, ULONG* ResultLength);
        typedef NTSTATUS (__stdcall NTENUMERATEVALUEKEY)(HANDLE, ULONG Index, KEY_VALUE_INFORMATION_CLASS, void* KeyValueInformation, ULONG KeyValueInformationLength, ULONG* ResultLength);
        typedef NTSTATUS (__stdcall NTCLOSE)(HANDLE);
    }
    // static global function pointers
    static RTLINITUNICODESTRING* RtlInitUnicodeString = 0;
    static NTOPENKEY*            NtOpenKey = 0;
    static NTQUERYVALUEKEY*      NtQueryValueKey = 0;
    static NTENUMERATEKEY*       NtEnumerateKey = 0;
    static NTENUMERATEVALUEKEY*  NtEnumerateValueKey = 0;
    static NTCLOSE*              NtClose = 0;

    // During construction, NtDllScopedLoader loads the native library dll and initializes
    // the global function pointers above.
    // When destroyed, it unloads the dll.
    class NtDllScopedLoader {
        HMODULE hNtDll;
    public:
        NtDllScopedLoader() {
            hNtDll = LoadLibraryA("ntdll.dll");
            if(!hNtDll) {
                std::wcout << L"LoadLibraryA failed loading ntdll.dll\n";
                return;
            }
            RtlInitUnicodeString = (RTLINITUNICODESTRING*) GetProcAddress(hNtDll, "RtlInitUnicodeString");
            NtOpenKey            = (NTOPENKEY*)            GetProcAddress(hNtDll, "NtOpenKey");
            NtQueryValueKey      = (NTQUERYVALUEKEY*)      GetProcAddress(hNtDll, "NtQueryValueKey");
            NtEnumerateKey       = (NTENUMERATEKEY*)       GetProcAddress(hNtDll, "NtEnumerateKey");
            NtEnumerateValueKey  = (NTENUMERATEVALUEKEY*)  GetProcAddress(hNtDll, "NtEnumerateValueKey");
            NtClose              = (NTCLOSE*)              GetProcAddress(hNtDll, "NtClose");
        }
        ~NtDllScopedLoader() { if(hNtDll) FreeLibrary(hNtDll); }
    };
    // everything happens during static initialization and destruction
    static const NtDllScopedLoader static_ntdll_loader;
}

// Gets an "escaped" version of a wstring for display,
// so embedded nuls, etc. can be seen.
std::wstring GetEscaped(const std::wstring& str) {
    std::wstring r;
    for(auto ch : str) {
        switch(ch) {
        case L'\\': r += L"\\\\"; break;
        case L'"':  r += L"\\\""; break;
        case L'\n': r += L"\\n"; break;
        case L'\r': r += L"\\r"; break;
        case L'\t': r += L"\\t"; break;
        case 0:     r += L"\\0"; break;
        default:
            if(ch < L' ') {
                static const wchar_t hexdigs[] = L"0123456789abcdef";
                r += L"\\x";
                r += hexdigs[ch / 16];
                r += hexdigs[ch % 16];
            } else {
                r += ch;
            }
            break;
        }   }
    return r;
}
// OpenKey wraps NtOpenKey.  Returns 0 on failure
nt::HANDLE OpenKey(std::wstring key_path, nt::ULONG desired_access) {
    using namespace nt;
    if(key_path.back() == L'\\') key_path.pop_back();
    UNICODE_STRING pathname;
    OBJECT_ATTRIBUTES path;
    path.ObjectName = &pathname;
    RtlInitUnicodeString(path.ObjectName, &key_path[0]);
    HANDLE hKey = 0;
    if(STATUS_SUCCESS != NtOpenKey(&hKey, desired_access, &path)) {
        //std::wcout << "NtOpenKey failed for " << key_path << L'\n';
    }
    return hKey;
}

// GetSubkeyNames gets a vector of wstrings containing the names of a key's sub-keys
std::vector<std::wstring> GetSubkeyNames(std::wstring key_path, bool include_parent=false, nt::ULONG bitness_flag=0) {
    using namespace nt;
    HANDLE hKey = OpenKey(key_path, KEY_ENUMERATE_SUB_KEYS | bitness_flag);
    std::vector<std::wstring> result;
    if(!hKey) return result;
    for(ULONG index=0;;++index) {
        KEY_BASIC_INFORMATION<256> ki;
        ULONG result_size = 0;
        NTSTATUS status = NtEnumerateKey(hKey, index, KeyBasicInformation, &ki, sizeof(ki), &result_size);
        if(status != STATUS_SUCCESS) break;
        std::wstring subkey_name(ki.Name, ki.NameLength / sizeof(ki.Name[0]));
        if(include_parent) {
            subkey_name = key_path + L'\\' + subkey_name;
        }
        result.push_back(subkey_name);
    }
    NtClose(hKey);
    return result;
}

// GetStringValues enumerates a key's values, returning a vector of std::pair<wstring, wsitring>
// the pair's first element is the value's name, the second element is the value's data, copied into
// a wstring (this should only be used if expecting 16-bit character string data).
std::vector<std::pair<std::wstring, std::wstring> >
GetStringValues(std::wstring key_path, nt::ULONG bitness_flag=0) {
    using namespace nt;
    HANDLE hKey = OpenKey(key_path, KEY_QUERY_VALUE | bitness_flag);
    std::vector<std::pair<std::wstring, std::wstring> > result;
    if(!hKey) return result;

    for(ULONG index=0;;++index) {
        KEY_VALUE_FULL_INFORMATION<1024> vi;
        ULONG result_size = 0;
        NTSTATUS status = NtEnumerateValueKey(hKey, index, KeyValueFullInformation, &vi, sizeof(vi), &result_size);
        if(status != STATUS_SUCCESS) break;
        std::wstring value_name(vi.Name, vi.NameLength/sizeof(vi.Name[0]));
        // Value data for registry strings include the terminating null character,
        // and this code displays the value data, exactly as it is stored -- so these data strings
        // will have the extra null character at the end.
        std::wstring value_data(reinterpret_cast<const wchar_t*>(reinterpret_cast<const char*>(&vi) + vi.DataOffset),
            vi.DataLength/sizeof(wchar_t));

        result.push_back(std::pair<std::wstring, std::wstring>(value_name, value_data));
    }
    NtClose(hKey);
    return result;
}

int main() {
    // There is no "Current User" registry path for the native API
    // So this just iterates over all existing users and displays any values found
    // inside each user's Software\Microsoft\Windows\CurrentVersion\Run key.
    // It also shows values found under HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run
    // In addition, both the 32 and 64 bit views are searched

    for(nt::ULONG bitness_flag : { nt::KEY_WOW64_32KEY, nt::KEY_WOW64_64KEY }) {
        // Display the current view
        std::wcout << (bitness_flag == nt::KEY_WOW64_32KEY ? L"\n32 Bit View:\n" : L"\n64 Bit View:\n");

        // Get a list of subkeys under HK_USERS
        auto subkeys = GetSubkeyNames(L"\\Registry\\User", true, bitness_flag);

        // Append the Run path to each user key
        for(auto& keypath : subkeys) {
            keypath += L"\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
        }

        // add the HKLM Run key path to the list
        subkeys.push_back(L"\\Registry\\Machine\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run");

        // Iterate over all paths in subkeys, get a list of each subkey's values, then print their names
        for(const auto& key_path : subkeys) {
            auto values = GetStringValues(key_path, bitness_flag);
            // The subkey path is only displayed if it contains values
            if(!values.empty()) {
                std::wcout << L"    " << key_path << L'\n';
                for(const auto& value_pair : values) {
                    // Display the "escaped" name
                    auto escaped_name = GetEscaped(value_pair.first);
                    auto escaped_data = GetEscaped(value_pair.second);
                    std::wcout << L"        " << L'"' << escaped_name << L"\" = \"" << escaped_data << L"\"\n"; 
}   }   }   }   }

The code below demonstrates searching for a null-embedded pattern inside a wstring value.

#include <iostream>
#include <string>

// FromLit constructs a std::wstring from a string literal,
// even when the literal contains an embedded null character.
// Only use this for literals.
template <std::wstring::size_type char_count>
inline std::wstring FromLit(const wchar_t (& str)[char_count]) {
    return std::wstring(&str[0], char_count-1);
}

void StringSearchExample() {
    std::wcout << L"o Naive Construction:\n";
    // First, naively attempt to construct a wstring from a literal
    // which contains an embedded null character:
    std::wstring a(L"null\0separated");
    // Use wcout to print the wstring...darn! just "null" and nothing after it.
    std::wcout << a << L"\n\n";

    std::wcout << L"o Using FromLit:\n";
    // Ok, now stop being naive and use a template function which
    // knows the literal's character count
    std::wstring b = FromLit(L"null\0separated");
    // wcout prints the null character like a space character,
    std::wcout << b << L'\n';
    // just to be sure, check the numeric value of the character at
    // index 4, expecting a numeric 0 for the null character.
    std::wcout << static_cast<int>(b[4]) << L"\n\n";

    // Create a fake registry value with an embedded null character.
    // (Includes the terminating null found in all registry value strings.)
    auto regval_evil = FromLit(L"Win32 sees this\0NT also can see this\0");
    // Create a fake normal registry string:
    auto regval_innocent = FromLit(L"I have nothing to hide\0");

    // We want to search for patterns containing embedded null characters,
    // to simplify this, remove any terminating nulls before the search:
    if(!regval_evil.back()) regval_evil.pop_back();
    if(!regval_innocent.back()) regval_innocent.pop_back();

    // If you just want to check for the presence of an embedded null character,
    // you can just use the character version of wstring::find, FromLit is not
    // needed or useful for this:
    std::wcout << L"o Checking for embedded null characters:\n";
    std::wcout << L"regval_evil:     " << (regval_evil.find(L'\0')     == std::wstring::npos ? L"CLEAN" : L"DIRTY") << L'\n';
    std::wcout << L"regval_innocent: " << (regval_innocent.find(L'\0') == std::wstring::npos ? L"CLEAN" : L"DIRTY") << L'\n';
    std::wcout << L'\n';

    // But for a null-embedded substring search, FromLit is helpful
    std::wcout << L"o Checking for a substring containing a null:\n";
    // Maybe "\0NT" (null + "NT") is something we need to look for
    // Create another fake registery value with an embedded null, but
    // which does not contain this substring:
    auto regval_legit_null = FromLit(L"Legit\0Secret DRM Value\0");
    if(!regval_legit_null.back()) regval_legit_null.pop_back();
    const auto find_substring = FromLit(L"\0NT");
    std::wcout << L"regval_evil:       " << (regval_evil.find(find_substring)       == std::wstring::npos ? L"CLEAN" : L"DIRTY") << L'\n';
    std::wcout << L"regval_innocent:   " << (regval_innocent.find(find_substring)   == std::wstring::npos ? L"CLEAN" : L"DIRTY") << L'\n';
    std::wcout << L"regval_legit_null: " << (regval_legit_null.find(find_substring) == std::wstring::npos ? L"CLEAN" : L"DIRTY") << L'\n';
    std::wcout << L'\n';
}


int main() {
    StringSearchExample();
}

This is what StringSearchExample displays:

o Naive Construction:
"null"

o Using FromLit:
"null separated"
(int)wstring[4] = 0

o Checking for embedded null characters:
regval_evil:     DIRTY
regval_innocent: CLEAN

o Checking for a substring containing a null:
regval_evil:       DIRTY
regval_innocent:   CLEAN
regval_legit_null: CLEAN
Christopher Oicles
  • 3,017
  • 16
  • 11
  • 1
    @JesseBropez I added showing the value data, but all I had to do was change the value enumeration from reporting `KeyValueBasicInformation` to `KeyValueFullInformation `. So I didn't end up using `NtQueryValueKey`. I noticed that you cannot initialize the `UNICODE_STRING` for the value name argument to `NtQueryValueKey` with `RtlInitUnicodeString` because it takes a null-terminated initialization string, so if a value name might have embedded nulls, the `UNICODE_STRING` needs to be initialized by directly assigning values it its fields. Also, string data gets stored with terminating nulls. – Christopher Oicles May 09 '16 at 18:56
  • @JesseBropez It would probably be better to use the original string (without the escaping), but that makes the `string::find` call inconvenient when you want to specify a null inside a literal, because it will be treated like a null-terminated `const char*` (C-string). But if you want to search the escaped string, you would need to specify the null character like `"\\0"`, and other characters are also changed -- I didn't spend a lot of time on the "escaping" because it was really just meant for showing the user unprintable characters. But I'll add an update for passing a literal to `find`. – Christopher Oicles May 11 '16 at 11:05
  • @JesseBropez Alright, I slapped a sub-demonstration into the answer's code. Uncomment the first line in `main()` if you want to see it run. – Christopher Oicles May 11 '16 at 12:33
  • Hi. I don't understand the problem. Does `StringSearchExample` work? I'll add its output. What version of VS do you have? Anyways, I've been improving the code to more transparently handle null-embedded strings, including in key paths. The problem is, to make it more usable, I introduced C++ classes which end up obfuscating the code with extra levels of abstraction. So, I think I will keep the current example, but make a new code section for this more library-like piece of code. I think I will also separate out `StringSearchExample` into another new code section. – Christopher Oicles May 18 '16 at 03:53
1

This is a more complete answer, but suffers in readability because of the compact style I had to use to keep it under the 3k character limit.

It also uses a C++ class (Udc) which automatically manages UNICODE_STRING data and allows seamless use of null-embedded string literals. This helps with usability, but the extra abstraction acts to obfuscate what is happening on a lower level.

So, I made this a separate answer to preserve the original's straightforward logic.

This new version's embedded API includes creating keys, querying and setting values, deleting values and keys, handling null-embedded key and value names, and finding the path for HKEY_CURRENT_USER. The user may select from several demos to run -- launch without command line arguments to see a menu.

#include <iostream>
#include <string>
#include <vector>
#include <utility>

namespace nt {
    typedef int BOOL;
    typedef unsigned short USHORT, WORD;
    typedef unsigned long NTSTATUS, ULONG, DWORD;
    typedef struct HMODULE__ {int unused;} *HMODULE;
    typedef struct HANDLE__ {int unused;} *HANDLE;

    enum { STATUS_SUCCESS=0, STATUS_OBJECT_NAME_NOT_FOUND=0xC0000034 };
    enum {
        KEY_QUERY_VALUE=1, KEY_SET_VALUE=2, KEY_ENUMERATE_SUB_KEYS=8,
        KEY_WOW64_64KEY=0x100, KEY_WOW64_32KEY=0x200, DELETE=0x10000
    };
    enum { REG_OPTION_NON_VOLATILE=0, REG_OPTION_VOLATILE=1 };
    enum { REG_CREATED_NEW_KEY=1, REG_OPENED_EXISTING_KEY=2 };
    enum { REG_NONE=0, REG_SZ=1, REG_EXPAND_SZ=2, REG_BINARY=3, REG_DWORD=4, REG_MULTI_SZ=7 };
    static const ULONG OBJ_CASE_INSENSITIVE = 0x40;

    struct UNICODE_STRING {
        USHORT   Length, MaximumLength;
        wchar_t* Buffer;
    };

    struct OBJECT_ATTRIBUTES {
        ULONG           Length;
        HANDLE          RootDirectory;
        UNICODE_STRING* ObjectName;
        ULONG           Attributes;
        void            *SecurityDescriptor, *SecurityQualityOfService;
        OBJECT_ATTRIBUTES() {
            Length = sizeof(OBJECT_ATTRIBUTES);
            RootDirectory = 0;
            ObjectName = 0;
            Attributes = OBJ_CASE_INSENSITIVE;
            SecurityQualityOfService = SecurityDescriptor = 0;
        }
    };

    enum KEY_VALUE_INFORMATION_CLASS { KeyValueBasicInformation=0, KeyValueFullInformation };
    enum KEY_INFORMATION_CLASS       { KeyBasicInformation=0 };

    template <int max_length>
    struct KEY_VALUE_BASIC_INFORMATION {
        ULONG TitleIndex, Type, NameLength;
        wchar_t Name[max_length+1];
    };
    template <int max_name_and_data_length>
    struct KEY_VALUE_FULL_INFORMATION {
        ULONG TitleIndex, Type, DataOffset, DataLength, NameLength;
        wchar_t Name[max_name_and_data_length+2];
        const void* GetData() const {
            return (const void*)((const char*)this + DataOffset);
        }
    };
    template <int max_length>
    struct KEY_BASIC_INFORMATION {
        long long  LastWriteTime;
        ULONG      TitleIndex, NameLength;
        wchar_t    Name[max_length+1];
    };

    extern "C" {
        // Kernel32 imports (assumed linked by default)
        typedef int(__stdcall *FARPROC)();
        HMODULE  __stdcall LoadLibraryA(const char*);
        BOOL     __stdcall FreeLibrary(HMODULE);
        FARPROC  __stdcall GetProcAddress(HMODULE, const char*);

        // Native NT API Functions
        typedef NTSTATUS(__stdcall RTLFORMATCURRENTUSERKEYPATH)(UNICODE_STRING*);
        typedef NTSTATUS(__stdcall NTCREATEKEY)(HANDLE*, ULONG DesiredAccess, OBJECT_ATTRIBUTES*, ULONG, UNICODE_STRING* Class, ULONG CreateOptions, ULONG* Disposition);
        typedef NTSTATUS(__stdcall NTOPENKEY)(HANDLE*, ULONG DesiredAccess, OBJECT_ATTRIBUTES*);
        typedef NTSTATUS(__stdcall NTENUMERATEKEY)(HANDLE, ULONG Index, KEY_INFORMATION_CLASS, void* KeyInformation, ULONG KeyInformationLength, ULONG* ResultLength);
        typedef NTSTATUS(__stdcall NTENUMERATEVALUEKEY)(HANDLE, ULONG Index, KEY_VALUE_INFORMATION_CLASS, void* KeyValueInformation, ULONG KeyValueInformationLength, ULONG* ResultLength);
        typedef NTSTATUS(__stdcall NTQUERYVALUEKEY)(HANDLE, UNICODE_STRING* ValueName, KEY_VALUE_INFORMATION_CLASS, void* KeyValueInformation, ULONG Length, ULONG* ResultLength);
        typedef NTSTATUS(__stdcall NTSETVALUEKEY)(HANDLE, UNICODE_STRING* ValueName, ULONG TitleIndex, ULONG Type, void* Data, ULONG DataSize);
        typedef NTSTATUS(__stdcall NTDELETEVALUEKEY)(HANDLE, UNICODE_STRING* ValueName);
        typedef NTSTATUS(__stdcall NTDELETEKEY)(HANDLE);
        typedef NTSTATUS(__stdcall NTCLOSE)(HANDLE);
    }

    static RTLFORMATCURRENTUSERKEYPATH* RtlFormatCurrentUserKeyPath;
    static NTCREATEKEY*          NtCreateKey;
    static NTOPENKEY*            NtOpenKey;
    static NTENUMERATEKEY*       NtEnumerateKey;
    static NTENUMERATEVALUEKEY*  NtEnumerateValueKey;
    static NTQUERYVALUEKEY*      NtQueryValueKey;
    static NTSETVALUEKEY*        NtSetValueKey;
    static NTDELETEVALUEKEY*     NtDeleteValueKey;
    static NTDELETEKEY*          NtDeleteKey;
    static NTCLOSE*              NtClose;

    class NtDllScopedLoader {
        HMODULE hNtDll;
    public:
        NtDllScopedLoader() {
            hNtDll = LoadLibraryA("ntdll.dll");
            if(!hNtDll) {
                std::wcout << L"LoadLibraryA failed loading ntdll.dll\n";
                return;
            }
            RtlFormatCurrentUserKeyPath = (RTLFORMATCURRENTUSERKEYPATH*)GetProcAddress(hNtDll, "RtlFormatCurrentUserKeyPath");
            NtCreateKey          = (NTCREATEKEY*)GetProcAddress(hNtDll, "NtCreateKey");
            NtOpenKey            = (NTOPENKEY*)GetProcAddress(hNtDll, "NtOpenKey");
            NtEnumerateKey       = (NTENUMERATEKEY*)GetProcAddress(hNtDll, "NtEnumerateKey");
            NtEnumerateValueKey  = (NTENUMERATEVALUEKEY*)GetProcAddress(hNtDll, "NtEnumerateValueKey");
            NtQueryValueKey      = (NTQUERYVALUEKEY*)GetProcAddress(hNtDll, "NtQueryValueKey");
            NtSetValueKey        = (NTSETVALUEKEY*)GetProcAddress(hNtDll, "NtSetValueKey");
            NtDeleteValueKey     = (NTDELETEVALUEKEY*)GetProcAddress(hNtDll, "NtDeleteValueKey");
            NtDeleteKey          = (NTDELETEKEY*)GetProcAddress(hNtDll, "NtDeleteKey");
            NtClose              = (NTCLOSE*)GetProcAddress(hNtDll, "NtClose");
        }
        ~NtDllScopedLoader() { if(hNtDll) FreeLibrary(hNtDll); }
    };
    // everything happens during static initialization and destruction
    static const NtDllScopedLoader static_ntdll_loader;
}

// The C++ wrappers below provide an intuitive interface for common Registry Tasks
namespace nt_cpp {
    typedef nt::HANDLE           HANDLE;
    typedef nt::ULONG            ULONG,Tt;
    typedef nt::USHORT           USHORT;
    typedef nt::DWORD            DWORD;
    typedef std::string          string;
    typedef std::wstring         wstring;
    typedef wstring::size_type   St;
    // Udc: Universal Data Class
    class Udc {
        enum {None=0,Str=1,StrEx=2,Bin=3,Dw=4};
        nt::UNICODE_STRING ucstr;
        wstring buf;
        void SyUsSz(St size) { ucstr.Buffer=&buf[0]; ucstr.Length=USHORT(size); ucstr.MaximumLength=USHORT(buf.length()*2); }
        void SyUs(St len) {SyUsSz(len*2);}
        static void Cpy(void*d, const void*s, St sz) { for(St i=0;i<sz;++i) {*((char*)d+i)=*((const char*)s+i);}}
        void unalgn_asgn(const void*s, St sz) { buf.assign((sz+3)/2, L'\0'); Cpy(&buf[0], s, sz); SyUsSz(sz); }
        static wstring hex(char b) {
            static const wchar_t hd[]=L"0123456789abcdef";
            return wstring(1,hd[(b>>4)&15]) + hd[b&15];
        }
    public:
        ULONG type;
        const static St npos = ~St(0);
        St size() const {return ucstr.Length;}
        St length() const {return size()/2;}
        bool empty() const {return !size();}
        void* data() {return ucstr.Buffer;}
        const void* data() const {return ucstr.Buffer;}
        Udc(St reserve=0) : buf(reserve+1,L'\0'),type(reserve?Str:None) { SyUs(0); }
        Udc(const Udc& s) : type(s.type) { unalgn_asgn(s.data(), s.size()); }
        Udc(const wstring& ws) : buf(ws+L'\0'),type(Str) { SyUs(ws.length()); }
        template <St ct> Udc(const wchar_t(&s)[ct]) : buf(s,ct),type(Str) { SyUs(ct - !s[ct-1]); }
        Udc(const void* s, St sz, ULONG tp=Bin) : buf((sz+3)/2, L'\0'),type(tp){SyUsSz(sz); Cpy(data(),s,sz);}
        Udc(const wchar_t*s, St len) : buf(s,len),type(Str) {buf+=L'\0';SyUs(len);}
        Udc(St len, wchar_t wc) : buf(len+1,wc),type(Str) {buf[len]=0; SyUs(len);}
        void pop_back() {if(ucstr.Length>=2) ucstr.Length-=2;}
        wchar_t back() const {return length()?ucstr.Buffer[length()-1]:L'\0';}
        wchar_t* begin() { return ucstr.Buffer; }
        wchar_t* end()   { return begin() + length(); }
        const wchar_t* begin() const { return ucstr.Buffer; }
        const wchar_t* end()   const { return begin() + length(); }
        Udc& operator = (Udc s) {type=s.type; unalgn_asgn(s.data(), s.size()); return *this;}
        operator nt::UNICODE_STRING* () {return &ucstr;}
        operator void* () { return data(); }
        operator wstring () const {
            switch(type) {
                case Str: case StrEx: {
                    wstring ws(length(), L'\0');
                    Cpy(&ws[0], data(), length()*2);
                    return ws;
                }
                case Bin: {
                    const char* p = (const char*)data();
                    wstring ws;
                    for(St i=0; i<size(); ++i) {if(i)ws+=L' ';ws+=hex(p[i]);}
                    return ws;
                }
                case Dw: {
                    if(size()<4) return wstring();
                    const char* p = (const char*)data();
                    return L"0x"+hex(p[3])+hex(p[2])+hex(p[1])+hex(p[0]);
                }
                case None: default: return L"REG_NONE";
            }
        }
        friend std::wostream& operator << (std::wostream& os, const Udc& udc) {return os << wstring(udc);}
        Udc& operator += (const Udc& s) {
            if(s.type == None) return *this;
            if(type == None) return *this = s;
            buf = wstring(*this) + wstring(s) + L'\0';
            type=Str;
            SyUs(buf.length()-1);
            return *this;
        }
        Udc& operator += (wchar_t wc) { return operator += (Udc(1,wc)); }
        friend Udc operator + (Udc a, const Udc &b) { return a += b; }
        friend Udc operator + (wchar_t a, const Udc& b) { Udc r(1,a); return r += b; }
        friend Udc operator + (Udc a, wchar_t b) { return a += b; }
        template <St ct> friend Udc operator + (Udc a, const wchar_t(&s)[ct]) { return a += Udc(s); }
        template <St ct> friend Udc operator + (const wchar_t(&s)[ct], const Udc& b) { Udc a(s); return a += b; }
        DWORD dword() const { return size()==4?*(const DWORD*)data():0; }
        St find(wchar_t wc) const { return wstring(*this).find(wc); }
        St rfind(wchar_t wc) const { return wstring(*this).rfind(wc); }
        Udc substr(St start, St len=npos) const { return wstring(*this).substr(start,len); }
    };

    inline Udc GetEscaped(Udc str) {
        Udc r;
        for(auto ch : str) {
            switch(ch) {
                case L'\\': r += L"\\\\"; break;
                case L'"':  r += L"\\\""; break;
                case L'\n': r += L"\\n"; break;
                case L'\r': r += L"\\r"; break;
                case L'\t': r += L"\\t"; break;
                case 0:     r += L"\\0"; break;
                default:
                    if(ch < L' ') {
                        static const wchar_t hd[] = L"0123456789abcdef";
                        r += L"\\x";
                        r += hd[ch / 16];
                        r += hd[ch % 16];
                    }
                    else { r += ch; }
                    break;
        }   }
        return r;
    }
    inline Udc EscapeData(Udc data) {
        if(data.type == nt::REG_SZ || data.type == nt::REG_EXPAND_SZ) return L'"' + GetEscaped(data) + L'"';
        return wstring(data);
    }

    inline std::pair<Udc, Udc> SplitFullPath(Udc full_path) {
        const auto delim = full_path.rfind(L'\\');
        if(delim == Udc::npos) return std::pair<Udc, Udc>(full_path, Udc());
        return std::pair<Udc, Udc>(full_path.substr(0, delim), full_path.substr(delim + 1));
    }

    static void CloseKey(HANDLE hkey) { nt::NtClose(hkey); }

    static HANDLE OpenKey(Udc key_path, ULONG desired_access) {
        while(key_path.back() == L'\\') key_path.pop_back();
        nt::OBJECT_ATTRIBUTES path;
        path.ObjectName = key_path;
        HANDLE hkey = 0;
        return !nt::NtOpenKey(&hkey, desired_access, &path) ? hkey : 0;
    }
    // CreateKey wraps NtCreateKey.  returns 0 on failure.
    // CreateKey will try to recursively create all missing parent keys in key_path.
    static HANDLE CreateKey(Udc key_path, ULONG desired_access=nt::KEY_QUERY_VALUE|nt::KEY_SET_VALUE|nt::KEY_ENUMERATE_SUB_KEYS, ULONG create_options=nt::REG_OPTION_NON_VOLATILE) {
        using namespace nt;
        while(key_path.back() == L'\\') { key_path.pop_back(); }
        OBJECT_ATTRIBUTES path;
        path.ObjectName = key_path;
        ULONG disposition = 0;
        for(int i=0; i<2; ++i) {
            HANDLE hkey = 0;
            NTSTATUS status = NtCreateKey(&hkey, desired_access, &path, 0, 0, create_options, &disposition);
            if(!status) return hkey;
            if(i || status != STATUS_OBJECT_NAME_NOT_FOUND) return 0;
            auto i_path_up = key_path.rfind(L'\\');
            if(i_path_up == Udc::npos) return 0;
            Udc path_up = key_path.substr(0, i_path_up);
            hkey = CreateKey(path_up, desired_access, create_options);
            if(!hkey) return 0;
            CloseKey(hkey);
        }
        return 0;
    }
    // GetSubkeyNames gets a vector of wstrings containing the names of a key's sub-keys
    std::vector<Udc> static GetSubkeyNames(Udc key_path, bool include_parent=false, ULONG bitness_flag=0) {
        using namespace nt;
        HANDLE hKey = OpenKey(key_path, KEY_ENUMERATE_SUB_KEYS | bitness_flag);
        std::vector<Udc> result;
        if(!hKey) return result;
        for(ULONG index=0;; ++index) {
            KEY_BASIC_INFORMATION<256> ki;
            ULONG result_size = 0;
            NTSTATUS status = NtEnumerateKey(hKey, index, KeyBasicInformation, &ki, sizeof(ki), &result_size);
            if(status) break;
            Udc subkey_name(ki.Name, ki.NameLength/2);
            if(include_parent) { subkey_name = key_path + L'\\' + subkey_name; }
            result.push_back(subkey_name);
        }
        CloseKey(hKey);
        return result;
    }
    // GetValues enumerates a key's values, returning a vector of std::pair<StrArg, ValueData>
    // containing the value's name and data
    std::vector<std::pair<Udc, Udc> > static GetValues(Udc key_path, ULONG bitness_flag=0) {
        using namespace nt;
        HANDLE hKey = OpenKey(key_path, KEY_QUERY_VALUE | bitness_flag);
        std::vector<std::pair<Udc, Udc> > result;
        if(!hKey) return result;
        for(ULONG index=0;; ++index) {
            KEY_VALUE_FULL_INFORMATION<2048> vi;
            ULONG result_size = 0;
            NTSTATUS status = NtEnumerateValueKey(hKey, index, KeyValueFullInformation, &vi, sizeof(vi), &result_size);
            if(status) break;
            Udc value_name(vi.Name, vi.NameLength/2);
            // Value data for registry strings includes the terminating null character,
            result.push_back(std::pair<Udc, Udc>(value_name, Udc(vi.GetData(), vi.DataLength, vi.Type)));
        }
        CloseKey(hKey);
        return result;
    }

    // NT paths for Win32 base keys:
    // HKEY_LOCAL_MACHINE   \Registry\Machine\
    // HKEY_USERS           \Registry\User\
    // HKEY_CURRENT_USER    (use RtlFormatCurrentUserKeyPath)
    // HKEY_CLASSES_ROOT    \Registry\Machine\SOFTWARE\Classes
    // HKEY_CURRENT_CONFIG  \Registry\Machine\SYSTEM\CurrentControlSet\Hardware Profiles\Current

    static Udc GetCurrentUserPath() {
        Udc path(512);
        nt::RtlFormatCurrentUserKeyPath(path);
        return path;
    }
    // This version of QueryValue takes an open key handle and a value name
    static Udc QueryValue(HANDLE hkey, Udc value_name) {
        using namespace nt;
        KEY_VALUE_FULL_INFORMATION<2048> vi; // TODO: allow arbitrary size
        ULONG result_size = 0;
        NTSTATUS status = NtQueryValueKey(hkey, value_name, KeyValueFullInformation, &vi, sizeof(vi), &result_size);
        if(status) return Udc();
        return Udc(vi.GetData(), vi.DataLength, vi.Type);
    }
    // This QueryValue takes a <key name>\<value name> path.  bitness_flag may be supplied to force a view.
    static Udc QueryValue(Udc full_path, ULONG bitness_flag=0) {
        auto key_value_name = SplitFullPath(full_path);
        HANDLE hkey = OpenKey(key_value_name.first, nt::KEY_QUERY_VALUE | bitness_flag);
        if(!hkey) return Udc();
        const Udc value = QueryValue(hkey, key_value_name.second);
        CloseKey(hkey);
        return value;
    }

    // SetValue and DeleteValue (from a path string) try to open their keys with KEY_SET_VALUE
    // access.  This will fail for system-owned paths unless the app is run with
    // Administrator access ("run as Administrator" in the File Explorer context menu).
    // The logged-in user's path, under \Registry\User\ does not need elevation, however.

    static bool SetValue(HANDLE hkey, Udc value_name, Udc data) {
        return !nt::NtSetValueKey(hkey, value_name, 0, data.type, data, data.size());
    }
    static bool SetValue(Udc full_path, Udc data, ULONG bitness_flag=0) {
        using namespace nt;
        auto key_value_name = SplitFullPath(full_path);
        HANDLE hkey = OpenKey(key_value_name.first, nt::KEY_SET_VALUE | bitness_flag);
        if(!hkey) return false;
        bool success = SetValue(hkey, key_value_name.second, data);
        CloseKey(hkey);
        return success;
    }
    static bool DeleteValue(HANDLE hkey, Udc value_name) {
        return !nt::NtDeleteValueKey(hkey, value_name);
    }
    static bool DeleteValue(Udc full_path, ULONG bitness_flag=0) {
        auto key_value_name = SplitFullPath(full_path);
        HANDLE hkey = OpenKey(key_value_name.first, nt::KEY_SET_VALUE | bitness_flag);
        if(!hkey) return false;
        bool success = DeleteValue(hkey, key_value_name.second);
        CloseKey(hkey);
        return success;
    }
    static bool DeleteKey(HANDLE hkey) {
        return !nt::NtDeleteKey(hkey);
    }
    static bool DeleteKey(Udc key_path, ULONG bitness_flag=0) {
        HANDLE hkey = OpenKey(key_path, nt::DELETE | bitness_flag);
        if(!hkey) return false;
        bool success = DeleteKey(hkey);
        CloseKey(hkey);
        return success;
    }
}

namespace {
    // Examples and Native API Parlor Tricks

    void EnumerateRuns() {
        using namespace nt_cpp;
        for(ULONG bitness_flag : { nt::KEY_WOW64_32KEY, nt::KEY_WOW64_64KEY }) {
            // Display the current view
            std::wcout << (bitness_flag == nt::KEY_WOW64_32KEY ? L"\n32 Bit View:\n" : L"\n64 Bit View:\n");
            // Get a list of subkeys under HK_USERS
            auto subkeys = GetSubkeyNames(L"\\Registry\\User", true, bitness_flag);
            // Append the Run path to each user key
            for(auto& keypath : subkeys) {
                keypath += L"\\Software\\Microsoft\\Windows\\CurrentVersion\\Run";
            }
            // add the HKLM Run key path to the list
            subkeys.push_back(L"\\Registry\\Machine\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run");
            // Iterate over all paths in subkeys, get a list of each subkey's values, then print their names
            for(const auto& key_path : subkeys) {
                auto values = GetValues(key_path, bitness_flag);
                // The subkey path is only displayed if it contains values
                if(!values.empty()) {
                    std::wcout << L"    " << key_path << L'\n';
                    for(const auto& value_pair : values) {
                        // Display the "escaped" name and data
                        std::wcout << L"        \"" << GetEscaped(value_pair.first) << L"\" = " << EscapeData(value_pair.second) << L'\n';
    }   }   }   }   }

    const wchar_t demo_key[] = L"\\Software\\NT Registry Demo";

    nt_cpp::Udc HKCU_path() { return nt_cpp::Udc(L"HKEY_CURRENT_USER") + demo_key; }

    nt::HANDLE ReportCreateKey(nt_cpp::Udc key_path,
        nt::ULONG desired_access=nt::KEY_QUERY_VALUE|nt::KEY_SET_VALUE|nt::KEY_ENUMERATE_SUB_KEYS)
    {
        using namespace nt_cpp;
        HANDLE hkey = CreateKey(key_path, desired_access);
        if(!hkey) std::wcout << L"CreateKey failed for \"" << GetEscaped(key_path) << L"\"\n";
        return hkey;
    }
    void ReportSetValue(nt::HANDLE hkey, nt_cpp::Udc value_name, const nt_cpp::Udc& data) {
        using namespace nt_cpp;
        std::wcout << L"    Value \"" << GetEscaped(value_name) << '"';
        if(SetValue(hkey, value_name, data)) { std::wcout << L" Successfully Set\n"; }
        else                                 { std::wcout << L" Set Failed\n"; }
    }
    void ReportDeleteValue(nt::HANDLE hkey, nt_cpp::Udc value_name) {
        using namespace nt_cpp;
        std::wcout << L"    Value \"" << GetEscaped(value_name) << '"';
        if(DeleteValue(hkey, value_name)) { std::wcout << L" Successfully Deleted\n"; }
        else                              { std::wcout << L" Delete Failed\n"; }
    }
    void ReportDeleteKey(nt::HANDLE hkey, nt_cpp::Udc key_name) {
        using namespace nt_cpp;
        std::wcout << L"    Subkey \"" << GetEscaped(key_name) << '"';
        if(DeleteKey(hkey))  { std::wcout << L" Successfully Deleted\n"; }
        else                 { std::wcout << L" Delete Failed\n"; }
    }
    void ReportVerifyData(nt_cpp::Udc value_path) {
        using namespace nt_cpp;
        auto i = value_path.rfind(L'\\');
        Udc value_name = i == Udc::npos ? value_path : value_path.substr(i+1);
        Udc rb = QueryValue(value_path);
        std::wcout << L"    Value \"" << GetEscaped(value_name) << L"\" data readback: " << EscapeData(rb) << L'\n';
    }

    void NullInValueData() {
        using namespace nt_cpp;
        Udc nt_key_path    = GetCurrentUserPath() + demo_key;
        Udc value_name     = L"NullInData";
        Udc nt_value_path  = nt_key_path + L'\\' + value_name;

        HANDLE hkey = ReportCreateKey(nt_key_path);
        if(!hkey) return;
        auto data = QueryValue(hkey, value_name);
        if(data.type == nt::REG_NONE) {
            // Win32 automatically places a null at the end of string values,
            // but with the Native API, the trailing null can be omitted.
            // So here, string values must explicitly include a trailing null
            // if one is desired.
            data = L"This string has a null here ->\0<- there!\0";
            std::wcout <<
            L"    In \"" << HKCU_path() << L"\"\n"
            L"    creating a string value named \"" << value_name << L"\" with a null in its data:\n"
            L"        " << EscapeData(data) << L"\n"
            L"    o RegEdit displays just the string before the null, but you\n"
            L"      can use \"Modify Binary Data\" to see the whole thing.\n"
            L"    o Repeat this command to remove the value.\n\n";
            ReportSetValue(hkey, value_name, data);
        } else {
            std::wcout << L"    Cleaning Up:\n";
            ReportDeleteValue(hkey, value_name);
        }
        CloseKey(hkey);
        ReportVerifyData(nt_value_path);
    }

    void NullInValueName() {
        using namespace nt_cpp;
        Udc nt_key_path    = GetCurrentUserPath() + demo_key;
        Udc value_name     = L"Null\0InName";
        Udc nt_value_path  = nt_key_path + L'\\' + value_name;

        HANDLE hkey = ReportCreateKey(nt_key_path);
        if(!hkey) return;
        auto data = QueryValue(hkey, value_name);
        if(data.type == nt::REG_NONE) {
            std::wcout <<
            L"    In \"" << HKCU_path() << L"\"\n"
            L"    creating a string value whose name contains an embedded null.\n"
            L"    o Causes RegEdit to glitch because it can only see part\n"
            L"      of the value's name and is unable to find its data\n"
            L"    o Repeat this command to remove the value.\n\n";
            data = L"A Normal String Value\0";
            ReportSetValue(hkey, value_name, data);
        } else {
            std::wcout << L"    Cleaning Up:\n";
            ReportDeleteValue(hkey, value_name);
        }
        CloseKey(hkey);
        ReportVerifyData(nt_value_path);
    }

    void NullInKeyName() {
        using namespace nt_cpp;
        Udc nt_key_path     = GetCurrentUserPath() + demo_key;
        Udc subkey_name     = L"Null\0InKeyName";
        Udc value_name      = L"NormalValueName";
        Udc nt_subkey_path  = nt_key_path + L'\\' + subkey_name;
        Udc nt_value_path   = nt_subkey_path + L'\\' + value_name;
        HANDLE hkey = ReportCreateKey(nt_subkey_path, nt::KEY_QUERY_VALUE|nt::KEY_SET_VALUE|nt::DELETE);
        if(!hkey) return;
        auto data = QueryValue(hkey, value_name);
        if(data.type == nt::REG_NONE) {
            std::wcout << 
            L"    In \"" << HKCU_path() << L"\"\n"
            L"    created subkey \"" << GetEscaped(subkey_name) << L"\"\n"
            L"    whose name contains an embedded null.\n"
            L"    o RegEdit will report an error when this key is selected\n"
            L"    o Repeat this command to remove the key.\n\n";
            data = L"A normal string value, hidden in a key which Win32 cannot access\0";
            ReportSetValue(hkey, value_name, data);
        } else {
            std::wcout << L"    Cleaning Up:\n";
            ReportDeleteValue(hkey, value_name);
            ReportDeleteKey(hkey, subkey_name);
        }
        CloseKey(hkey);
        ReportVerifyData(nt_value_path);
    }

    void TwoValuesOneName() {
        using namespace nt_cpp;
        Udc nt_key_path    = GetCurrentUserPath() + demo_key;
        Udc normal_name    = L"ValueName";
        Udc abnormal_name  = L"ValueName\0OhYeah";
        Udc normal_path    = nt_key_path + L'\\' + normal_name;
        Udc abnormal_path  = nt_key_path + L'\\' + abnormal_name;

        HANDLE hkey = ReportCreateKey(nt_key_path);
        if(!hkey) return;
        Udc normal_data = QueryValue(hkey, normal_name);
        if(normal_data.type == nt::REG_NONE) {
            std::wcout << 
            L"    In \"" << HKCU_path() << L"\"\n"
            L"    creating two values with different names,\n"
            L"    but because of an embedded null in one of the names,\n"
            L"    both appear identical to Win32\n"
            L"    o RegEdit does not report an error, but it sees two values with the same name.\n"
            L"      And both values appear to share the same string data.\n"
            L"    o Repeat this command to remove the values.\n";
            normal_data       = L"Normal Name's Data\0";
            Udc abnormal_data = L"AbNorMal nAme'S daTA\0";
            ReportSetValue(hkey, normal_name, normal_data);
            ReportSetValue(hkey, abnormal_name, abnormal_data);
        } else {
            std::wcout << L"    Cleaning Up:\n";
            ReportDeleteValue(hkey, normal_name);
            ReportDeleteValue(hkey, abnormal_name);
        }
        CloseKey(hkey);
        ReportVerifyData(normal_path);
        ReportVerifyData(abnormal_path);
    }

    std::vector<nt_cpp::Udc> RecursiveEmbeddedNullSearch(nt_cpp::Udc key_path) {
        using namespace nt_cpp;
        while(key_path.back() == L'\\') { key_path.pop_back(); }
        std::vector<Udc> results;
        auto values = GetValues(key_path);
        for(const auto& nv : values) {
            if(nv.first.find(L'\0') != Udc::npos) {
                results.push_back(key_path + L'\\' + nv.first);
        }   }
        auto subkeys = GetSubkeyNames(key_path, false);
        for(const auto& kn : subkeys) {
            if(kn.find(L'\0') != Udc::npos) {
                results.push_back(key_path + L'\\' + kn + L'\\');
        }   }
        for(const auto& kn : subkeys) {
            auto sub_results = RecursiveEmbeddedNullSearch(key_path + L'\\' + kn);
            if(!sub_results.empty()) {
                results.insert(results.end(), sub_results.begin(), sub_results.end());
        }   }
        return results;
    }

    bool IsAffirmative(std::wstring answer) {
        for(auto wc : answer) {
            if(wc>=L'A'||wc<=L'Z'||wc>=L'a'||wc<=L'z'||wc==L'0'||wc==L'1') {
                return wc==L'y'||wc==L'Y'||wc==L'1';
        }   }
        return false;
    }

    void FixMistakes() {
        using namespace nt_cpp;
        using std::wcout;
        Udc root_key = GetCurrentUserPath();
        auto paths = RecursiveEmbeddedNullSearch(root_key);
        wcout << L'\n';
        for(const auto& path : paths) {
            if(path.back() == L'\\') {
                wcout << L"Found Key: \"" << GetEscaped(path) << L"\"\nDelete It?\n";
                std::wstring answer;
                std::getline(std::wcin, answer);
                if(IsAffirmative(answer)) {
                    HANDLE hkey = OpenKey(path, nt::DELETE);
                    if(hkey) {
                        if(DeleteKey(hkey)) { wcout << L"Successfully deleted " << GetEscaped(path) << L"\n"; }
                        else                { wcout << L"Failed to delete " << GetEscaped(path) << L"\n"; }
                        CloseKey(hkey);
                    }
                    else { wcout << L"Failed to open " << GetEscaped(path) << L'\n'; }
            }   }
            else {
                wcout << 
                    L"Found Value: \"" << GetEscaped(path) << L"\"\n"
                    L"Containing:  " << EscapeData(QueryValue(path)) << L"\n"
                    L"Delete It?\n";
                std::wstring answer;
                std::getline(std::wcin, answer);
                if(IsAffirmative(answer)) {
                    auto kv = SplitFullPath(path);
                    HANDLE hkey = OpenKey(kv.first, nt::KEY_SET_VALUE);
                    if(hkey) {
                        if(DeleteValue(hkey, kv.second)) wcout << L"Successfully deleted";
                        else wcout << L"Failed to delete";
                        wcout << L" value \"" << GetEscaped(kv.second) << L"\"\n";
                    }
                    else { wcout << L"Failed to open key \"" << kv.first << L"\"\n"; }
    }   }   }   }

    struct { const char *id, *desc; void(*handler)(); } const demos[] ={
        {"runs", "Displays the contents of Windows' \"Run\" keys", EnumerateRuns},
        {"nullinstring", "Creates a null-embedded string value in current user's Registry", NullInValueData},
        {"nullinvalname", "Creates a string with a null-embedded value name in current user's Registry", NullInValueName},
        {"nullinkeyname", "Creates a sub-key with a null-embedded name in current user's Registry", NullInKeyName},
        {"samename", "Creates two values in current user's Registry whose names cannot be distingushed by Win32", TwoValuesOneName},
        {"fix", "Performs recursive search for names with nulls and gives option to delete", FixMistakes}
    };
    void Usage(const char* exe) {
        using std::cout;
        for(const char* p=exe; *p; ++p) { if(*p == '\\') exe = p+1; }
        cout << "\n    Usage:   " << exe << " <command>\n\n";
        cout << "    Available Commands:\n";
        for(int i=0; i<sizeof(demos)/sizeof(demos[0]); ++i) {
            cout << "             " << demos[i].id;
            int len;
            for(len=0; demos[i].id[len]; ++len) {}
            for(; len < 16; ++len) { cout << ' '; }
            cout << demos[i].desc << '\n';
    }   }
    void ExecDemo(std::string id) {
        for(int i=0; i<sizeof(demos)/sizeof(demos[0]); ++i) {
            if(id == demos[i].id) {
                demos[i].handler();
                return;
        }   }
        std::cout << "Command ID not recognized: " << id << '\n';
    }
}

int main(int argc, const char* argv[]) {
    if(argc < 2)              { Usage(argv[0]); }
    for(int i=1; i<argc; ++i) { ExecDemo(argv[i]); }
}
Christopher Oicles
  • 3,017
  • 16
  • 11