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]); }
}