blob: cca08a2f8447c443a31191995c68852b3657fee0 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome_elf/nt_registry/nt_registry.h"
#include <assert.h>
#include <stdlib.h>
#include <memory>
#include <string>
namespace {
// Function pointers used for registry access.
RtlInitUnicodeStringFunction g_rtl_init_unicode_string = nullptr;
NtCreateKeyFunction g_nt_create_key = nullptr;
NtDeleteKeyFunction g_nt_delete_key = nullptr;
NtOpenKeyExFunction g_nt_open_key_ex = nullptr;
NtCloseFunction g_nt_close = nullptr;
NtQueryKeyFunction g_nt_query_key = nullptr;
NtEnumerateKeyFunction g_nt_enumerate_key = nullptr;
NtQueryValueKeyFunction g_nt_query_value_key = nullptr;
NtSetValueKeyFunction g_nt_set_value_key = nullptr;
// Lazy init. No concern about concurrency in chrome_elf.
bool g_initialized = false;
bool g_system_install = false;
bool g_wow64_proc = false;
wchar_t g_kRegPathHKLM[] = L"\\Registry\\Machine\\";
wchar_t g_kRegPathHKCU[nt::g_kRegMaxPathLen + 1] = L"";
wchar_t g_current_user_sid_string[nt::g_kRegMaxPathLen + 1] = L"";
// Max number of tries for system API calls when STATUS_BUFFER_OVERFLOW or
// STATUS_BUFFER_TOO_SMALL can be returned.
enum { kMaxTries = 5 };
// For testing only.
wchar_t g_HKLM_override[nt::g_kRegMaxPathLen + 1] = L"";
wchar_t g_HKCU_override[nt::g_kRegMaxPathLen + 1] = L"";
//------------------------------------------------------------------------------
// Initialization - LOCAL
//------------------------------------------------------------------------------
// Not using install_static, to prevent circular dependency.
bool IsThisProcSystem() {
wchar_t program_dir[MAX_PATH] = {};
wchar_t* cmd_line = GetCommandLineW();
// If our command line starts with the "Program Files" or
// "Program Files (x86)" path, we're system.
DWORD ret = ::GetEnvironmentVariable(L"PROGRAMFILES", program_dir, MAX_PATH);
if (ret && ret < MAX_PATH && !::wcsncmp(cmd_line, program_dir, ret))
return true;
ret = ::GetEnvironmentVariable(L"PROGRAMFILES(X86)", program_dir, MAX_PATH);
if (ret && ret < MAX_PATH && !::wcsncmp(cmd_line, program_dir, ret))
return true;
return false;
}
bool IsThisProcWow64() {
// Using BOOL type for compat with IsWow64Process() system API.
BOOL is_wow64 = FALSE;
// API might not exist, so dynamic lookup.
using IsWow64ProcessFunction = decltype(&IsWow64Process);
IsWow64ProcessFunction is_wow64_process =
reinterpret_cast<IsWow64ProcessFunction>(::GetProcAddress(
::GetModuleHandle(L"kernel32.dll"), "IsWow64Process"));
if (!is_wow64_process)
return false;
if (!is_wow64_process(::GetCurrentProcess(), &is_wow64))
return false;
return is_wow64 ? true : false;
}
bool InitNativeRegApi() {
HMODULE ntdll = ::GetModuleHandleW(L"ntdll.dll");
// Setup the global function pointers for registry access.
g_rtl_init_unicode_string = reinterpret_cast<RtlInitUnicodeStringFunction>(
::GetProcAddress(ntdll, "RtlInitUnicodeString"));
g_nt_create_key = reinterpret_cast<NtCreateKeyFunction>(
::GetProcAddress(ntdll, "NtCreateKey"));
g_nt_delete_key = reinterpret_cast<NtDeleteKeyFunction>(
::GetProcAddress(ntdll, "NtDeleteKey"));
g_nt_open_key_ex = reinterpret_cast<NtOpenKeyExFunction>(
::GetProcAddress(ntdll, "NtOpenKeyEx"));
g_nt_close =
reinterpret_cast<NtCloseFunction>(::GetProcAddress(ntdll, "NtClose"));
g_nt_query_key = reinterpret_cast<NtQueryKeyFunction>(
::GetProcAddress(ntdll, "NtQueryKey"));
g_nt_enumerate_key = reinterpret_cast<NtEnumerateKeyFunction>(
::GetProcAddress(ntdll, "NtEnumerateKey"));
g_nt_query_value_key = reinterpret_cast<NtQueryValueKeyFunction>(
::GetProcAddress(ntdll, "NtQueryValueKey"));
g_nt_set_value_key = reinterpret_cast<NtSetValueKeyFunction>(
::GetProcAddress(ntdll, "NtSetValueKey"));
if (!g_rtl_init_unicode_string || !g_nt_create_key || !g_nt_open_key_ex ||
!g_nt_delete_key || !g_nt_close || !g_nt_query_key ||
!g_nt_enumerate_key || !g_nt_query_value_key || !g_nt_set_value_key)
return false;
// We need to set HKCU based on the sid of the current user account.
RtlFormatCurrentUserKeyPathFunction rtl_current_user_string =
reinterpret_cast<RtlFormatCurrentUserKeyPathFunction>(
::GetProcAddress(ntdll, "RtlFormatCurrentUserKeyPath"));
RtlFreeUnicodeStringFunction rtl_free_unicode_str =
reinterpret_cast<RtlFreeUnicodeStringFunction>(
::GetProcAddress(ntdll, "RtlFreeUnicodeString"));
if (!rtl_current_user_string || !rtl_free_unicode_str)
return false;
UNICODE_STRING current_user_reg_path;
if (!NT_SUCCESS(rtl_current_user_string(&current_user_reg_path)))
return false;
// Finish setting up global HKCU path.
::wcsncat(g_kRegPathHKCU, current_user_reg_path.Buffer, nt::g_kRegMaxPathLen);
::wcsncat(g_kRegPathHKCU, L"\\",
(nt::g_kRegMaxPathLen - ::wcslen(g_kRegPathHKCU)));
// Keep the sid string as well.
wchar_t* ptr = ::wcsrchr(current_user_reg_path.Buffer, L'\\');
ptr++;
::wcsncpy(g_current_user_sid_string, ptr, nt::g_kRegMaxPathLen);
rtl_free_unicode_str(&current_user_reg_path);
// Figure out if this is a system or user install.
g_system_install = IsThisProcSystem();
// Figure out if this is a WOW64 process.
g_wow64_proc = IsThisProcWow64();
g_initialized = true;
return true;
}
//------------------------------------------------------------------------------
// Reg WOW64 Redirection - LOCAL
//
// How registry redirection works directly calling NTDLL APIs:
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// - NOTE: On >= Win7, reflection support was removed.
// -
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa384253(v=vs.85).aspx
//
// - 1) 32-bit / WOW64 process:
// a) Default access WILL be redirected to WOW64.
// b) KEY_WOW64_32KEY access WILL be redirected to WOW64.
// c) KEY_WOW64_64KEY access will NOT be redirected to WOW64.
//
// - 2) 64-bit process:
// a) Default access will NOT be redirected to WOW64.
// b) KEY_WOW64_32KEY access will NOT be redirected to WOW64.
// c) KEY_WOW64_64KEY access will NOT be redirected to WOW64.
//
// - Key point from above is that NTDLL redirects and respects access
// overrides for WOW64 calling processes. But does NOT do any of that if the
// calling process is 64-bit. 2b is surprising and troublesome.
//
// How registry redirection works using these nt_registry APIs:
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// - These APIs will behave the same as NTDLL above, EXCEPT for 2b.
// nt_registry APIs will respect the override access flags for all processes.
//
// - How the WOW64 redirection decision trees / Nodes work below:
//
// The HKLM and HKCU decision trees represent the information at the MSDN
// link above... but in a way that generates a decision about whether a
// registry path should be subject to WOW64 redirection. The tree is
// traversed as you scan along the registry path in question.
//
// - Each Node contains a chunk of registry subkey(s) to match.
// - If it is NOT matched, traversal is done.
// - If it is matched:
// - Current state of |redirection_type| for the whole registry path is
// updated.
// - If |next| is empty, traversal is done.
// - Otherwise, |next| is an array of child Nodes to try to match against.
// Loop.
//------------------------------------------------------------------------------
// This enum defines states for how to handle redirection.
// NOTE: When WOW64 redirection should happen, the redirect subkey can be either
// before or after the latest Node match. Unfortunately not consistent.
enum RedirectionType { SHARED = 0, REDIRECTED_BEFORE, REDIRECTED_AFTER };
struct Node {
template <size_t len, size_t n_len>
constexpr Node(const wchar_t (&wcs)[len],
RedirectionType rt,
const Node (&n)[n_len])
: to_match(wcs),
to_match_len(len - 1),
redirection_type(rt),
next(n),
next_len(n_len) {}
template <size_t len>
constexpr Node(const wchar_t (&wcs)[len], RedirectionType rt)
: to_match(wcs),
to_match_len(len - 1),
redirection_type(rt),
next(nullptr),
next_len(0) {}
const wchar_t* to_match;
size_t to_match_len;
// If a match, this is the new state of how to redirect.
RedirectionType redirection_type;
// |next| is nullptr or an array of Nodes of length |array_len|.
const Node* next;
size_t next_len;
};
// HKLM or HKCU SOFTWARE\Classes is shared by default. Specific subkeys under
// Classes are redirected to SOFTWARE\WOW6432Node\Classes\<subkey> though.
constexpr Node kClassesSubtree[] = {{L"CLSID", REDIRECTED_BEFORE},
{L"DirectShow", REDIRECTED_BEFORE},
{L"Interface", REDIRECTED_BEFORE},
{L"Media Type", REDIRECTED_BEFORE},
{L"MediaFoundation", REDIRECTED_BEFORE}};
// These specific HKLM\SOFTWARE subkeys are shared. Specific
// subkeys under Classes are redirected though... see classes_subtree.
constexpr Node kHklmSoftwareSubtree[] = {
// TODO(pennymac): when MS fixes compiler bug, or bots are all using clang,
// remove the "Classes" subkeys below and replace with:
// {L"Classes", SHARED, kClassesSubtree},
// https://connect.microsoft.com/VisualStudio/feedback/details/3104499
{L"Classes\\CLSID", REDIRECTED_BEFORE},
{L"Classes\\DirectShow", REDIRECTED_BEFORE},
{L"Classes\\Interface", REDIRECTED_BEFORE},
{L"Classes\\Media Type", REDIRECTED_BEFORE},
{L"Classes\\MediaFoundation", REDIRECTED_BEFORE},
{L"Classes", SHARED},
{L"Clients", SHARED},
{L"Microsoft\\COM3", SHARED},
{L"Microsoft\\Cryptography\\Calais\\Current", SHARED},
{L"Microsoft\\Cryptography\\Calais\\Readers", SHARED},
{L"Microsoft\\Cryptography\\Services", SHARED},
{L"Microsoft\\CTF\\SystemShared", SHARED},
{L"Microsoft\\CTF\\TIP", SHARED},
{L"Microsoft\\DFS", SHARED},
{L"Microsoft\\Driver Signing", SHARED},
{L"Microsoft\\EnterpriseCertificates", SHARED},
{L"Microsoft\\EventSystem", SHARED},
{L"Microsoft\\MSMQ", SHARED},
{L"Microsoft\\Non-Driver Signing", SHARED},
{L"Microsoft\\Notepad\\DefaultFonts", SHARED},
{L"Microsoft\\OLE", SHARED},
{L"Microsoft\\RAS", SHARED},
{L"Microsoft\\RPC", SHARED},
{L"Microsoft\\SOFTWARE\\Microsoft\\Shared Tools\\MSInfo", SHARED},
{L"Microsoft\\SystemCertificates", SHARED},
{L"Microsoft\\TermServLicensing", SHARED},
{L"Microsoft\\Transaction Server", SHARED},
{L"Microsoft\\Windows\\CurrentVersion\\App Paths", SHARED},
{L"Microsoft\\Windows\\CurrentVersion\\Control Panel\\Cursors\\Schemes",
SHARED},
{L"Microsoft\\Windows\\CurrentVersion\\Explorer\\AutoplayHandlers", SHARED},
{L"Microsoft\\Windows\\CurrentVersion\\Explorer\\DriveIcons", SHARED},
{L"Microsoft\\Windows\\CurrentVersion\\Explorer\\KindMap", SHARED},
{L"Microsoft\\Windows\\CurrentVersion\\Group Policy", SHARED},
{L"Microsoft\\Windows\\CurrentVersion\\Policies", SHARED},
{L"Microsoft\\Windows\\CurrentVersion\\PreviewHandlers", SHARED},
{L"Microsoft\\Windows\\CurrentVersion\\Setup", SHARED},
{L"Microsoft\\Windows\\CurrentVersion\\Telephony\\Locations", SHARED},
{L"Microsoft\\Windows NT\\CurrentVersion\\Console", SHARED},
{L"Microsoft\\Windows NT\\CurrentVersion\\FontDpi", SHARED},
{L"Microsoft\\Windows NT\\CurrentVersion\\FontLink", SHARED},
{L"Microsoft\\Windows NT\\CurrentVersion\\FontMapper", SHARED},
{L"Microsoft\\Windows NT\\CurrentVersion\\Fonts", SHARED},
{L"Microsoft\\Windows NT\\CurrentVersion\\FontSubstitutes", SHARED},
{L"Microsoft\\Windows NT\\CurrentVersion\\Gre_Initialize", SHARED},
{L"Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options",
SHARED},
{L"Microsoft\\Windows NT\\CurrentVersion\\LanguagePack", SHARED},
{L"Microsoft\\Windows NT\\CurrentVersion\\NetworkCards", SHARED},
{L"Microsoft\\Windows NT\\CurrentVersion\\Perflib", SHARED},
{L"Microsoft\\Windows NT\\CurrentVersion\\Ports", SHARED},
{L"Microsoft\\Windows NT\\CurrentVersion\\Print", SHARED},
{L"Microsoft\\Windows NT\\CurrentVersion\\ProfileList", SHARED},
{L"Microsoft\\Windows NT\\CurrentVersion\\Time Zones", SHARED},
{L"Policies", SHARED},
{L"RegisteredApplications", SHARED}};
// HKCU is entirely shared, except for a few specific Classes subkeys which
// are redirected. See |classes_subtree|.
constexpr Node kRedirectionDecisionTreeHkcu = {L"SOFTWARE\\Classes", SHARED,
kClassesSubtree};
// HKLM\SOFTWARE is redirected by default to SOFTWARE\WOW6432Node. Specific
// subkeys under SOFTWARE are shared though... see |hklm_software_subtree|.
constexpr Node kRedirectionDecisionTreeHklm = {L"SOFTWARE", REDIRECTED_AFTER,
kHklmSoftwareSubtree};
// Main redirection handler function.
// If redirection is required, change is made to |subkey_path| in place.
//
// - This function should be called BEFORE concatenating |subkey_path| with the
// root hive or calling ParseFullRegPath().
// - Also, |subkey_path| should be passed to SanitizeSubkeyPath() before calling
// this function.
void ProcessRedirection(nt::ROOT_KEY root,
ACCESS_MASK access,
std::wstring* subkey_path) {
static constexpr wchar_t kRedirectBefore[] = L"WOW6432Node\\";
static constexpr wchar_t kRedirectAfter[] = L"\\WOW6432Node";
assert(subkey_path != nullptr);
assert(subkey_path->empty() || subkey_path->front() != L'\\');
assert(subkey_path->empty() || subkey_path->back() != L'\\');
assert(root != nt::AUTO);
// |subkey_path| could legitimately be empty.
if (subkey_path->empty() ||
(access & KEY_WOW64_32KEY && access & KEY_WOW64_64KEY))
return;
// No redirection during testing when there's already an override.
// Otherwise, the testing redirect directory Software\Chromium\TempTestKeys
// would get WOW64 redirected if root_key == HKLM in this function.
if (root == nt::HKCU ? *g_HKCU_override : *g_HKLM_override)
return;
// WOW64 redirection only supported on x64 architecture. Return if x86.
SYSTEM_INFO system_info = {};
::GetNativeSystemInfo(&system_info);
if (system_info.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL)
return;
bool use_wow64 = g_wow64_proc;
// Consider KEY_WOW64_32KEY and KEY_WOW64_64KEY override access flags.
if (access & KEY_WOW64_32KEY)
use_wow64 = true;
if (access & KEY_WOW64_64KEY)
use_wow64 = false;
// If !use_wow64, there's nothing more to do.
if (!use_wow64)
return;
// The root of the decision trees are an array of 1.
size_t node_array_len = 1;
// Pick which decision tree to use.
const Node* current_node = (root == nt::HKCU) ? &kRedirectionDecisionTreeHkcu
: &kRedirectionDecisionTreeHklm;
// The following loop works on the |subkey_path| from left to right.
// |position| tracks progress along |subkey_path|.
const wchar_t* position = subkey_path->c_str();
// Hold a count of chars left after position, for efficient calculations.
size_t chars_left = subkey_path->length();
// |redirect_state| holds the latest state of redirection requirement.
RedirectionType redirect_state = SHARED;
// |insertion_point| tracks latest spot for redirection subkey to be inserted.
const wchar_t* insertion_point = nullptr;
// |insert_string| tracks which redirection string would be inserted.
const wchar_t* insert_string = nullptr;
size_t node_index = 0;
while (node_index < node_array_len) {
size_t current_to_match_len = current_node->to_match_len;
// Make sure the remainder of the path is at least as long as the current
// subkey to match.
if (chars_left >= current_to_match_len) {
// Do case insensitive comparisons.
if (!::wcsnicmp(position, current_node->to_match, current_to_match_len)) {
// Make sure not to match on a substring.
if (*(position + current_to_match_len) == L'\\' ||
*(position + current_to_match_len) == L'\0') {
// MATCH!
// -------------------------------------------------------------------
// 1) Update state of redirection.
redirect_state = current_node->redirection_type;
// 1.5) If new state is to redirect, the new insertion point will be
// either right before or right after this match.
if (redirect_state == REDIRECTED_BEFORE) {
insertion_point = position;
insert_string = kRedirectBefore;
} else if (redirect_state == REDIRECTED_AFTER) {
insertion_point = position + current_to_match_len;
insert_string = kRedirectAfter;
}
// 2) Adjust |position| along the subkey path.
position += current_to_match_len;
chars_left -= current_to_match_len;
// 2.5) Increment the position, to move past path seperator(s).
while (*position == L'\\') {
++position;
--chars_left;
}
// 3) Move our loop parameters to the |next| array of Nodes.
node_array_len = current_node->next_len;
current_node = current_node->next;
node_index = 0;
// 4) Finish this loop and start on new array.
continue;
}
}
}
// Move to the next node in the array if we didn't match this loop.
++current_node;
++node_index;
}
if (redirect_state == SHARED)
return;
// Insert the redirection into |subkey_path|, at |insertion_point|.
subkey_path->insert((insertion_point - subkey_path->c_str()), insert_string);
}
//------------------------------------------------------------------------------
// Reg Path Utilities - LOCAL
//------------------------------------------------------------------------------
std::wstring ConvertRootKey(nt::ROOT_KEY root) {
assert(root != nt::AUTO);
if (root == nt::HKCU && *g_HKCU_override) {
std::wstring temp = g_kRegPathHKCU;
temp.append(g_HKCU_override);
temp.append(L"\\");
return temp;
} else if (root == nt::HKLM && *g_HKLM_override) {
// Yes, HKLM override goes into HKCU. This is not a typo.
std::wstring temp = g_kRegPathHKCU;
temp.append(g_HKLM_override);
temp.append(L"\\");
return temp;
}
return (root == nt::HKCU) ? g_kRegPathHKCU : g_kRegPathHKLM;
}
// This utility should be called on an externally provided subkey path.
// - Ensures there are no starting or trailing backslashes, and no more than
// - one backslash in a row.
// - Note from MSDN: "Key names cannot include the backslash character (\),
// but any other printable character can be used. Value names and data can
// include the backslash character."
void SanitizeSubkeyPath(std::wstring* input) {
assert(input != nullptr);
// Remove trailing backslashes.
size_t last_valid_pos = input->find_last_not_of(L'\\');
if (last_valid_pos == std::wstring::npos) {
// The string is all backslashes, or it's empty. Clear and abort.
input->clear();
return;
}
// Chop off the trailing backslashes.
input->resize(last_valid_pos + 1);
// Remove leading backslashes.
input->erase(0, input->find_first_not_of(L'\\'));
// Replace any occurances of more than 1 backslash in a row with just 1.
size_t index = input->find_first_of(L"\\");
while (index != std::wstring::npos) {
// Remove a second consecutive backslash, and leave index where it is,
// or move to the next backslash in the string.
if ((*input)[index + 1] == L'\\')
input->erase(index + 1, 1);
else
index = input->find_first_of(L"\\", index + 1);
}
}
// Turns a root and subkey path into the registry base hive and the rest of the
// subkey tokens.
// - |converted_root| should come directly out of ConvertRootKey function.
// - |subkey_path| should be passed to SanitizeSubkeyPath() first.
// - E.g. base hive: "\Registry\Machine\", "\Registry\User\<SID>\".
bool ParseFullRegPath(const std::wstring& converted_root,
const std::wstring& subkey_path,
std::wstring* out_base,
std::vector<std::wstring>* subkeys) {
out_base->clear();
subkeys->clear();
std::wstring temp_path;
// Special case if there is testing redirection set up.
if (*g_HKCU_override || *g_HKLM_override) {
// Why process |converted_root|? To handle reg redirection used by tests.
// E.g.:
// |converted_root| = "\REGISTRY\USER\S-1-5-21-39260824-743453154-142223018-
// 716772\Software\Chromium\TempTestKeys\13110669370890870$94c6ed9d-bc34-
// 44f3-a0b3-9eee2d3f2f82\".
// |subkey_path| = "SOFTWARE\Google\Chrome\BrowserSec".
//
// Note: bypassing the starting backslash in the |converted_root|.
temp_path.append(converted_root, 1, converted_root.size() - 1);
}
temp_path.append(subkey_path);
// Tokenize the full path.
size_t find_start = 0;
size_t delimiter = temp_path.find_first_of(L'\\');
while (delimiter != std::wstring::npos) {
subkeys->emplace_back(temp_path, find_start, delimiter - find_start);
// Move past the backslash.
find_start = delimiter + 1;
delimiter = temp_path.find_first_of(L'\\', find_start);
}
// Get the last token if there is one.
if (!temp_path.empty())
subkeys->emplace_back(temp_path, find_start);
// Special case if there is testing redirection set up.
if (*g_HKCU_override || *g_HKLM_override) {
// The base hive for HKCU needs to include the user SID.
uint32_t num_base_tokens = 2;
if (0 == temp_path.compare(0, 14, L"REGISTRY\\USER\\"))
num_base_tokens = 3;
if (subkeys->size() < num_base_tokens)
return false;
// Pull out the base hive tokens.
out_base->push_back(L'\\');
for (size_t i = 0; i < num_base_tokens; ++i) {
out_base->append((*subkeys)[i]);
out_base->push_back(L'\\');
}
subkeys->erase(subkeys->begin(), subkeys->begin() + num_base_tokens);
} else {
out_base->assign(converted_root);
}
return true;
}
// String safety.
// - NOTE: only working with wchar_t here.
// - Also ensures the content of |value_bytes| is at least a terminator.
// - Pass "true" for |multi| for MULTISZ.
void EnsureTerminatedSZ(std::vector<BYTE>* value_bytes, bool multi) {
DWORD terminator_size = sizeof(wchar_t);
if (multi)
terminator_size = 2 * sizeof(wchar_t);
// Ensure content is at least the size of a terminator.
if (value_bytes->size() < terminator_size) {
value_bytes->insert(value_bytes->end(),
terminator_size - value_bytes->size(), 0);
}
// Sanity check content size based on character size.
DWORD modulo = value_bytes->size() % sizeof(wchar_t);
value_bytes->insert(value_bytes->end(), modulo, 0);
// Now finally check for trailing terminator.
bool terminated = true;
size_t last_element = value_bytes->size() - 1;
for (size_t i = 0; i < terminator_size; i++) {
if ((*value_bytes)[last_element - i] != 0) {
terminated = false;
break;
}
}
if (terminated)
return;
// Append a full terminator to be safe.
value_bytes->insert(value_bytes->end(), terminator_size, 0);
return;
}
//------------------------------------------------------------------------------
// Misc wrapper functions - LOCAL
//------------------------------------------------------------------------------
NTSTATUS CreateKeyWrapper(const std::wstring& key_path,
ACCESS_MASK access,
HANDLE* out_handle,
ULONG* create_or_open OPTIONAL) {
UNICODE_STRING key_path_uni = {};
g_rtl_init_unicode_string(&key_path_uni, key_path.c_str());
OBJECT_ATTRIBUTES obj = {};
InitializeObjectAttributes(&obj, &key_path_uni, OBJ_CASE_INSENSITIVE, NULL,
nullptr);
return g_nt_create_key(out_handle, access, &obj, 0, nullptr,
REG_OPTION_NON_VOLATILE, create_or_open);
}
} // namespace
namespace nt {
//------------------------------------------------------------------------------
// Create, open, delete, close functions
//------------------------------------------------------------------------------
bool CreateRegKey(ROOT_KEY root,
const wchar_t* key_path,
ACCESS_MASK access,
HANDLE* out_handle OPTIONAL) {
// |key_path| can be null or empty, but it can't be longer than
// |g_kRegMaxPathLen| at this point.
if (key_path != nullptr &&
::wcsnlen(key_path, g_kRegMaxPathLen + 1) == g_kRegMaxPathLen + 1)
return false;
if (!g_initialized && !InitNativeRegApi())
return false;
if (root == nt::AUTO)
root = g_system_install ? nt::HKLM : nt::HKCU;
std::wstring redirected_key_path;
if (key_path) {
redirected_key_path = key_path;
SanitizeSubkeyPath(&redirected_key_path);
ProcessRedirection(root, access, &redirected_key_path);
}
std::wstring current_path;
std::vector<std::wstring> subkeys;
if (!ParseFullRegPath(ConvertRootKey(root), redirected_key_path,
&current_path, &subkeys))
return false;
// Open the base hive first. It should always exist already.
HANDLE last_handle = INVALID_HANDLE_VALUE;
NTSTATUS status =
CreateKeyWrapper(current_path, access, &last_handle, nullptr);
if (!NT_SUCCESS(status))
return false;
size_t subkeys_size = subkeys.size();
if (subkeys_size != 0)
g_nt_close(last_handle);
// Recursively open/create each subkey.
std::vector<HANDLE> rollback;
bool success = true;
for (size_t i = 0; i < subkeys_size; i++) {
current_path.append(subkeys[i]);
current_path.push_back(L'\\');
// Process the latest subkey.
ULONG created = 0;
HANDLE key_handle = INVALID_HANDLE_VALUE;
status =
CreateKeyWrapper(current_path.c_str(), access, &key_handle, &created);
if (!NT_SUCCESS(status)) {
success = false;
break;
}
if (i == subkeys_size - 1) {
last_handle = key_handle;
} else {
// Save any subkey handle created, in case of rollback.
if (created == REG_CREATED_NEW_KEY)
rollback.push_back(key_handle);
else
g_nt_close(key_handle);
}
}
if (!success) {
// Delete any subkeys created.
for (HANDLE handle : rollback) {
g_nt_delete_key(handle);
}
}
for (HANDLE handle : rollback) {
// Close the rollback handles, on success or failure.
g_nt_close(handle);
}
if (!success)
return false;
// See if caller wants the handle left open.
if (out_handle)
*out_handle = last_handle;
else
g_nt_close(last_handle);
return true;
}
bool OpenRegKey(ROOT_KEY root,
const wchar_t* key_path,
ACCESS_MASK access,
HANDLE* out_handle,
NTSTATUS* error_code OPTIONAL) {
// |key_path| can be null or empty, but it can't be longer than
// |g_kRegMaxPathLen| at this point.
if (key_path != nullptr &&
::wcsnlen(key_path, g_kRegMaxPathLen + 1) == g_kRegMaxPathLen + 1)
return false;
if (!g_initialized && !InitNativeRegApi())
return false;
NTSTATUS status = STATUS_UNSUCCESSFUL;
UNICODE_STRING key_path_uni = {};
OBJECT_ATTRIBUTES obj = {};
*out_handle = INVALID_HANDLE_VALUE;
if (root == nt::AUTO)
root = g_system_install ? nt::HKLM : nt::HKCU;
std::wstring full_path;
if (key_path) {
full_path = key_path;
SanitizeSubkeyPath(&full_path);
ProcessRedirection(root, access, &full_path);
}
full_path.insert(0, ConvertRootKey(root));
g_rtl_init_unicode_string(&key_path_uni, full_path.c_str());
InitializeObjectAttributes(&obj, &key_path_uni, OBJ_CASE_INSENSITIVE, NULL,
NULL);
status = g_nt_open_key_ex(out_handle, access, &obj, 0);
// See if caller wants the NTSTATUS.
if (error_code)
*error_code = status;
if (NT_SUCCESS(status))
return true;
return false;
}
bool DeleteRegKey(HANDLE key) {
if (!g_initialized && !InitNativeRegApi())
return false;
NTSTATUS status = g_nt_delete_key(key);
return NT_SUCCESS(status);
}
// wrapper function
bool DeleteRegKey(ROOT_KEY root,
WOW64_OVERRIDE wow64_override,
const wchar_t* key_path) {
HANDLE key = INVALID_HANDLE_VALUE;
if (!OpenRegKey(root, key_path, DELETE | wow64_override, &key, nullptr))
return false;
if (!DeleteRegKey(key)) {
CloseRegKey(key);
return false;
}
CloseRegKey(key);
return true;
}
void CloseRegKey(HANDLE key) {
if (!g_initialized)
InitNativeRegApi();
g_nt_close(key);
}
//------------------------------------------------------------------------------
// Getter functions
//------------------------------------------------------------------------------
bool QueryRegKeyValue(HANDLE key,
const wchar_t* value_name,
ULONG* out_type,
std::vector<BYTE>* out_buffer) {
if (!g_initialized && !InitNativeRegApi())
return false;
UNICODE_STRING value_uni = {};
g_rtl_init_unicode_string(&value_uni, value_name);
// Use a loop here, to be a little more tolerant of concurrent registry
// changes.
NTSTATUS ntstatus = STATUS_UNSUCCESSFUL;
int tries = 0;
KEY_VALUE_FULL_INFORMATION* value_info = nullptr;
DWORD size_needed = sizeof(*value_info);
std::vector<BYTE> buffer(size_needed);
do {
buffer.resize(size_needed);
value_info = reinterpret_cast<KEY_VALUE_FULL_INFORMATION*>(buffer.data());
ntstatus = g_nt_query_value_key(key, &value_uni, KeyValueFullInformation,
value_info, size_needed, &size_needed);
} while ((ntstatus == STATUS_BUFFER_OVERFLOW ||
ntstatus == STATUS_BUFFER_TOO_SMALL) &&
++tries < kMaxTries);
if (!NT_SUCCESS(ntstatus))
return false;
*out_type = value_info->Type;
DWORD data_size = value_info->DataLength;
if (data_size) {
// Move the data into |out_buffer| vector.
BYTE* data = reinterpret_cast<BYTE*>(value_info) + value_info->DataOffset;
out_buffer->assign(data, data + data_size);
} else {
out_buffer->clear();
}
return true;
}
// wrapper function
bool QueryRegValueDWORD(HANDLE key,
const wchar_t* value_name,
DWORD* out_dword) {
ULONG type = REG_NONE;
std::vector<BYTE> value_bytes;
if (!QueryRegKeyValue(key, value_name, &type, &value_bytes) ||
type != REG_DWORD) {
return false;
}
if (value_bytes.size() < sizeof(*out_dword))
return false;
*out_dword = *(reinterpret_cast<DWORD*>(value_bytes.data()));
return true;
}
// wrapper function
bool QueryRegValueDWORD(ROOT_KEY root,
WOW64_OVERRIDE wow64_override,
const wchar_t* key_path,
const wchar_t* value_name,
DWORD* out_dword) {
HANDLE key = INVALID_HANDLE_VALUE;
if (!OpenRegKey(root, key_path, KEY_QUERY_VALUE | wow64_override, &key, NULL))
return false;
if (!QueryRegValueDWORD(key, value_name, out_dword)) {
CloseRegKey(key);
return false;
}
CloseRegKey(key);
return true;
}
// wrapper function
bool QueryRegValueSZ(HANDLE key,
const wchar_t* value_name,
std::wstring* out_sz) {
std::vector<BYTE> value_bytes;
ULONG type = REG_NONE;
if (!QueryRegKeyValue(key, value_name, &type, &value_bytes) ||
(type != REG_SZ && type != REG_EXPAND_SZ)) {
return false;
}
EnsureTerminatedSZ(&value_bytes, false);
*out_sz = reinterpret_cast<wchar_t*>(value_bytes.data());
return true;
}
// wrapper function
bool QueryRegValueSZ(ROOT_KEY root,
WOW64_OVERRIDE wow64_override,
const wchar_t* key_path,
const wchar_t* value_name,
std::wstring* out_sz) {
HANDLE key = INVALID_HANDLE_VALUE;
if (!OpenRegKey(root, key_path, KEY_QUERY_VALUE | wow64_override, &key, NULL))
return false;
if (!QueryRegValueSZ(key, value_name, out_sz)) {
CloseRegKey(key);
return false;
}
CloseRegKey(key);
return true;
}
// wrapper function
bool QueryRegValueMULTISZ(HANDLE key,
const wchar_t* value_name,
std::vector<std::wstring>* out_multi_sz) {
std::vector<BYTE> value_bytes;
ULONG type = REG_NONE;
if (!QueryRegKeyValue(key, value_name, &type, &value_bytes) ||
type != REG_MULTI_SZ) {
return false;
}
EnsureTerminatedSZ(&value_bytes, true);
// Make sure the out vector is empty to start.
out_multi_sz->clear();
wchar_t* pointer = reinterpret_cast<wchar_t*>(value_bytes.data());
std::wstring temp = pointer;
// Loop. Each string is separated by '\0'. Another '\0' at very end (so 2 in
// a row).
while (!temp.empty()) {
pointer += temp.length() + 1;
out_multi_sz->push_back(std::move(temp));
temp = pointer;
}
return true;
}
// wrapper function
bool QueryRegValueMULTISZ(ROOT_KEY root,
WOW64_OVERRIDE wow64_override,
const wchar_t* key_path,
const wchar_t* value_name,
std::vector<std::wstring>* out_multi_sz) {
HANDLE key = INVALID_HANDLE_VALUE;
if (!OpenRegKey(root, key_path, KEY_QUERY_VALUE | wow64_override, &key, NULL))
return false;
if (!QueryRegValueMULTISZ(key, value_name, out_multi_sz)) {
CloseRegKey(key);
return false;
}
CloseRegKey(key);
return true;
}
//------------------------------------------------------------------------------
// Setter functions
//------------------------------------------------------------------------------
bool SetRegKeyValue(HANDLE key,
const wchar_t* value_name,
ULONG type,
const BYTE* data,
DWORD data_size) {
if (!g_initialized && !InitNativeRegApi())
return false;
NTSTATUS ntstatus = STATUS_UNSUCCESSFUL;
UNICODE_STRING value_uni = {};
g_rtl_init_unicode_string(&value_uni, value_name);
BYTE* non_const_data = const_cast<BYTE*>(data);
ntstatus =
g_nt_set_value_key(key, &value_uni, 0, type, non_const_data, data_size);
if (NT_SUCCESS(ntstatus))
return true;
return false;
}
// wrapper function
bool SetRegValueDWORD(HANDLE key, const wchar_t* value_name, DWORD value) {
return SetRegKeyValue(key, value_name, REG_DWORD,
reinterpret_cast<BYTE*>(&value), sizeof(value));
}
// wrapper function
bool SetRegValueDWORD(ROOT_KEY root,
WOW64_OVERRIDE wow64_override,
const wchar_t* key_path,
const wchar_t* value_name,
DWORD value) {
HANDLE key = INVALID_HANDLE_VALUE;
if (!OpenRegKey(root, key_path, KEY_SET_VALUE | wow64_override, &key, NULL))
return false;
if (!SetRegValueDWORD(key, value_name, value)) {
CloseRegKey(key);
return false;
}
return true;
}
// wrapper function
bool SetRegValueSZ(HANDLE key,
const wchar_t* value_name,
const std::wstring& value) {
// Make sure the number of bytes in |value|, including EoS, fits in a DWORD.
if (std::numeric_limits<DWORD>::max() <
((value.length() + 1) * sizeof(wchar_t)))
return false;
DWORD size = (static_cast<DWORD>((value.length() + 1) * sizeof(wchar_t)));
return SetRegKeyValue(key, value_name, REG_SZ,
reinterpret_cast<const BYTE*>(value.c_str()), size);
}
// wrapper function
bool SetRegValueSZ(ROOT_KEY root,
WOW64_OVERRIDE wow64_override,
const wchar_t* key_path,
const wchar_t* value_name,
const std::wstring& value) {
HANDLE key = INVALID_HANDLE_VALUE;
if (!OpenRegKey(root, key_path, KEY_SET_VALUE | wow64_override, &key, NULL))
return false;
if (!SetRegValueSZ(key, value_name, value)) {
CloseRegKey(key);
return false;
}
return true;
}
// wrapper function
bool SetRegValueMULTISZ(HANDLE key,
const wchar_t* value_name,
const std::vector<std::wstring>& values) {
std::vector<wchar_t> builder;
for (auto& string : values) {
// Just in case someone is passing in an illegal empty string
// (not allowed in REG_MULTI_SZ), ignore it.
if (!string.empty()) {
for (const wchar_t& w : string) {
builder.push_back(w);
}
builder.push_back(L'\0');
}
}
// Add second null terminator to end REG_MULTI_SZ.
builder.push_back(L'\0');
// Handle rare case where the vector passed in was empty,
// or only had an empty string.
if (builder.size() == 1)
builder.push_back(L'\0');
if (std::numeric_limits<DWORD>::max() < builder.size())
return false;
return SetRegKeyValue(
key, value_name, REG_MULTI_SZ, reinterpret_cast<BYTE*>(builder.data()),
(static_cast<DWORD>(builder.size()) + 1) * sizeof(wchar_t));
}
// wrapper function
bool SetRegValueMULTISZ(ROOT_KEY root,
WOW64_OVERRIDE wow64_override,
const wchar_t* key_path,
const wchar_t* value_name,
const std::vector<std::wstring>& values) {
HANDLE key = INVALID_HANDLE_VALUE;
if (!OpenRegKey(root, key_path, KEY_SET_VALUE | wow64_override, &key, NULL))
return false;
if (!SetRegValueMULTISZ(key, value_name, values)) {
CloseRegKey(key);
return false;
}
return true;
}
//------------------------------------------------------------------------------
// Enumeration Support
//------------------------------------------------------------------------------
bool QueryRegEnumerationInfo(HANDLE key, ULONG* out_subkey_count) {
if (!g_initialized && !InitNativeRegApi())
return false;
// Use a loop here, to be a little more tolerant of concurrent registry
// changes.
NTSTATUS ntstatus = STATUS_UNSUCCESSFUL;
int tries = 0;
// Start with sizeof the structure. It's very common for the variable sized
// "Class" element to be of length 0.
KEY_FULL_INFORMATION* key_info = nullptr;
DWORD size_needed = sizeof(*key_info);
std::vector<BYTE> buffer(size_needed);
do {
buffer.resize(size_needed);
key_info = reinterpret_cast<KEY_FULL_INFORMATION*>(buffer.data());
ntstatus = g_nt_query_key(key, KeyFullInformation, key_info, size_needed,
&size_needed);
} while ((ntstatus == STATUS_BUFFER_OVERFLOW ||
ntstatus == STATUS_BUFFER_TOO_SMALL) &&
++tries < kMaxTries);
if (!NT_SUCCESS(ntstatus))
return false;
// Move desired information to out variables.
*out_subkey_count = key_info->SubKeys;
return true;
}
bool QueryRegSubkey(HANDLE key,
ULONG subkey_index,
std::wstring* out_subkey_name) {
if (!g_initialized && !InitNativeRegApi())
return false;
// Use a loop here, to be a little more tolerant of concurrent registry
// changes.
NTSTATUS ntstatus = STATUS_UNSUCCESSFUL;
int tries = 0;
// Start with sizeof the structure, plus 12 characters. It's very common for
// key names to be < 12 characters (without being inefficient as an initial
// allocation).
KEY_BASIC_INFORMATION* subkey_info = nullptr;
DWORD size_needed = sizeof(*subkey_info) + (12 * sizeof(wchar_t));
std::vector<BYTE> buffer(size_needed);
do {
buffer.resize(size_needed);
subkey_info = reinterpret_cast<KEY_BASIC_INFORMATION*>(buffer.data());
ntstatus = g_nt_enumerate_key(key, subkey_index, KeyBasicInformation,
subkey_info, size_needed, &size_needed);
} while ((ntstatus == STATUS_BUFFER_OVERFLOW ||
ntstatus == STATUS_BUFFER_TOO_SMALL) &&
++tries < kMaxTries);
if (!NT_SUCCESS(ntstatus))
return false;
// Move desired information to out variables.
// NOTE: NameLength is size of Name array in bytes. Name array is also
// NOT null terminated!
BYTE* name = reinterpret_cast<BYTE*>(subkey_info->Name);
std::vector<BYTE> content(name, name + subkey_info->NameLength);
EnsureTerminatedSZ(&content, false);
out_subkey_name->assign(reinterpret_cast<wchar_t*>(content.data()));
return true;
}
//------------------------------------------------------------------------------
// Utils
//------------------------------------------------------------------------------
const wchar_t* GetCurrentUserSidString() {
if (!g_initialized && !InitNativeRegApi())
return nullptr;
return g_current_user_sid_string;
}
bool IsCurrentProcWow64() {
if (!g_initialized && !InitNativeRegApi())
return false;
return g_wow64_proc;
}
bool SetTestingOverride(ROOT_KEY root, const std::wstring& new_path) {
if (!g_initialized && !InitNativeRegApi())
return false;
std::wstring sani_new_path = new_path;
SanitizeSubkeyPath(&sani_new_path);
if (sani_new_path.length() > g_kRegMaxPathLen)
return false;
if (root == HKCU || (root == AUTO && !g_system_install))
::wcsncpy(g_HKCU_override, sani_new_path.c_str(), nt::g_kRegMaxPathLen);
else
::wcsncpy(g_HKLM_override, sani_new_path.c_str(), nt::g_kRegMaxPathLen);
return true;
}
std::wstring GetTestingOverride(ROOT_KEY root) {
if (!g_initialized && !InitNativeRegApi())
return std::wstring();
if (root == HKCU || (root == AUTO && !g_system_install))
return g_HKCU_override;
return g_HKLM_override;
}
}; // namespace nt