我正在尝试RegOpenKeyEx,然后是RegEnumValue,最后是RegQueryValueEx。我确实得到了返回的数据,但不是我要搜索的数据。
HKCU \ Microsoft \ Windows \ CurrentVersion \ Run - 我想要搜索数据的密钥。下面的代码只是整个程序的一部分,仅供阅读之用。
我猜测问题在于我尝试使用RegEnumValue并且值名称不存在因此RegQueryValue甚至不尝试查询它。我正在考虑使用Ntopenkey,因为即使Windows注册表也无法读取密钥。有什么想法吗?
此外,当在proc mon中查看程序的事件时,它似乎确实找到了一个值,但错误是NAME_NOT_FOUND并且没有给出lPdata。我知道该值不存在,我只想搜索其数据。
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."));
}
}
}
}
}
答案 0 :(得分:1)
本土化
这是一个自包含的Native代码块,用于枚举Subkeys和Values。该演示显示了所有用户的价值观。 &#34;运行&#34;键,以及HKLM \ Software ... \ Run键,用于32位和64位视图。
值名称存储在std :: wstring对象中,嵌入的空值保持不变。为了显示这些字符串,&#34;转义&#34;函数用于将嵌入的空值转换为&#34; \ 0&#34;,因此您可能会看到类似&#34; Value \ 0Name&#34;。
键名不显示为&#34;转义&#34;值(但子键名称的wstring对象仍将包含空值,如果存在)。
此处包含所有必需的符号,定义等,位于nt命名空间内,因此您不需要包含任何外部文件。只需创建一个新的Console Project并将其粘贴到其主.cpp文件中。
这适用于Visual Studio 2013。
<强>更新强> 现在价值观&#39;字符串数据包含在枚举中。新版本将数据字符串显示为另一个&#34;转义&#34;值名称后的字符串。
<强>更新强>
添加了示例代码,演示了如何在包含注册表值数据(原始,未转义)的wstring
内查找包含空值的模式。
取消注释main()
中的第一行以运行此演示。
<强>更新强>
将StringSearchExample
演示移动到自己的代码框并添加其输出。
#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";
} } } } }
下面的代码演示了在wstring
值内搜索空嵌入模式。
#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();
}
这是StringSearchExample
显示的内容:
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
答案 1 :(得分:1)
这是一个更完整的答案,但由于我必须使用紧凑的风格使其低于3k字符限制,因此可读性受损。
它还使用C ++类(Udc
),它自动管理UNICODE_STRING
数据并允许无缝使用空嵌入字符串文字。这有助于实现可用性,但额外的抽象行为可以模糊低层次上发生的事情。
所以,我把它作为一个单独的答案来保留原始的直接逻辑。
这个新版本的嵌入式API包括创建密钥,查询和设置值,删除值和密钥,处理空嵌入的键和值名称,以及查找HKEY_CURRENT_USER
的路径。用户可以从几个演示中选择要运行 - 在没有命令行参数的情况下启动以查看菜单。
#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]); }
}