/*++ Copyright (c) 2000 Microsoft Corporation Module Name: VRegistry.cpp Abstract: A virtual registry for misbehaving registry readers. This engine has 5 main features: 1. Key redirection Eg: HKLM\Software -> HKLM\Hardware 2. Key and Value spoofing Eg: HKLM\Software\VersionNumber can be made to appear as a valid value HKEY_DYN_DATA can appear valid 3. Expansion of REG_EXPAND_SZ value type to REG_SZ Eg: %SystemRoot%\Media will result in C:\WINNT\Media 4. Support for EnumKey, EnumValue and QueryInfoKey on virtual keys 5. Support for CreateKey Other features: 1. Strip leading '\' characters from keys 2. Add MAXIMUM_ALLOWED security attributes to all keys 3. Adjust parameters of QueryInfoKey to match Win95 4. Enable key deletion for key which still has subkeys in order to match Win95 behavior for RegDeleteKey 5. Values and keys can be protected from modification and deletion 6. Custom triggers on opening a key. 7. Values that have extra data beyond end of string can be queried even though the provided buffer is too small for extra data. Known limitations: No support for RegSetValue and RegSetValueEx other than known parameter error and value protection. Notes: This is for apps with registry problems History: 01/06/2000 linstev Created 01/10/2000 linstev Added support for RegEnumKey, RegEnumValue 01/10/2000 linstev Added support for RegCreateKey 05/05/2000 linstev Parameterized 10/03/2000 maonis Bug fixes and got rid of the cleanup code in process detach. 10/30/2000 andyseti Added support for RegDeleteKey 02/27/2001 robkenny Converted to use CString 08/07/2001 mikrause Added protectors, enumeration of virtual & non-virtual keys & values, triggers on opening a key, and querying values with extra data and a too small buffer. 10/12/2001 mikrause Added support for custom callbacks on SetValue. Reimplemented value protectors as do-nothing callbacks. --*/ #include "precomp.h" IMPLEMENT_SHIM_BEGIN(VirtualRegistry) #include "ShimHookMacro.h" #include "VRegistry.h" #include "VRegistry_Worker.h" // Allows us to have only one code path for dumping all APIs or just the APIs // that had errors #define ELEVEL(lRet) SUCCESS(lRet) ? eDbgLevelInfo : eDbgLevelError CRITICAL_SECTION csRegCriticalSection; // Global instance of Virtual Registry class CVirtualRegistry VRegistry; // Used to enable win9x only features BOOL g_bWin9x = TRUE; /*++ Class Description: This class is designed to simplify locking logic. If an object of this class is instantiated, then internal lock will be taken. As soon as object is destroyed lock will be released. We also check if the registry has been initialized. This has to happen late because we don't get notified after we've been loaded. History: 01/10/2000 linstev Created --*/ static BOOL g_bInitialized = FALSE; BOOL ParseCommandLineA(LPCSTR lpCommandLine); class CRegLock { public: CRegLock() { EnterCriticalSection(&csRegCriticalSection); if (!g_bInitialized) { VENTRY* ventry = g_pVList; while (ventry->pfnBuilder) { if (ventry->bShouldCall) { DPFN( eDbgLevelInfo, " %S", ventry->cName); ventry->pfnBuilder(ventry->szParam); if (ventry->szParam) { free(ventry->szParam); } ventry->bShouldCall = FALSE; } ventry++; } g_bInitialized = TRUE; } } ~CRegLock() { LeaveCriticalSection(&csRegCriticalSection); } }; /*++ Function Description: Remove leading slash from an Unicode string Arguments: IN lpSubKey - path to string Return Value: Subkey without leading \ History: 01/06/2000 linstev Created --*/ LPCWSTR TrimSlashW( IN OUT LPCWSTR lpSubKey ) { if (!lpSubKey) { return lpSubKey; } LPWSTR lpNew = (LPWSTR) lpSubKey; #define REG_MACHINE L"\\Registry\\Machine" #define REG_USER L"\\Registry\\User" // // Pull off the old NT4 legacy stuff. This only works on NT4, but we're // making it for everyone since it's low risk. // if (wcsistr(lpNew, REG_MACHINE) == lpNew) { LOGN( eDbgLevelError, "[TrimSlashW] Bypass \\Registry\\Machine"); lpNew += wcslen(REG_MACHINE); } else if (wcsistr(lpNew, REG_USER) == lpNew) { LOGN( eDbgLevelError, "[TrimSlashW] Bypass \\Registry\\User"); lpNew += wcslen(REG_USER); } if (*lpNew == L'\\') { LOGN( eDbgLevelError, "[TrimSlashW] Removed slash from key beginning"); lpNew++; } return lpNew; } /*++ Function Description: Convert a key from registry format to virtual registry format. i.e.: HKEY, Path -> VPath. The VPath format has the base included as "HKLM" instead of HKEY_LOCAL_MACHINE etc. Algorithm: 1. Case the different keys and output a 4 letter string 2. Append subkey if available Arguments: IN hkBase - Base key, eg: HKEY_LOCAL_MACHINE IN lpSubKey - Subkey, eg: SOFTWARE OUT lpPath - Output, eg: HKLM\SOFTWARE Return Value: A string path of the form HKLM\SOFTWARE History: 01/06/2000 linstev Created --*/ LPWSTR MakePath( IN HKEY hkBase, IN LPCWSTR lpKey, IN LPCWSTR lpSubKey ) { DWORD dwSize = 0; if (hkBase) { // Length of HKCU + NULL dwSize = 5; } if (lpKey) { dwSize += wcslen(lpKey) + 1; } if (lpSubKey) { dwSize += wcslen(lpSubKey) + 1; } LPWSTR lpPath = (LPWSTR) malloc((dwSize + 1) * sizeof(WCHAR)); if (!lpPath) { if (dwSize) { DPFN( eDbgLevelError, szOutOfMemory); } return NULL; } *lpPath = L'\0'; HRESULT hr; if (hkBase) { if (hkBase == HKEY_CLASSES_ROOT) { hr = StringCchCopyW(lpPath, dwSize + 1, L"HKCR"); if (FAILED(hr)) { goto ErrorCleanup; } } else if (hkBase == HKEY_CURRENT_CONFIG) { hr = StringCchCopyW(lpPath, dwSize + 1, L"HKCC"); if (FAILED(hr)) { goto ErrorCleanup; } } else if (hkBase == HKEY_CURRENT_USER) { hr = StringCchCopyW(lpPath, dwSize + 1, L"HKCU"); if (FAILED(hr)) { goto ErrorCleanup; } } else if (hkBase == HKEY_LOCAL_MACHINE) { hr = StringCchCopyW(lpPath, dwSize + 1, L"HKLM"); if (FAILED(hr)) { goto ErrorCleanup; } } else if (hkBase == HKEY_USERS) { hr = StringCchCopyW(lpPath, dwSize + 1, L"HKUS"); if (FAILED(hr)) { goto ErrorCleanup; } } else if (hkBase == HKEY_PERFORMANCE_DATA) { hr = StringCchCopyW(lpPath, dwSize + 1, L"HKPD"); if (FAILED(hr)) { goto ErrorCleanup; } } else if (hkBase == HKEY_DYN_DATA) { hr = StringCchCopyW(lpPath, dwSize + 1, L"HKDD"); if (FAILED(hr)) { goto ErrorCleanup; } } else { DPFN( eDbgLevelWarning, "Key not found: %08lx - did not get an openkey or createkey", hkBase); } } // Add the key if (lpKey) { if (wcslen(lpPath) != 0) { hr = StringCchCatW(lpPath, dwSize + 1, L"\\"); if (FAILED(hr)) { goto ErrorCleanup; } } hr = StringCchCatW(lpPath, dwSize + 1, lpKey); if (FAILED(hr)) { goto ErrorCleanup; } } // Add the subkey if (lpSubKey) { if (wcslen(lpPath) != 0) { hr = StringCchCatW(lpPath, dwSize + 1, L"\\"); if (FAILED(hr)) { goto ErrorCleanup; } } hr = StringCchCatW(lpPath, dwSize + 1, lpSubKey); if (FAILED(hr)) { goto ErrorCleanup; } } // The key name can have a trailing slash, so we clean this up DWORD dwLen = wcslen(lpPath); if (dwLen && (lpPath[dwLen - 1] == L'\\')) { lpPath[dwLen - 1] = L'\0'; } return lpPath; ErrorCleanup: free(lpPath); return NULL; } /*++ Function Description: Convert a key from Path format into key and subkey format. Algorithm: 1. Case the different keys and output a 4 letter string 2. Append subkey if available Arguments: IN lpPath - Path, eg: HKLM\Software OUT hkBase - Key, eg: HKEY_LOCAL_MACHINE OUT lpSubKey - Subkey, eg: Software Return Value: None History: 01/06/2000 linstev Created --*/ LPWSTR SplitPath( IN LPCWSTR lpPath, OUT HKEY *hkBase ) { LPWSTR p = (LPWSTR) lpPath; // Find first \ or NULL while (*p && (*p != L'\\')) p++; if (wcsncmp(lpPath, L"HKCR", 4) == 0) *hkBase = HKEY_CLASSES_ROOT; else if (wcsncmp(lpPath, L"HKCC", 4) == 0) *hkBase = HKEY_CURRENT_CONFIG; else if (wcsncmp(lpPath, L"HKCU", 4) == 0) *hkBase = HKEY_CURRENT_USER; else if (wcsncmp(lpPath, L"HKLM", 4) == 0) *hkBase = HKEY_LOCAL_MACHINE; else if (wcsncmp(lpPath, L"HKUS", 4) == 0) *hkBase = HKEY_USERS; else if (wcsncmp(lpPath, L"HKPD", 4) == 0) *hkBase = HKEY_PERFORMANCE_DATA; else if (wcsncmp(lpPath, L"HKDD", 4) == 0) *hkBase = HKEY_DYN_DATA; else *hkBase = 0; // Don't allow an invalid base key to get through. if (*hkBase && lpPath[4] != '\\') { *hkBase = 0; } if (*p) { p++; } return p; } /*++ Function Description: Add a virtual key: a key contains other keys and values and will behave like a normal registry key, but of course has no persistent storage. Algorithm: 1. The input string is split apart and a tree is created recursively 2. The key is created only if it doesn't already exist Arguments: IN lpPath - Path to key, eg: "HKLM\\Software" Return Value: Pointer to key or NULL History: 01/06/2000 linstev Created --*/ VIRTUALKEY * VIRTUALKEY::AddKey( IN LPCWSTR lpPath ) { VIRTUALKEY *key; LPWSTR p = (LPWSTR)lpPath; // Find first \ or NULL while (*p && (*p != L'\\')) p++; // Check if this part already exists key = keys; while (key != NULL) { if (_wcsnicmp(lpPath, key->wName, p - lpPath) == 0) { if (*p == L'\\') { // Continue the search return key->AddKey(p + 1); } else { // We already added this key return key; } } key = key->next; } // Create a new key key = (VIRTUALKEY *) malloc(sizeof(VIRTUALKEY)); if (!key) { DPFN( eDbgLevelError, szOutOfMemory); return NULL; } ZeroMemory(key, sizeof(VIRTUALKEY)); // // Still use wcsncpy, because here it specifies number of characters // to copy, not size of the destination buffer. Add in check // for destination buffer size. // if ( (p - lpPath) > sizeof(key->wName)/sizeof(WCHAR)) { free (key); return NULL; } wcsncpy((LPWSTR)key->wName, lpPath, p - lpPath); key->next = keys; keys = key; DPFN( eDbgLevelSpew, "Adding Key %S", key->wName); if (*p == L'\0') { // We are at the end of the chain, so just return this one return key; } else { // More subkeys to go return key->AddKey(p + 1); } } /*++ Function Description: Add a value to a virtual key. The actual registry key may exist and the value may even exist, but this value will override. Algorithm: 1. If lpData is a string and cbData is 0, calculate the size 2. Add this value (no duplicate checking) Arguments: IN lpValueName - Value name IN dwType - Type of key; eg: REG_SZ, REG_DWORD etc IN lpData - Data, use unicode if string IN cbData - Size of lpData Return Value: Pointer to value or NULL History: 01/06/2000 linstev Created --*/ VIRTUALVAL * VIRTUALKEY::AddValue( IN LPCWSTR lpValueName, IN DWORD dwType, IN BYTE *lpData, IN DWORD cbData ) { // Parameter validation if (lpData == NULL && cbData != 0) { return NULL; } VIRTUALVAL *value = (VIRTUALVAL *) malloc(sizeof(VIRTUALVAL)); if (!value) { DPFN( eDbgLevelError, szOutOfMemory); return NULL; } ZeroMemory(value, sizeof(VIRTUALVAL)); // Auto calculate size if cbData is 0 if (lpData && (cbData == 0)) { switch (dwType) { case REG_SZ: case REG_EXPAND_SZ: cbData = wcslen((LPWSTR)lpData)*2 + sizeof(WCHAR); break; case REG_DWORD: cbData = sizeof(DWORD); break; } } // lpValueName can == NULL, which means default value if (lpValueName) { HRESULT hr = StringCchCopy(value->wName, sizeof(value->wName)/sizeof(WCHAR), lpValueName); if (FAILED(hr)) { free(value); return NULL; } } if (cbData) { // Make a copy of the data if needed value->lpData = (BYTE *) malloc(cbData); if (!value->lpData) { DPFN( eDbgLevelError, szOutOfMemory); free(value); return NULL; } MoveMemory(value->lpData, lpData, cbData); value->cbData = cbData; } value->pfnQueryValue = NULL; value->pfnSetValue = NULL; value->dwType = dwType; value->next = values; values = value; if (lpData && ((dwType == REG_SZ) || (dwType == REG_EXPAND_SZ))) { DPFN( eDbgLevelSpew, "Adding Value %S\\%S = %S", wName, lpValueName, lpData); } else { DPFN( eDbgLevelSpew, "Adding Value %S\\%S", wName, lpValueName); } return value; } /*++ Function Description: Add a dword value to a key. Calls off to AddValue. Arguments: IN lpValueName - Value name IN Value - DWord value Return Value: Pointer to a virtual dword value History: 05/25/2000 linstev Created --*/ VIRTUALVAL * VIRTUALKEY::AddValueDWORD( IN LPCWSTR lpValueName, IN DWORD dwValue ) { return AddValue(lpValueName, REG_DWORD, (LPBYTE)&dwValue); } /*++ Function Description: Add an expander to a key. An expander causes QueryValue to expand the REG_EXPAND_SZ type to a REG_SZ type. The expander itself is just a virtual value which allows us to intercept queries to it. Arguments: IN lpValueName - Value name Return Value: Pointer to a virtual value History: 01/06/2000 linstev Created --*/ VIRTUALVAL * VIRTUALKEY::AddExpander( IN LPCWSTR lpValueName ) { VIRTUALVAL *value = AddValue(lpValueName, REG_SZ, 0, 0); if (value) { value->pfnQueryValue = VR_Expand; } return value; } /*++ Function Description: Add a protector on a value. A protector causes SetValue to be ignored. This is implemented through a custom setvalue callback that does nothing. Arguments: IN lpValueName - Value name Return Value: Pointer to a virtual value History: 10/12/2001 mikrause Created --*/ VIRTUALVAL * VIRTUALKEY::AddProtector( IN LPCWSTR lpValueName ) { VIRTUALVAL *value = AddValue(lpValueName, REG_SZ, 0, 0); if (value) { value->pfnSetValue = VR_Protect; } return value; } /*++ Function Description: Add a custom queryvalue routine Arguments: IN lpValueName - Value name IN pfnQueryValue - routine to call when this value is queried Return Value: Pointer to a virtual value History: 07/18/2000 linstev Created --*/ VIRTUALVAL * VIRTUALKEY::AddCustom( IN LPCWSTR lpValueName, _pfn_QueryValue pfnQueryValue ) { VIRTUALVAL *value = AddValue(lpValueName, REG_SZ, 0, 0); if (value) { value->pfnQueryValue = pfnQueryValue; } return value; } /*++ Function Description: Add a custom setvalue routine Arguments: IN lpValueName - Value name IN pfnSetValue - routine to call when this value is set Return Value: Pointer to a virtual value History: 11/06/2001 mikrause Created --*/ VIRTUALVAL * VIRTUALKEY::AddCustomSet( IN LPCWSTR lpValueName, _pfn_SetValue pfnSetValue ) { VIRTUALVAL *value = AddValue(lpValueName, REG_SZ, 0, 0); if (value) { value->pfnSetValue = pfnSetValue; } return value; } /*++ Function Description: Find a subkey of a key. Algorithm: 1. Recursively search the tree for the matching subkey Arguments: IN lpKeyName - Name of key to find Return Value: Pointer to value or NULL History: 01/06/2000 linstev Created --*/ VIRTUALKEY * VIRTUALKEY::FindKey( IN LPCWSTR lpPath ) { VIRTUALKEY *key = keys; LPWSTR p = (LPWSTR)lpPath; if (!lpPath) { return NULL; } // Find first \ or NULL while (*p && (*p != L'\\')) p++; // recursively look for the key while (key) { if (_wcsnicmp( lpPath, key->wName, max((DWORD_PTR)(p - lpPath), wcslen(key->wName))) == 0) { if (*p == L'\\') { key = key->FindKey(p + 1); } break; } key = key->next; } // We're at the end of the chain return key; } /*++ Function Description: Find a value in a key. Arguments: IN key - Key used for expanders; unused at this time IN lpValueName - Value name Return Value: Pointer to value or NULL History: 01/06/2000 linstev Created --*/ VIRTUALVAL * VIRTUALKEY::FindValue( IN LPCWSTR lpValueName ) { VIRTUALVAL *value = values; WCHAR wDef[1] = L""; LPWSTR lpName; if (!lpValueName) { lpName = (LPWSTR)wDef; } else { lpName = (LPWSTR)lpValueName; } // Find the value while (value) { if (_wcsicmp(lpName, value->wName) == 0) { LOGN( eDbgLevelWarning, "[FindValue] Using virtual value: %S", value->wName); break; } value = value->next; } return value; } /*++ Function Description: Free the subkeys and values belonging to a key Algorithm: 1. Free all values belonging to a key, including any data 2. Free all subkeys recursively Arguments: None Return Value: None History: 01/06/2000 linstev Created --*/ VOID VIRTUALKEY::Free() { VIRTUALVAL *value = values; VIRTUALKEY *key = keys; while (value) { values = value->next; if (value->lpData) { free((PVOID) value->lpData); } free((PVOID) value); value = values; } while (key) { keys = key->next; key->Free(); free((PVOID) key); key = keys; } DPFN( eDbgLevelSpew, "Free keys and values from %S", wName); } /*++ Function Description: Allocate a new enum entry Arguments: IN wzPath - Key path or value name of entry. IN next - Next entry in the list. Return Value: Pointer to new entry or NULL History: 08/21/2001 mikrause Created --*/ ENUMENTRY* CreateNewEnumEntry( IN LPWSTR wzPath, IN ENUMENTRY* next) { ENUMENTRY* enumEntry; enumEntry = (ENUMENTRY*)malloc(sizeof(ENUMENTRY)); if (enumEntry == NULL) { DPFN( eDbgLevelError, szOutOfMemory); return NULL; } ZeroMemory(enumEntry, sizeof(ENUMENTRY)); enumEntry->wzName = (LPWSTR)malloc((wcslen(wzPath) + 1)*sizeof(WCHAR)); if (enumEntry->wzName == NULL) { free(enumEntry); DPFN( eDbgLevelError, szOutOfMemory); return NULL; } HRESULT hr = StringCchCopyW(enumEntry->wzName, wcslen(wzPath)+1, wzPath); if (FAILED(hr)) { free(enumEntry->wzName); free(enumEntry); return NULL; } enumEntry->next = next; return enumEntry; } /*++ Function Description: Add enumeration entries to a list. Templatized, so the same code works for keys or values. Arguments: IN entryHead - Head of the list containing virtual keys or values. IN enumFunc - Enumeration function to use. Either RegEnumKey or RegEnumValue Return Value: Pointer to the head of the entry list, or NULL. History: 08/21/2001 mikrause Created --*/ template ENUMENTRY* OPENKEY::AddEnumEntries(T* entryHead, _pfn_EnumFunction enumFunc) { LONG lRet; DWORD dwIndex; DWORD dwSize; WCHAR wzName[MAX_PATH + 1]; ENUMENTRY* enumEntryList = NULL; ENUMENTRY* newEnumEntry = NULL; // Add virtual entries to the list. T* entry = entryHead; while (entry) { // Create a new entry. newEnumEntry = CreateNewEnumEntry(entry->wName, enumEntryList); if (newEnumEntry != NULL) { enumEntryList = newEnumEntry; } entry = entry->next; } // Now non-virtuals. if (bVirtual == FALSE) { dwIndex = 0; for (;;) { dwSize = MAX_PATH * sizeof(WCHAR); lRet = enumFunc(hkOpen, dwIndex, wzName, &dwSize, NULL, NULL, NULL, NULL); // No more items, we're done. if (lRet == ERROR_NO_MORE_ITEMS) { break; } // // Check for error. // On Win2K, this can return more data if there are additional keys. // if (lRet != ERROR_SUCCESS && lRet != ERROR_MORE_DATA) { break; } // Check if this key is a duplicate. entry = entryHead; while (entry) { if (_wcsicmp(entry->wName, wzName) == 0) { break; } entry = entry->next; } // Add this key to the list, if it's not a duplicate. if (entry == NULL) { // Create a new entry. newEnumEntry = CreateNewEnumEntry(wzName, enumEntryList); if (newEnumEntry != NULL) { enumEntryList = newEnumEntry; } } dwIndex++; } } return enumEntryList; } /*++ Function Description: Builds the list of enumerated keys and values. Arguments: None Return Value: None History: 08/10/2001 mikrause Created --*/ VOID OPENKEY::BuildEnumList() { VIRTUALKEY* keyHead = NULL; VIRTUALVAL* valHead = NULL; if (vkey) { keyHead = vkey->keys; valHead = vkey->values; } enumKeys = AddEnumEntries(keyHead, (_pfn_EnumFunction)ORIGINAL_API(RegEnumKeyExW)); enumValues = AddEnumEntries(valHead, (_pfn_EnumFunction)ORIGINAL_API(RegEnumValueW)); } /*++ Function Description: Flushes all enumerated data.. Arguments: None Return Value: None History: 08/10/2001 mikrause Created --*/ VOID OPENKEY::FlushEnumList() { ENUMENTRY *enumentry; DPFN(eDbgLevelInfo, "Flushing enumeration data for %S", wzPath); while (enumKeys) { enumentry = enumKeys; enumKeys = enumKeys->next; if (enumentry->wzName) { free(enumentry->wzName); } free(enumentry); } while (enumValues) { enumentry = enumValues; enumValues = enumValues->next; if (enumentry->wzName) { free(enumentry->wzName); } free(enumentry); } enumKeys = enumValues = NULL; } /*++ Function Description: Initialize the virtual registry. This would ordinarily go into the constructor, but because of the shim architecture, we need to explicity initialize and free the virtual registry. Arguments: None Return Value: None History: 01/06/2000 linstev Created --*/ BOOL CVirtualRegistry::Init() { OpenKeys = NULL; Redirectors = NULL; KeyProtectors = NULL; OpenKeyTriggers = NULL; Root = (VIRTUALKEY *) malloc(sizeof(VIRTUALKEY)); if (!Root) { DPFN(eDbgLevelError, szOutOfMemory); return FALSE; } ZeroMemory(Root, sizeof(VIRTUALKEY)); HRESULT hr = StringCchCopyW(Root->wName, sizeof(Root->wName)/sizeof(WCHAR), L"ROOT"); if (FAILED(hr)) { return FALSE; } DPFN( eDbgLevelSpew, "Initializing Virtual Registry"); return TRUE; } /*++ Function Description: Free the lists contained by the virtual registry. This includes keys, their values and redirectors. Algorithm: 1. Free virtual root key which recursively frees subkeys and values 2. Free open keys 3. Free redirectors Arguments: None Return Value: None History: 01/06/2000 linstev Created --*/ VOID CVirtualRegistry::Free() { OPENKEY *key; REDIRECTOR *redirect; OPENKEYTRIGGER *trigger; PROTECTOR *protector; DPFN( eDbgLevelSpew, "Freeing Virtual Registry"); // Free Root and all subkeys/values if (Root) { Root->Free(); free(Root); Root = NULL; } // Delete all enumeration data. FlushEnumLists(); // Free list of open registry keys key = OpenKeys; while (key) { OpenKeys = key->next; free(key->wzPath); free(key); key = OpenKeys; } // Free redirectors redirect = Redirectors; while (redirect) { Redirectors = redirect->next; free(redirect->wzPath); free(redirect->wzPathNew); free(redirect); redirect = Redirectors; } // Free open key triggers trigger = OpenKeyTriggers; while(trigger) { OpenKeyTriggers = trigger->next; free(trigger->wzPath); free(trigger); trigger = OpenKeyTriggers; } // Free Protectors protector = KeyProtectors; while(protector) { KeyProtectors = protector->next; free(protector->wzPath); free(protector); protector = KeyProtectors; } } /*++ Function Description: Create a dummy key for use as a virtual key. We need to have unique handles in order to look up the keys, so by creating a key off HKLM, we can be sure it won't fail. We can't damage the registry like this because writes to this key will fail. Calls to QueryValue, QueryInfo and EnumKey will work correctly because the virtual registry is used in preference to the real one. Arguments: None Return Value: Dummy key History: 01/06/2000 linstev Created --*/ HKEY CVirtualRegistry::CreateDummyKey() { HKEY key = NULL; LONG lRet = RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"Software", 0, KEY_READ, &key); if (lRet != ERROR_SUCCESS) { return NULL; } return key; } /*++ Function Description: Find an open key in the list of open keys. Algorithm: 1. Search the list of open keys for a match Arguments: IN hKey - Open HKEY Return Value: Pointer to a key or NULL History: 01/06/2000 linstev Created --*/ OPENKEY * CVirtualRegistry::FindOpenKey( IN HKEY hKey ) { OPENKEY *key = OpenKeys; while (key) { if (key->hkOpen == hKey) { return key; } key = key->next; } return NULL; } /*++ Function Description: If this key is to be redirected, we adjust the path to the redirected version. This works even if the requested path is a 'subpath' of a redirector, eg: Input = HKLM\Software\Test Redirector = HKLM\Software -> HKLM\Hardware Output = HKLM\Hardware\Test If no redirector is present for this key/path, then lpPath is unchanged Algorithm: 1. Find a key whose base is a redirector 2. Substitute the new base for the key Arguments: IN OUT lpPath - Path to redirect Return Value: TRUE if redirected History: 01/06/2000 linstev Created --*/ BOOL CVirtualRegistry::CheckRedirect( IN OUT LPWSTR *lpPath ) { REDIRECTOR *redirect = Redirectors; DWORD sza = wcslen(*lpPath); // Go through the list of redirectors while (redirect) { DWORD szb = wcslen(redirect->wzPath); if ((szb <= sza) && (_wcsnicmp(*lpPath, redirect->wzPath, szb) == 0) && ((*lpPath)[szb] == L'\\' || (*lpPath)[szb] == L'\0')) { WCHAR *p = *lpPath + szb; DWORD cchPathSize = wcslen(redirect->wzPathNew) + wcslen(p) + 1; LPWSTR wzPathNew = (LPWSTR) malloc(cchPathSize * sizeof(WCHAR)); if (wzPathNew) { HRESULT hr; hr = StringCchCopyW(wzPathNew, cchPathSize, redirect->wzPathNew); if (FAILED(hr)) { free (wzPathNew); return FALSE; } hr = StringCchCatW(wzPathNew, cchPathSize, p); if (FAILED(hr)) { free(wzPathNew); return FALSE; } // return the new path LOGN( eDbgLevelWarning, "Redirecting: %S -> %S", *lpPath, wzPathNew); free(*lpPath); *lpPath = wzPathNew; return TRUE; } else { DPFN( eDbgLevelError, szOutOfMemory); return FALSE; } } redirect = redirect->next; } return FALSE; } /*++ Function Description: Returns true if a protector guards this key. This will even work on a subkey of a protector. Arguments: IN lpPath - Path to protect Return Value: TRUE if protected History: 08/07/2001 mikrause Created --*/ BOOL CVirtualRegistry::CheckProtected( IN LPWSTR lpPath ) { PROTECTOR *protect; DWORD sza = wcslen(lpPath); DWORD szb; protect = KeyProtectors; while (protect) { szb = wcslen(protect->wzPath); // Check if we have a key or subkey match. if ((szb <= sza) && (_wcsnicmp(protect->wzPath, lpPath, szb) == 0) && (lpPath[szb] == L'\\' || lpPath[szb] == L'\0')) { // Protector found. LOGN( eDbgLevelWarning, "\tProtecting: %S", lpPath); return TRUE; } protect = protect->next; } // Fell through, no protector found. return FALSE; } /*++ Function Description: Checks if any triggers should be called on this path, and calls them. Arguments: IN lpPath - Path to check triggers for. Return Value: None History: 08/09/2001 mikrause Created --*/ VOID CVirtualRegistry::CheckTriggers( IN LPWSTR lpPath) { OPENKEYTRIGGER *trigger; DWORD sza, szb; sza = wcslen(lpPath); trigger = OpenKeyTriggers; // // Loop through all triggers and check. Even after finding a match, // keep repeating, because a single OpenKey can cause multiple triggers. // while (trigger) { szb = wcslen(trigger->wzPath); if ((szb <= sza) && (_wcsnicmp(lpPath, trigger->wzPath, szb)==0) && (lpPath[szb] == L'\\' || lpPath[szb] == L'\0')) { DPFN(eDbgLevelInfo, "Triggering %S on opening of %S", trigger->wzPath, lpPath); trigger->pfnTrigger(lpPath); } trigger = trigger->next; } } /*++ Function Description: Flushes all enumerated lists. Arguments: IN lpPath - Path to redirect, eg: HKLM\Software\Microsoft IN lpPathNew - Redirect to this path Return Value: None History: 01/06/2000 linstev Created --*/ VOID CVirtualRegistry::FlushEnumLists() { OPENKEY *key; key = OpenKeys; while (key) { key->FlushEnumList(); key = key->next; } } /*++ Function Description: Add a redirector to the virtual registry. See CheckRedirect(). Arguments: IN lpPath - Path to redirect, eg: HKLM\Software\Microsoft IN lpPathNew - Redirect to this path Return Value: None History: 01/06/2000 linstev Created --*/ REDIRECTOR * CVirtualRegistry::AddRedirect( IN LPCWSTR lpPath, IN LPCWSTR lpPathNew) { REDIRECTOR *redirect = (REDIRECTOR *) malloc(sizeof(REDIRECTOR)); if (!redirect) { DPFN( eDbgLevelError, szOutOfMemory); return NULL; } ZeroMemory(redirect, sizeof(REDIRECTOR)); DWORD cchPath = wcslen(lpPath) + 1; DWORD cchNewPath = wcslen(lpPathNew) + 1; redirect->wzPath = (LPWSTR) malloc(cchPath * sizeof(WCHAR)); redirect->wzPathNew = (LPWSTR) malloc(cchNewPath * sizeof(WCHAR)); if (redirect->wzPath && redirect->wzPathNew) { HRESULT hr; hr = StringCchCopyW(redirect->wzPath, cchPath, lpPath); if (FAILED(hr)) { goto ErrorCleanup; } hr = StringCchCopyW(redirect->wzPathNew, cchNewPath, lpPathNew); if (FAILED(hr)) { goto ErrorCleanup; } } else { DPFN( eDbgLevelError, szOutOfMemory); goto ErrorCleanup; } redirect->next = Redirectors; Redirectors = redirect; DPFN( eDbgLevelSpew, "Adding Redirector: %S ->\n %S", lpPath, lpPathNew); return redirect; ErrorCleanup: free(redirect->wzPath); free(redirect->wzPathNew); free(redirect); return NULL; } /*++ Function Description: Add a key protector to the virtual registry. See CheckProtected(). Arguments: IN lpPath - Path to protector, eg: HKLM\Software\Microsoft Return Value: None History: 08/21/2001 mikrause Created --*/ PROTECTOR * CVirtualRegistry::AddKeyProtector( IN LPCWSTR lpPath) { PROTECTOR *protect = (PROTECTOR *) malloc(sizeof(PROTECTOR)); if (!protect) { DPFN( eDbgLevelError, szOutOfMemory); return NULL; } ZeroMemory(protect, sizeof(PROTECTOR)); DWORD cchPath = wcslen(lpPath) + 1; protect->wzPath = (LPWSTR) malloc(cchPath * sizeof(WCHAR)); if (protect->wzPath) { HRESULT hr; hr = StringCchCopyW(protect->wzPath, cchPath, lpPath); if (FAILED(hr)) { goto ErrorCleanup; } } else { DPFN( eDbgLevelError, szOutOfMemory); goto ErrorCleanup; } DPFN( eDbgLevelSpew, "Adding Key Protector: %S", lpPath); protect->next = KeyProtectors; KeyProtectors = protect; return protect; ErrorCleanup: free(protect->wzPath); free(protect); return NULL; } /*++ Function Description: Add an open key trigger to the virtual registry. Arguments: IN lpPath - Path to trigger on, eg: HKLM\Software\Microsoft IN pfnOpenKey - Function to be called when key is opened. Return Value: New open key trigger, or NULL on failure. History: 08/07/2001 mikrause Created --*/ OPENKEYTRIGGER* CVirtualRegistry::AddOpenKeyTrigger( IN LPCWSTR lpPath, IN _pfn_OpenKeyTrigger pfnOpenKey) { OPENKEYTRIGGER *openkeytrigger = (OPENKEYTRIGGER *) malloc(sizeof(OPENKEYTRIGGER)); if (!openkeytrigger) { DPFN( eDbgLevelError, szOutOfMemory); return NULL; } ZeroMemory(openkeytrigger, sizeof(OPENKEYTRIGGER)); DWORD cchPath = wcslen(lpPath) + 1; openkeytrigger->wzPath = (LPWSTR) malloc(cchPath * sizeof(WCHAR)); if (openkeytrigger->wzPath) { HRESULT hr = StringCchCopyW(openkeytrigger->wzPath, cchPath, lpPath); if (FAILED(hr)) { goto ErrorCleanup; } } else { DPFN( eDbgLevelError, szOutOfMemory); goto ErrorCleanup; } openkeytrigger->pfnTrigger = pfnOpenKey; openkeytrigger->next = OpenKeyTriggers; OpenKeyTriggers = openkeytrigger; DPFN( eDbgLevelSpew, "Adding Open Key Trigger: %S, func@0x%x", lpPath, pfnOpenKey); return openkeytrigger; ErrorCleanup: free(openkeytrigger->wzPath); free(openkeytrigger); return NULL; } /*++ Function Description: Allow user to specify VRegistry.AddKey instead of VRegistry.Root->AddKey. Arguments: IN lpPath - Path of key Return Value: Virtual key History: 01/06/2000 linstev Created --*/ VIRTUALKEY * CVirtualRegistry::AddKey( IN LPCWSTR lpPath ) { return Root->AddKey(lpPath); } /*++ Function Description: Virtualized version of RegCreateKeyA, RegCreateKeyExA, RegOpenKeyA and RegOpenKeyExA See RegOpenKey* and RegCreateKey* for details Algorithm: 1. Convert lpSubKey and lpClass to WCHAR 2. Pass through to OpenKeyW Arguments: IN hKey - Handle to open key or HKLM etc IN lpSubKey - Subkey to open IN lpClass - Address of a class string IN DWORD dwOptions - special options flag OUT phkResult - Handle to open key if successful OUT lpdwDisposition - Address of disposition value buffer IN bCreate - Create the key if it doesn't exist Return Value: Error code or ERROR_SUCCESS History: 01/06/2000 linstev Created --*/ LONG CVirtualRegistry::OpenKeyA( IN HKEY hKey, IN LPCSTR lpSubKey, IN LPSTR lpClass, IN DWORD dwOptions, IN REGSAM samDesired, IN LPSECURITY_ATTRIBUTES pSecurityAttributes, OUT HKEY *phkResult, OUT LPDWORD lpdwDisposition, IN BOOL bCreate ) { LONG lRet; LPWSTR wzSubKey = NULL; LPWSTR wzClass = NULL; if (lpSubKey) { wzSubKey = ToUnicode(lpSubKey); if (!wzSubKey) { DPFN( eDbgLevelError, szOutOfMemory); return ERROR_NOT_ENOUGH_MEMORY; } } if (lpClass) { wzClass = ToUnicode(lpClass); if (!wzClass) { free(wzSubKey); DPFN( eDbgLevelError, szOutOfMemory); return ERROR_NOT_ENOUGH_MEMORY; } } lRet = OpenKeyW( hKey, wzSubKey, wzClass, dwOptions, samDesired, pSecurityAttributes, phkResult, lpdwDisposition, bCreate, FALSE, NULL); free(wzSubKey); free(wzClass); return lRet; } /*++ Function Description: Wrapper for RegOpenKeyExW, RegOpenKeyW, RegCreateKeyW and RegCreateKeyExW Algorithm: 1. Strip leading '\' characters 2. Inherit already open key data to get full key path 3. Redirect if necessary 4. RegOpenKeyEx with maximum possible security attributes 5. If the open failed, check for virtual key 6. If virtual, return a dummy key and succeed 7. Find the virtual key if it exists and attach it to the open key Arguments: IN hKey - Handle to open key or HKLM etc IN lpSubKey - Subkey to open IN lpClass - Address of a class string IN DWORD dwOptions - special options flag OUT phkResult - Handle to open key if successful OUT lpdwDisposition - Address of disposition value buffer IN bCreate - Create the key if it doesn't exist IN bRemote - Opening the remote registry. IN lpMachineName - Machine name. Return Value: Error code or ERROR_SUCCESS History: 01/06/2000 linstev Created --*/ LONG CVirtualRegistry::OpenKeyW( IN HKEY hKey, IN LPCWSTR lpSubKey, IN LPWSTR lpClass, IN DWORD dwOptions, IN REGSAM samDesired, IN LPSECURITY_ATTRIBUTES pSecurityAttributes, OUT HKEY *phkResult, OUT LPDWORD lpdwDisposition, IN BOOL bCreate, IN BOOL bRemote, IN LPCWSTR lpMachineName ) { // Just a paranoid sanity check if (!hKey) { DPFN( eDbgLevelError, "NULL handle passed to OpenKeyW"); return ERROR_INVALID_HANDLE; } // Hack for Mavis Beacon which uses really old stack for this parameter if (lpdwDisposition && IsBadWritePtr(lpdwDisposition, sizeof(DWORD_PTR))) { DPFN( eDbgLevelError, "HACK: Ignoring bad lpdwDispostion pointer"); lpdwDisposition = NULL; } LONG lRet; OPENKEY *key; BOOL bVirtual, bRedirected; VIRTUALKEY *vkey; LPWSTR wzPath = NULL; __try { // Base error condition lRet = ERROR_INVALID_HANDLE; // Everybody AVs if this ones bad *phkResult = 0; samDesired &= (KEY_WOW64_64KEY | KEY_WOW64_32KEY); samDesired |= MAXIMUM_ALLOWED; // Win9x ignores the options parameter if (g_bWin9x) { if (dwOptions & REG_OPTION_VOLATILE) { LOGN( eDbgLevelWarning, "[OpenKeyW] Removing volatile flag"); } dwOptions = REG_OPTION_NON_VOLATILE; } // Trim leading stuff, e.g. '\' character lpSubKey = TrimSlashW(lpSubKey); // Inherit from previously opened key key = FindOpenKey(hKey); if (key) { bVirtual = key->bVirtual; bRedirected = key->bRedirected; wzPath = MakePath(0, key->wzPath, lpSubKey); } else { bVirtual = FALSE; bRedirected = FALSE; wzPath = MakePath(hKey, NULL, lpSubKey); } if (!wzPath) { // Set the error code appropriately lRet = ERROR_NOT_ENOUGH_MEMORY; } // Check if we need to trigger on this key else { CheckTriggers(wzPath); } // Now that we have the full path, see if we want to redirect it if (!bRedirected && wzPath && CheckRedirect(&wzPath)) { // // Turn off virtual mode - since we don't know anything about the // key we're redirecting to... // bVirtual = FALSE; // // Make sure we know we've been redirected so we don't get into recursive // problems if the destination is a subkey of the source. // bRedirected = TRUE; // // We've been redirected, so we can no longer open the key directly: // we have to get the full path in order to open the right key. // lpSubKey = SplitPath(wzPath, &hKey); } // Try and open the key if it's not already virtual if (!bVirtual) { // // Since we aren't virtual yet, we need to try for the original // key. If one of these fail, then we'll go ahead and try for a // virtual key. // if (bCreate) { lRet = ORIGINAL_API(RegCreateKeyExW)( hKey, lpSubKey, 0, lpClass, dwOptions, samDesired, pSecurityAttributes, phkResult, lpdwDisposition); if (lRet == ERROR_SUCCESS) { // Possible change in enumeration data, flush lists. FlushEnumLists(); } } else { // // bRemote is only true when this is called by the // RegConnectRegistry hook so bCreate can't be true. // if (bRemote) { lRet = ORIGINAL_API(RegConnectRegistryW)( lpMachineName, hKey, phkResult); } else { lRet = ORIGINAL_API(RegOpenKeyExW)( hKey, lpSubKey, 0, samDesired, phkResult); } } } // // We have to look up the virtual key even if we managed to open an // actual key, because when we query, we look for virtual values // first. i.e. the virtual values override existing values. // vkey = Root->FindKey(wzPath); // Check if our key is virtual, or may need to become virtual if (bVirtual || FAILURE(lRet)) { if (vkey) { // // We have a virtual key, so create a dummy handle to hand back // to the app. // *phkResult = CreateDummyKey(); if (*phkResult) { bVirtual = TRUE; lRet = ERROR_SUCCESS; } else { // Couldn't create the dummy key, something seriously wrong. DPFN(eDbgLevelError, "Couldn't create dummy key in OpenKeyW"); lRet = ERROR_FILE_NOT_FOUND; } } } if (SUCCESS(lRet) && wzPath) { // Made it this far, so make a new key entry key = (OPENKEY *) malloc(sizeof(OPENKEY)); if (key) { key->vkey = vkey; key->bVirtual = bVirtual; key->bRedirected = bRedirected; key->hkOpen = *phkResult; key->wzPath = wzPath; key->enumKeys = NULL; key->enumValues = NULL; key->next = OpenKeys; OpenKeys = key; } else { DPFN( eDbgLevelError, szOutOfMemory); // Clean up the dummy key RegCloseKey(*phkResult); lRet = ERROR_NOT_ENOUGH_MEMORY; } } DPFN( ELEVEL(lRet), "%08lx=OpenKeyW(Key=%04lx)", lRet, hKey); if (wzPath) { DPFN( ELEVEL(lRet), " Path=%S", wzPath); } DPFN( ELEVEL(lRet), " Result=%04lx", *phkResult); } __except(EXCEPTION_EXECUTE_HANDLER) { DPFN( eDbgLevelError, "Exception occurred in OpenKeyW"); lRet = ERROR_BAD_ARGUMENTS; } if (FAILURE(lRet)) { // // If we failed for any reason, we didn't create an OPENKEY and so we // can kill wzPath which was allocated by MakePath. // free(wzPath); } return lRet; } /*++ Function Description: Wrapper for RegQueryValueExA and RegQueryValue. See QueryValueW for more details. Algorithm: 1. Call QueryValueW 2. If it's a string, convert back to ANSI Note: this whole function is slightly more complex than it needs to be because we don't want to query the value twice: once to get it's type and the second time to get the value. Most of the complications are due to the strings: we have to make sure we have a buffer large enough so we can figure out how large the (possibly DBCS) string is. Arguments: IN hKey - Handle to open key IN lpValueName - Value to query IN lpType - Type of data, eg: REG_SZ IN OUT lpData - Buffer for queries data IN OUT lpcbData - Size of input buffer/size of returned data Return Value: Error code or ERROR_SUCCESS History: 01/06/2000 linstev Created --*/ LONG CVirtualRegistry::QueryValueA( IN HKEY hKey, IN LPSTR lpValueName, IN LPDWORD lpType, IN OUT LPBYTE lpData, IN OUT LPDWORD lpcbData ) { LONG lRet; WCHAR wValueName[MAX_PATH]; DWORD dwType; DWORD dwSize, dwOutSize; LPBYTE lpBigData = NULL; BOOL bText; __try { // Can't have this if (lpData && !lpcbData) { return ERROR_INVALID_PARAMETER; } // Convert the Value Name to WCHAR if (lpValueName) { if (MultiByteToWideChar( CP_ACP, 0, lpValueName, -1, (LPWSTR)wValueName, MAX_PATH) == 0) { return ERROR_INVALID_PARAMETER; } } else { wValueName[0] = L'\0'; } // // Get an initial size to use: if they sent us a buffer, we start with // that size, otherwise, we try a reasonable string length // if (lpData && *lpcbData) { dwSize = *lpcbData; } else { dwSize = MAX_PATH; } Retry: // // We can't touch their buffer unless we're going to succeed, so we // have to double buffer the call. // lpBigData = (LPBYTE) malloc(dwSize); if (!lpBigData) { DPFN( eDbgLevelError, szOutOfMemory); return ERROR_NOT_ENOUGH_MEMORY; } lRet = QueryValueW(hKey, wValueName, &dwType, lpBigData, &dwSize); // // We need to know if it's a string, since then we have to do extra // work to calculate the real size of the buffer etc. // bText = (SUCCESS(lRet) || (lRet == ERROR_MORE_DATA)) && ((dwType == REG_SZ) || (dwType == REG_EXPAND_SZ) || (dwType == REG_MULTI_SZ)); if (bText && (lRet == ERROR_MORE_DATA)) { // // The buffer wasn't big enough: we have to actually query the value // so we can get the real length in case it's DBCS, so we retry. // Note: dwSize now contains the required size, so it will succeed // this time around. // free(lpBigData); goto Retry; } // // Calculate the size of the output buffer: if it's text, it may be // a DBCS string, so we need to get the right size // if (bText) { dwOutSize = WideCharToMultiByte( CP_ACP, 0, (LPWSTR) lpBigData, dwSize / sizeof(WCHAR), NULL, NULL, 0, 0); } else { // It's not text, so we just use the actual size dwOutSize = dwSize; } // // If they gave us a buffer, we fill it in with what we got back // if (SUCCESS(lRet) && lpData) { // // Make sure we have enough space: lpcbData is guaranteed to be // valid since lpData is ok. // if (*lpcbData >= dwOutSize) { if (bText) { // // Convert the string back to ANSI. The buffer must have been big // enough since QueryValue succeeded. // Note: we have to give the exact size to convert otherwise we // use more of the buffer than absolutely necessary. Some apps, // like NHL 98 say they have a 256 byte buffer, but only give us // a 42 byte buffer. On NT, everything is done in place on that // buffer: so we always use more than the exact string length. // This shim gets around that because we use separate buffers. // if (WideCharToMultiByte( CP_ACP, 0, (LPWSTR)lpBigData, dwSize / 2, (LPSTR)lpData, dwOutSize, // *lpcbData, 0, 0) == 0) { free(lpBigData); return ERROR_INVALID_PARAMETER; } } else { MoveMemory(lpData, lpBigData, dwSize); } } else { lRet = ERROR_MORE_DATA; } } free(lpBigData); // Fill the output structures in if possible if (lpType) { *lpType = dwType; } if (lpcbData) { *lpcbData = dwOutSize; } } __except(EXCEPTION_EXECUTE_HANDLER) { DPFN( eDbgLevelError, "Exception occurred in QueryValueA"); lRet = ERROR_BAD_ARGUMENTS; } return lRet; } /*++ Function Description: Wrapper for RegQueryValueExW and RegQueryValue. We first see if the value is virtual because virtual values override actual values Algorithm: 1. Check if it's a virtual value and if so, spoof it 2. If it's not virtual, query registry normally Arguments: IN hKey - Handle to open key IN lpValueName - Value to query IN lpType - Type of data, eg: REG_SZ IN OUT lpData - Buffer for queries data IN OUT lpcbData - Size of input buffer/size of returned data Return Value: Error code or ERROR_SUCCESS History: 01/06/2000 linstev Created --*/ LONG CVirtualRegistry::QueryValueW( IN HKEY hKey, IN LPWSTR lpValueName, IN LPDWORD lpType, IN OUT LPBYTE lpData, IN OUT LPDWORD lpcbData ) { // Just a paranoid sanity check if (!hKey) { DPFN( eDbgLevelError, "NULL handle passed to OpenKeyW"); return ERROR_INVALID_HANDLE; } LONG lRet; OPENKEY *key; VIRTUALKEY *vkey; VIRTUALVAL *vvalue; DWORD dwType; WCHAR* lpBuffer; DWORD dwStringSize; DWORD cbData = 0; BOOL bDataPresent = TRUE; __try { lRet = ERROR_FILE_NOT_FOUND; // Can't have this if (lpData && !lpcbData) { return ERROR_INVALID_PARAMETER; } // We always need the type if (!lpType) { lpType = &dwType; } // Do we want to spoof this key = FindOpenKey(hKey); vkey = key ? key->vkey : NULL; vvalue = vkey ? vkey->FindValue(lpValueName) : NULL; if (key && vkey && vvalue && (vvalue->cbData != 0 || vvalue->pfnQueryValue)) { // Use the callback if available if (vvalue->pfnQueryValue) { // // Note, the callback puts it's values into the vvalue field, // just as if we knew it all along. In addition, we can fail // the call... but that doesn't allow us defer to the original // value. // lRet = (*vvalue->pfnQueryValue)( key, vkey, vvalue); } else { lRet = ERROR_SUCCESS; } // Copy the virtual value into the buffer if (SUCCESS(lRet)) { *lpType = vvalue->dwType; if (lpData) { if (vvalue->cbData <= *lpcbData) { MoveMemory(lpData, vvalue->lpData, vvalue->cbData); } else { lRet = ERROR_MORE_DATA; } } if (lpcbData) { *lpcbData = vvalue->cbData; } } } else if (key && vkey && vvalue && (vvalue->cbData == 0)) { bDataPresent = FALSE; lRet = ERROR_SUCCESS; } else { // Save the size of the data buffer. if (lpcbData) { cbData = *lpcbData; } // // Get the key normally as if it weren't virtual at all // lRet = ORIGINAL_API(RegQueryValueExW)( hKey, lpValueName, NULL, lpType, lpData, lpcbData); // // Some apps store bogus data beyond the end of the string. // Attempt to fix. // // Only try this if it's a string. if (lRet == ERROR_MORE_DATA && (*lpType == REG_SZ || *lpType == REG_EXPAND_SZ)) { // // Create a buffer large enough to hold the data // We read from lpcbData here, but this should be ok, // since RegQueryValueEx shouldn't return ERROR_MORE_DATA // if lpcbData is NULL. // lpBuffer = (WCHAR*)malloc(*lpcbData); if (lpBuffer) { // Requery with new buffer. lRet = ORIGINAL_API(RegQueryValueExW)( hKey, lpValueName, NULL, lpType, (BYTE*)lpBuffer, lpcbData); if (lRet == ERROR_SUCCESS) { dwStringSize = wcslen(lpBuffer)*sizeof(WCHAR) + sizeof(WCHAR); // If size of dest buffer can hold the string . . . if (cbData >= dwStringSize) { DPFN(eDbgLevelInfo, "\tTrimming data beyond end of string in Query for %S", lpValueName); // Copy the data to the caller's buffer, CopyMemory(lpData, lpBuffer, dwStringSize); *lpcbData = dwStringSize; } else { // Set *lpcbData to the correct size, and return more data error *lpcbData = dwStringSize; lRet = ERROR_MORE_DATA; } } free(lpBuffer); } } // // Here's another hack for us: if the value is NULL or an empty string // Win9x defers to QueryValue... // if (g_bWin9x && (lRet == ERROR_FILE_NOT_FOUND) && (!lpValueName || !lpValueName[0])) { lRet = ORIGINAL_API(RegQueryValueW)( hKey, NULL, (LPWSTR)lpData, (PLONG)lpcbData); if (SUCCESS(lRet)) { *lpType = REG_SZ; } } } DPFN( ELEVEL(lRet), "%08lx=QueryValueW(Key=%04lx)", lRet, hKey); if (key) { DPFN( ELEVEL(lRet), " Path=%S\\%S", key->wzPath, lpValueName); } else { DPFN( ELEVEL(lRet), " Value=%S", lpValueName); } if (SUCCESS(lRet) && ((*lpType == REG_SZ) || (*lpType == REG_EXPAND_SZ))&& (bDataPresent == TRUE)) { DPFN( eDbgLevelInfo, " Result=%S", lpData); } } __except(EXCEPTION_EXECUTE_HANDLER) { DPFN( eDbgLevelError, "Exception occurred in QueryValueW"); lRet = ERROR_BAD_ARGUMENTS; } return lRet; } /*++ Function Description: Wrapper for RegEnumKeyA Call out to EnumKeyW and convert the name back to ANSI. Note we pass the size given to us (in lpcbName) down to EnumKeyW in case the lpName buffer is too small. Algoritm: 1. EnumKeyW with a large buffer 2. Convert the key back to ansi if it succeeds Arguments: IN hKey - Handle to open key IN dwIndex - Index to enumerate OUT lpName - Name of subkey IN OUT lpcbName - Size of name buffer Return Value: Error code or ERROR_SUCCESS History: 01/06/2000 linstev Created --*/ LONG CVirtualRegistry::EnumKeyA( IN HKEY hKey, IN DWORD dwIndex, OUT LPSTR lpName, OUT LPDWORD lpcbName ) { LONG lRet = ERROR_NO_MORE_ITEMS; WCHAR wKey[MAX_PATH + 1]; DWORD dwcbName = MAX_PATH + 1; __try { lRet = EnumKeyW(hKey, dwIndex, (LPWSTR)wKey, &dwcbName); if (SUCCESS(lRet)) { DWORD dwByte = WideCharToMultiByte( CP_ACP, 0, (LPWSTR)wKey, dwcbName, (LPSTR)lpName, *lpcbName, 0, 0); lpName[dwByte] = '\0'; *lpcbName = dwByte; if (!dwByte) { lRet = GetLastError(); // Generate a registry error code if (lRet == ERROR_INSUFFICIENT_BUFFER) { lRet = ERROR_MORE_DATA; } } } } __except(EXCEPTION_EXECUTE_HANDLER) { lRet = ERROR_BAD_ARGUMENTS; } return lRet; } /*++ Function Description: Wrapper for RegEnumKeyW. Algorithm: 1. Build enumeration list, if necessary. 2. Iterate through enumeration list until index is found. Arguments: IN hKey - Handle to open key IN dwIndex - Index to enumerate OUT lpName - Name of subkey OUT lpcbName - Size of name buffer Return Value: Error code or ERROR_SUCCESS History: 01/06/2000 linstev Created --*/ LONG CVirtualRegistry::EnumKeyW( HKEY hKey, DWORD dwIndex, LPWSTR lpName, LPDWORD lpcbName ) { LONG lRet = ERROR_BAD_ARGUMENTS; OPENKEY *key; ENUMENTRY *enumkey; DWORD i; __try { key = FindOpenKey(hKey); if (key) { if (key->enumKeys == NULL) { key->BuildEnumList(); } i = 0; enumkey = key->enumKeys; while (enumkey) { if (dwIndex == i) { DWORD len = wcslen(enumkey->wzName); if (*lpcbName > len) { HRESULT hr; hr = StringCchCopyW(lpName, *lpcbName, enumkey->wzName); if (FAILED(hr)) { lRet = ERROR_MORE_DATA; } else { *lpcbName = len; lRet = ERROR_SUCCESS; } } else { lRet = ERROR_MORE_DATA; } break; } i++; enumkey = enumkey->next; } // No key found for index if (enumkey == NULL) { lRet = ERROR_NO_MORE_ITEMS; } } else { lRet = ORIGINAL_API(RegEnumKeyExW)( hKey, dwIndex, lpName, lpcbName, 0, 0, 0, 0); } } __except(EXCEPTION_EXECUTE_HANDLER) { lRet = ERROR_BAD_ARGUMENTS; } DPFN( ELEVEL(lRet), "%08lx=EnumKeyW(hKey=%04lx,dwIndex=%d)", lRet, hKey, dwIndex); if (SUCCESS(lRet)) { DPFN( eDbgLevelInfo, " Result=%S", lpName); } return lRet; } /*++ Function Description: Wrapper for RegEnumValueA. Thunks to QueryValueW. This function calls QueryValueA to get the data out of the value, so most error handling is done by QueryValueA. Arguments: IN hKey - Handle to open key IN dwIndex - Index of value to enumerate IN OUT lpValueName - Value name buffer IN OUT lpcbValueName - Sizeof value name buffer IN OUT lpType - Type of data, eg: REG_SZ IN OUT lpData - Buffer for queries data IN OUT lpcbData - Size of input buffer/size of returned data Return Value: Error code or ERROR_SUCCESS History: 01/06/2000 linstev Created --*/ LONG CVirtualRegistry::EnumValueA( IN HKEY hKey, IN DWORD dwIndex, IN OUT LPSTR lpValueName, IN OUT LPDWORD lpcbValueName, IN OUT LPDWORD lpType, IN OUT LPBYTE lpData, IN OUT LPDWORD lpcbData ) { LONG lRet; WCHAR wzValueName[MAX_PATH]; DWORD dwValNameSize; __try { dwValNameSize = MAX_PATH; lRet = EnumValueW(hKey, dwIndex, wzValueName, &dwValNameSize, NULL, NULL, NULL); if (lRet == ERROR_SUCCESS) { dwValNameSize = WideCharToMultiByte( CP_ACP, 0, wzValueName, -1, lpValueName, *lpcbValueName, NULL, NULL); if (dwValNameSize != 0) { // Just do a normal query value for the remaining parameters. lRet = QueryValueA(hKey, lpValueName, lpType, lpData, lpcbData); } else { if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { lRet = ERROR_MORE_DATA; *lpcbValueName = WideCharToMultiByte( CP_ACP, 0, wzValueName, -1, NULL, 0, NULL, NULL); } else { lRet = GetLastError(); } } } } __except(EXCEPTION_EXECUTE_HANDLER) { lRet = ERROR_BAD_ARGUMENTS; } return lRet; } /*++ Function Description: Wrapper for RegEnumValueW. This function calls QueryValueW to get the data out of the value, so most error handling is done by QueryValueW. Algorithm: 1. Check if key has virtual values, if not default to RegEnumValueW. 2. Build enumeration list, if necessary. 3. Iterate through enumeration list until index is found. Arguments: IN hKey - Handle to open key IN dwIndex - Index of value to enumerate IN OUT lpValueName - Value name buffer IN OUT lpcbValueName - Sizeof value name buffer IN OUT lpType - Type of data, eg: REG_SZ IN OUT lpData - Buffer for queries data IN OUT lpcbData - Size of input buffer/size of returned data Return Value: Error code or ERROR_SUCCESS History: 01/06/2000 linstev Created --*/ LONG CVirtualRegistry::EnumValueW( IN HKEY hKey, IN DWORD dwIndex, IN OUT LPWSTR lpValueName, IN OUT LPDWORD lpcbValueName, IN OUT LPDWORD lpType, IN OUT LPBYTE lpData, IN OUT LPDWORD lpcbData ) { LONG lRet; OPENKEY *key; ENUMENTRY *enumval; // Check if it has virtual values . . . key = FindOpenKey(hKey); if (key && key->vkey && key->vkey->values) { DWORD i = 0; if (key->enumValues == NULL) { key->BuildEnumList(); } enumval = key->enumValues; lRet = ERROR_NO_MORE_ITEMS; while (enumval) { if (dwIndex == i) { DWORD len = wcslen(enumval->wzName); if (*lpcbValueName > len) { // Copy the name and query the data HRESULT hr = StringCchCopyW(lpValueName, *lpcbValueName, enumval->wzName); if (FAILED(hr)) { lRet = ERROR_MORE_DATA; } else { *lpcbValueName = len; lRet = QueryValueW( hKey, enumval->wzName, lpType, lpData, lpcbData); } } else { // The buffer given for name wasn't big enough lRet = ERROR_MORE_DATA; } break; } i++; enumval = enumval->next; } } // No virtual values, fall through to original API. else { lRet = ORIGINAL_API(RegEnumValueW)( hKey, dwIndex, lpValueName, lpcbValueName, 0, lpType, lpData, lpcbData); } DPFN( ELEVEL(lRet), "%08lx=EnumValueW(hKey=%04lx,dwIndex=%d)", lRet, hKey, dwIndex); if (SUCCESS(lRet)) { DPFN( eDbgLevelInfo, " Result=%S", lpValueName); } return lRet; } /*++ Function Description: Wrapper for RegQueryInfoKeyA. We don't need to worry about the conversion of ansi->unicode in the sizes of values and keys because they are defined as string lengths. Algorithm: 1. Convert the class string to unicode 2. Call QueryInfoW Arguments: IN hKey - handle to key to query OUT lpClass - address of buffer for class string OUT lpcbClass - address of size of class string buffer OUT lpReserved - reserved OUT lpcSubKeys - address of buffer for number of subkeys OUT lpcbMaxSubKeyLen - address of buffer for longest subkey OUT lpcbMaxClassLen - address of buffer for longest class string length OUT lpcValues - address of buffer for number of value entries OUT lpcbMaxValueNameLen - address of buffer for longest value name length OUT lpcbMaxValueLen - address of buffer for longest value data length OUT lpcbSecurityDescriptor - address of buffer for security descriptor length OUT lpftLastWriteTime - address of buffer for last write time Return Value: Error code or ERROR_SUCCESS History: 01/06/2000 linstev Created --*/ LONG CVirtualRegistry::QueryInfoA( IN HKEY hKey, OUT LPSTR lpClass, OUT LPDWORD lpcbClass, OUT LPDWORD lpReserved, OUT LPDWORD lpcSubKeys, OUT LPDWORD lpcbMaxSubKeyLen, OUT LPDWORD lpcbMaxClassLen, OUT LPDWORD lpcValues, OUT LPDWORD lpcbMaxValueNameLen, OUT LPDWORD lpcbMaxValueLen, OUT LPDWORD lpcbSecurityDescriptor, OUT PFILETIME lpftLastWriteTime ) { LONG lRet; if (lpClass && !lpcbClass) { LOGN( eDbgLevelError, "[QueryInfoA] NULL passed for lpClass but not lpcbClass. Fixing."); lpcbClass = NULL; } if (lpClass && lpcbClass) { WCHAR wClass[MAX_PATH]; DWORD dwSize; if (MultiByteToWideChar( CP_ACP, 0, lpClass, -1, (LPWSTR)wClass, MAX_PATH) == 0) { return ERROR_INVALID_PARAMETER; } dwSize = *lpcbClass * 2; lRet = QueryInfoW( hKey, wClass, &dwSize, lpReserved, lpcSubKeys, lpcbMaxSubKeyLen, lpcbMaxClassLen, lpcValues, lpcbMaxValueNameLen, lpcbMaxValueLen, lpcbSecurityDescriptor, lpftLastWriteTime); if (SUCCESS(lRet)) { if (WideCharToMultiByte( CP_ACP, 0, (LPWSTR)wClass, dwSize, (LPSTR)lpClass, *lpcbClass, 0, 0) == 0) { return ERROR_INVALID_PARAMETER; } } *lpcbClass = dwSize / 2; } else { lRet = QueryInfoW( hKey, NULL, NULL, lpReserved, lpcSubKeys, lpcbMaxSubKeyLen, lpcbMaxClassLen, lpcValues, lpcbMaxValueNameLen, lpcbMaxValueLen, lpcbSecurityDescriptor, lpftLastWriteTime); } return lRet; } /*++ Function Description: Wrapper for RegQueryInfoKeyW. Algorithm: 1. Revert to the old API if the key isn't virtual 2. Calculate all the virtual key and value name lengths by going through them individually. 3. Add all non-virtual key and value's that don't have overriding virtual's. Arguments: IN hKey - handle to key to query OUT lpClass - address of buffer for class string OUT lpcbClass - address of size of class string buffer OUT lpReserved - reserved OUT lpcSubKeys - address of buffer for number of subkeys OUT lpcbMaxSubKeyLen - address of buffer for longest subkey OUT lpcbMaxClassLen - address of buffer for longest class string length OUT lpcValues - address of buffer for number of value entries OUT lpcbMaxValueNameLen - address of buffer for longest value name length OUT lpcbMaxValueLen - address of buffer for longest value data length OUT lpcbSecurityDescriptor - address of buffer for security descriptor length OUT lpftLastWriteTime - address of buffer for last write time Return Value: Error code or ERROR_SUCCESS History: 01/06/2000 linstev Created 08/03/2001 mikrause Added support for counting both virtual & non-virtual keys & values. --*/ LONG CVirtualRegistry::QueryInfoW( IN HKEY hKey, OUT LPWSTR lpClass, OUT LPDWORD lpcbClass, OUT LPDWORD lpReserved, OUT LPDWORD lpcSubKeys, OUT LPDWORD lpcbMaxSubKeyLen, OUT LPDWORD lpcbMaxClassLen, OUT LPDWORD lpcValues, OUT LPDWORD lpcbMaxValueNameLen, OUT LPDWORD lpcbMaxValueLen, OUT LPDWORD lpcbSecurityDescriptor, OUT PFILETIME lpftLastWriteTime ) { LONG lRet; OPENKEY *key; DWORD cbData = 0; ENUMENTRY *enumkey; ENUMENTRY *enumval; if (lpClass && !lpcbClass) { LOGN( eDbgLevelError, "[QueryInfoW] NULL passed for lpClass but not lpcbClass. Fixing."); lpcbClass = NULL; } key = FindOpenKey(hKey); if (key) { if (lpClass) { lpClass[0] = L'\0'; } if (lpcbClass) { *lpcbClass = 0; } if (lpcbMaxClassLen) { *lpcbMaxClassLen = 0; } if (lpReserved) { *lpReserved = 0; } if (lpcSubKeys || lpcbMaxSubKeyLen) { DWORD i = 0; DWORD len = 0; // Count virtual keys. if (!key->enumKeys) { key->BuildEnumList(); } enumkey = key->enumKeys; while (enumkey) { i++; len = max(len, wcslen(enumkey->wzName)); enumkey = enumkey->next; } if (lpcSubKeys) { *lpcSubKeys = i; } if (lpcbMaxSubKeyLen) { *lpcbMaxSubKeyLen = len; } } if (lpcValues || lpcbMaxValueNameLen || lpcbMaxValueLen) { // Check if this key has virtual values or is virtual. if (key->bVirtual || (key->vkey && key->vkey->values)) { DWORD i = 0; DWORD lenA = 0, lenB = 0; if (key->enumValues == NULL) { key->BuildEnumList(); } enumval = key->enumValues; while (enumval) { i++; QueryValueW(key->hkOpen, enumval->wzName, NULL, NULL, &cbData); lenA = max(lenA, cbData); lenB = max(lenB, wcslen(enumval->wzName)); enumval = enumval->next; } if (lpcValues) { *lpcValues = i; } if (lpcbMaxValueLen) { *lpcbMaxValueLen = lenA; } if (lpcbMaxValueNameLen) { *lpcbMaxValueNameLen = lenB; } } // No virtual values, do a normal query. else { lRet = ORIGINAL_API(RegQueryInfoKeyW)( key->hkOpen, NULL, NULL, NULL, NULL, NULL, NULL, lpcValues, lpcbMaxValueNameLen, lpcbMaxValueLen, NULL, lpftLastWriteTime); } } if (lpcbSecurityDescriptor) { *lpcbSecurityDescriptor = NULL; } lRet = ERROR_SUCCESS; } else { lRet = ORIGINAL_API(RegQueryInfoKeyW)( hKey, lpClass, lpcbClass, lpReserved, lpcSubKeys, lpcbMaxSubKeyLen, lpcbMaxClassLen, lpcValues, lpcbMaxValueNameLen, lpcbMaxValueLen, lpcbSecurityDescriptor, lpftLastWriteTime); } DPFN( ELEVEL(lRet), "%08lx=QueryInfoW(hKey=%04lx)", lRet, hKey); if (key) { DPFN( ELEVEL(lRet), " Path=%S", key->wzPath); } return lRet; } /*++ Function Description: Wrapper for RegSetValueA. Algorithm: 1. Convert value name and data (if string) to Unicode. 2. Call SetValueW Arguments: hKey - Key to set value in. lpValueName - Name of value to set. dwType - Type of value (string, DWORD, etc.) lpData - Buffer containing data to write. cbData - Size of lpData in bytes. Return Value: ERROR_SUCCESS on success, failure code otherwise. History: 08/07/2001 mikrause Created --*/ LONG CVirtualRegistry::SetValueA( HKEY hKey, LPCSTR lpValueName, DWORD dwType, CONST BYTE* lpData, DWORD cbData ) { LONG lRet; DWORD dwSize; WCHAR* wszValName = NULL; BYTE* lpExpandedData = (BYTE*)lpData; if (lpValueName != NULL) { dwSize = (DWORD)(lstrlenA(lpValueName) + 1); dwSize *= sizeof(WCHAR); wszValName = (WCHAR*)malloc(dwSize); if (wszValName == NULL) { DPFN( eDbgLevelError, szOutOfMemory); return ERROR_NOT_ENOUGH_MEMORY; } if (MultiByteToWideChar( CP_ACP, 0, lpValueName, -1, (LPWSTR)wszValName, dwSize/sizeof(WCHAR)) == 0) { return ERROR_INVALID_PARAMETER; } } dwSize = cbData; // // Expand text buffers // if (lpData && (dwType == REG_SZ || dwType == REG_EXPAND_SZ || dwType == REG_MULTI_SZ)) { if ((dwType != REG_MULTI_SZ) && g_bWin9x) { dwSize = (DWORD)(lstrlenA((char*)lpData) + 1); } lpExpandedData = (BYTE*) malloc(dwSize * sizeof(WCHAR)); if (lpExpandedData == NULL) { if (wszValName) { free(wszValName); } DPFN( eDbgLevelError, szOutOfMemory); return ERROR_NOT_ENOUGH_MEMORY; } if (MultiByteToWideChar( CP_ACP, 0, (LPCSTR)lpData, dwSize, (LPWSTR)lpExpandedData, dwSize) == 0) { return ERROR_INVALID_PARAMETER; } dwSize = dwSize * sizeof(WCHAR); } lRet = SetValueW(hKey, wszValName, dwType, lpExpandedData, dwSize); if (lpExpandedData != lpData) { free(lpExpandedData); } if (wszValName) { free(wszValName); } return lRet; } /*++ Function Description: Wrapper for RegSetValueW. Also protects for non-zero buffer length with zero buffer AV. Algorithm: 1. If non-protected key, write to registry using RegSetValueW Arguments: hKey - Key to set value in. lpValueName - Name of value to set. dwType - Type of value (string, DWORD, etc.) lpData - Buffer containing data to write. cbData - Size of lpData in bytes. Return Value: ERROR_SUCCESS on success, failure code otherwise. History: 08/07/2001 mikrause Created --*/ LONG CVirtualRegistry::SetValueW( HKEY hKey, LPCWSTR lpValueName, DWORD dwType, CONST BYTE* lpData, DWORD cbData ) { LONG lRet; // Just a paranoid sanity check if (!hKey) { DPFN( eDbgLevelError, "NULL handle passed to SetValueW"); return ERROR_INVALID_HANDLE; } __try { lRet = ERROR_FILE_NOT_FOUND; // To duplicate Win95/win98 behavior automatically override // the cbData with the actual length of the lpData for REG_SZ. if (g_bWin9x && lpData && ((dwType == REG_SZ) || (dwType == REG_EXPAND_SZ))) { cbData = (wcslen((WCHAR *)lpData)+1)*sizeof(WCHAR); } VIRTUALKEY *vkey; VIRTUALVAL *vvalue; OPENKEY* key = FindOpenKey(hKey); if (key) { // Check if we should execute a custom action. vkey = key->vkey; vvalue = vkey ? vkey->FindValue(lpValueName) : NULL; if (vkey && vvalue && vvalue->pfnSetValue) { lRet = vvalue->pfnSetValue(key, vkey, vvalue, dwType, lpData,cbData); } else { // No custom action, just set value as normal. lRet = ORIGINAL_API(RegSetValueExW)( hKey, lpValueName, 0, dwType, lpData, cbData); } // Possible change in enumeration data, flush lists. if (lRet == ERROR_SUCCESS) { key->FlushEnumList(); } } // No key, fall through to original API else { lRet = ORIGINAL_API(RegSetValueExW)( hKey, lpValueName, 0, dwType, lpData, cbData); } DPFN( ELEVEL(lRet), "%08lx=SetValueW(Key=%04lx)", lRet, hKey); if (key) { DPFN( ELEVEL(lRet), " Path=%S\\%S", key->wzPath, lpValueName); } else { DPFN( ELEVEL(lRet), " Value=%S", lpValueName); } if (SUCCESS(lRet) && ((dwType == REG_SZ) || (dwType == REG_EXPAND_SZ))) { DPFN( eDbgLevelInfo, " Result=%S", lpData); } } __except(EXCEPTION_EXECUTE_HANDLER) { DPFN( eDbgLevelError, "Exception occurred in SetValueW"); lRet = ERROR_BAD_ARGUMENTS; } return lRet; } /*++ Function Description: Wrapper for RegDeleteKeyA. Algorithm: 1. Convert key name to Unicode. 2. Call DeleteKeyW Arguments: hKey - Key that contains subkey to delete. lpSubKey - Key name to delete. Return Value: ERROR_SUCCESS on success, failure code otherwise. History: 08/07/2001 mikrause Created --*/ LONG CVirtualRegistry::DeleteKeyA( IN HKEY hKey, IN LPCSTR lpSubKey ) { LONG lRet; DWORD dwSize; WCHAR* wszSubKey = NULL; dwSize = (DWORD)(lstrlenA(lpSubKey) + 1); dwSize *= sizeof(WCHAR); wszSubKey = (WCHAR*)malloc(dwSize); if (wszSubKey == NULL) { DPFN( eDbgLevelError, szOutOfMemory); return ERROR_NOT_ENOUGH_MEMORY; } if (MultiByteToWideChar( CP_ACP, 0, lpSubKey, -1, (LPWSTR)wszSubKey, dwSize/sizeof(WCHAR)) == 0) { return ERROR_INVALID_PARAMETER; } lRet = DeleteKeyW(hKey, wszSubKey); free(wszSubKey); return lRet; } /*++ Function Description: Wrapper for DeleteKeyW. Algorithm: 1. If key is not protected, delete key. 2. If in 9x compat mode, recursively delete all subkeys. Arguments: hKey - Key to that contains subkey to delete. lpSubKey - Name of key to delete Return Value: ERROR_SUCCESS on success, failure code otherwise. History: 08/07/2001 mikrause Created --*/ LONG CVirtualRegistry::DeleteKeyW( IN HKEY hKey, IN LPCWSTR lpSubKey ) { LONG hRet; OPENKEY* key = FindOpenKey(hKey); LPWSTR wzPath = NULL; BOOL bProtected; // Key not found, assume it's a root key. if (!key) { DPFN( eDbgLevelInfo, "Key not found!"); wzPath = MakePath(hKey, 0, lpSubKey); if (!wzPath) { DPFN(eDbgLevelError, szOutOfMemory); return ERROR_NOT_ENOUGH_MEMORY; } DPFN( eDbgLevelInfo, "Using path %S", wzPath); } else if (lpSubKey) { DWORD dwSize = wcslen(key->wzPath) + wcslen(L"\\") + wcslen(lpSubKey) + 1; wzPath = (LPWSTR) malloc(dwSize * sizeof(WCHAR)); if (!wzPath) { DPFN(eDbgLevelError, szOutOfMemory); return ERROR_NOT_ENOUGH_MEMORY; } ZeroMemory(wzPath, dwSize); StringCchCopyW(wzPath, dwSize, key->wzPath); StringCchCatW(wzPath, dwSize, L"\\"); StringCchCatW(wzPath, dwSize, lpSubKey); } bProtected = (key && CheckProtected(key->wzPath)) || (wzPath && CheckProtected(wzPath)); if (!bProtected) { if (g_bWin9x) { // // Find out whether hKey has any subkeys under it or not. // If not, then proceed as normal. // If yes, recursively delete the subkeys under it // Then proceed as normal. // DWORD cSize = 0; WCHAR lpSubKeyName[MAX_PATH]; HKEY hSubKey; DPFN( eDbgLevelInfo, "RegDeleteKeyW called with hKey: %x, SubKey: %S", hKey, lpSubKey); hRet = ORIGINAL_API(RegOpenKeyExW)( hKey, lpSubKey, 0, KEY_ENUMERATE_SUB_KEYS, &hSubKey); if (SUCCESS(hRet)) { for (;;) { cSize = MAX_PATH; hRet = ORIGINAL_API(RegEnumKeyExW)( hSubKey, 0, lpSubKeyName, &cSize, NULL, NULL, NULL, NULL ); if (SUCCESS(hRet)) { LOGN( eDbgLevelInfo, "[DeleteKeyW] Deleting subkey %S for key %S.", lpSubKeyName, lpSubKey); hRet = DeleteKeyW( hSubKey, lpSubKeyName); if (SUCCESS(hRet)) { LOGN( eDbgLevelInfo, "[DeleteKeyW] subkey %S was deleted.",lpSubKeyName); } else { LOGN( eDbgLevelInfo, "[DeleteKeyW] subkey %S was not deleted.",lpSubKeyName); break; } } else { DPFN( eDbgLevelInfo, "[DeleteKeyW] No more subkey under key %S.",lpSubKey); break; } } ORIGINAL_API(RegCloseKey)(hSubKey); } } DPFN( eDbgLevelInfo, "[RegDeleteKeyW] Deleting subkey %S.",lpSubKey); hRet = ORIGINAL_API(RegDeleteKeyW)( hKey, lpSubKey); } else { // Protected, just say it succeeded hRet = ERROR_SUCCESS; } if (wzPath) { free(wzPath); } // Possible change in enumeration data, flush lists. FlushEnumLists(); return hRet; } /*++ Function Description: Wrapper for RegCloseKey. Note that we make sure we know about the key before closing it. Algorithm: 1. Run the list of open keys and free if found 2. Close the key Arguments: IN hKey - Handle to open key to close Return Value: Error code or ERROR_SUCCESS History: 01/06/2000 linstev Created --*/ LONG CVirtualRegistry::CloseKey( IN HKEY hKey ) { OPENKEY *key = OpenKeys, *last = NULL; LONG lRet; __try { lRet = ERROR_INVALID_HANDLE; while (key) { if (key->hkOpen == hKey) { if (last) { last->next = key->next; } else { OpenKeys = key->next; } lRet = ORIGINAL_API(RegCloseKey)(hKey); free(key->wzPath); free(key); break; } last = key; key = key->next; } if (key == NULL) { RegCloseKey(hKey); } DPFN( ELEVEL(lRet), "%08lx=CloseKey(Key=%04lx)", lRet, hKey); } __except(EXCEPTION_EXECUTE_HANDLER) { lRet = ERROR_INVALID_HANDLE; } return lRet; } /*++ Pass through to virtual registry to handle call. --*/ LONG APIHOOK(RegCreateKeyA)( HKEY hKey, LPCSTR lpSubKey, PHKEY phkResult ) { CRegLock Lock; return VRegistry.OpenKeyA( hKey, lpSubKey, 0, REG_OPTION_NON_VOLATILE, MAXIMUM_ALLOWED, NULL, phkResult, 0, TRUE); } /*++ Pass through to virtual registry to handle call. --*/ LONG APIHOOK(RegCreateKeyW)( HKEY hKey, LPCWSTR lpSubKey, PHKEY phkResult ) { CRegLock Lock; return VRegistry.OpenKeyW( hKey, lpSubKey, 0, REG_OPTION_NON_VOLATILE, MAXIMUM_ALLOWED, NULL, phkResult, 0, TRUE); } /*++ Pass through to virtual registry to handle call. --*/ LONG APIHOOK(RegCreateKeyExA)( HKEY hKey, LPCSTR lpSubKey, DWORD /* Reserved */, LPSTR lpClass, DWORD dwOptions, REGSAM samDesired, LPSECURITY_ATTRIBUTES lpSecurityAttributes, PHKEY phkResult, LPDWORD lpdwDisposition ) { CRegLock Lock; return VRegistry.OpenKeyA( hKey, lpSubKey, lpClass, dwOptions, samDesired, lpSecurityAttributes, phkResult, lpdwDisposition, TRUE); } /*++ Pass through to virtual registry to handle call. --*/ LONG APIHOOK(RegCreateKeyExW)( HKEY hKey, LPCWSTR lpSubKey, DWORD /* Reserved */, LPWSTR lpClass, DWORD dwOptions, REGSAM samDesired, LPSECURITY_ATTRIBUTES lpSecurityAttributes, PHKEY phkResult, LPDWORD lpdwDisposition ) { CRegLock Lock; return VRegistry.OpenKeyW( hKey, lpSubKey, lpClass, dwOptions, samDesired, lpSecurityAttributes, phkResult, lpdwDisposition, TRUE); } /*++ Pass through to virtual registry to handle call. --*/ LONG APIHOOK(RegOpenKeyA)( HKEY hKey, LPCSTR lpSubKey, PHKEY phkResult ) { CRegLock Lock; return VRegistry.OpenKeyA(hKey, lpSubKey, 0, 0, MAXIMUM_ALLOWED, NULL, phkResult, 0, FALSE); } /*++ Pass through to virtual registry to handle call. --*/ LONG APIHOOK(RegOpenKeyW)( HKEY hKey, LPCWSTR lpSubKey, PHKEY phkResult ) { CRegLock Lock; return VRegistry.OpenKeyW(hKey, lpSubKey, 0, 0, MAXIMUM_ALLOWED, NULL, phkResult, 0, FALSE); } /*++ Pass through to virtual registry to handle call. --*/ LONG APIHOOK(RegOpenKeyExA)( HKEY hKey, LPCSTR lpSubKey, DWORD /* ulOptions */, REGSAM samDesired, PHKEY phkResult ) { CRegLock Lock; return VRegistry.OpenKeyA(hKey, lpSubKey, 0, 0, samDesired, NULL, phkResult, 0, FALSE); } /*++ Pass through to virtual registry to handle call. --*/ LONG APIHOOK(RegOpenKeyExW)( HKEY hKey, LPCWSTR lpSubKey, DWORD /* ulOptions */, REGSAM samDesired, PHKEY phkResult ) { CRegLock Lock; return VRegistry.OpenKeyW(hKey, lpSubKey, 0, 0, samDesired, NULL, phkResult, 0, FALSE); } /*++ Not yet implemented --*/ LONG APIHOOK(RegQueryValueA)( HKEY hKey, LPCSTR lpSubKey, LPSTR lpData, PLONG lpcbData ) { CRegLock Lock; return ORIGINAL_API(RegQueryValueA)( hKey, lpSubKey, lpData, lpcbData); } /*++ Not yet implemented --*/ LONG APIHOOK(RegQueryValueW)( HKEY hKey, LPCWSTR lpSubKey, LPWSTR lpData, PLONG lpcbData ) { CRegLock Lock; return ORIGINAL_API(RegQueryValueW)( hKey, lpSubKey, lpData, lpcbData); } /*++ Pass through to virtual registry to handle call. --*/ LONG APIHOOK(RegQueryValueExA)( HKEY hKey, LPSTR lpValueName, LPDWORD /* lpReserved */, LPDWORD lpType, LPBYTE lpData, LPDWORD lpcbData ) { CRegLock Lock; return VRegistry.QueryValueA(hKey, lpValueName, lpType, lpData, lpcbData); } /*++ Pass through to virtual registry to handle call. --*/ LONG APIHOOK(RegQueryValueExW)( HKEY hKey, LPWSTR lpValueName, LPDWORD /* lpReserved */, LPDWORD lpType, LPBYTE lpData, LPDWORD lpcbData ) { CRegLock Lock; return VRegistry.QueryValueW(hKey, lpValueName, lpType, lpData, lpcbData); } /*++ Pass through to virtual registry to handle call. --*/ LONG APIHOOK(RegCloseKey)(HKEY hKey) { CRegLock Lock; return VRegistry.CloseKey(hKey); } /*++ Pass through to virtual registry to handle call. --*/ LONG APIHOOK(RegEnumValueA)( HKEY hKey, DWORD dwIndex, LPSTR lpValueName, LPDWORD lpcbValueName, LPDWORD /* lpReserved */, LPDWORD lpType, LPBYTE lpData, LPDWORD lpcbData ) { CRegLock Lock; return VRegistry.EnumValueA( hKey, dwIndex, lpValueName, lpcbValueName, lpType, lpData, lpcbData); } /*++ Pass through to virtual registry to handle call. --*/ LONG APIHOOK(RegEnumValueW)( HKEY hKey, DWORD dwIndex, LPWSTR lpValueName, LPDWORD lpcbValueName, LPDWORD /* lpReserved */, LPDWORD lpType, LPBYTE lpData, LPDWORD lpcbData ) { CRegLock Lock; return VRegistry.EnumValueW( hKey, dwIndex, lpValueName, lpcbValueName, lpType, lpData, lpcbData); } /*++ Pass through to virtual registry to handle call. --*/ LONG APIHOOK(RegEnumKeyExA)( HKEY hKey, DWORD dwIndex, LPSTR lpName, LPDWORD lpcbName, LPDWORD /* lpReserved */, LPSTR /* lpClass */, LPDWORD /* lpcbClass */, PFILETIME /* lpftLastWriteTime */ ) { CRegLock Lock; return VRegistry.EnumKeyA(hKey, dwIndex, lpName, lpcbName); } /*++ Pass through to virtual registry to handle call. --*/ LONG APIHOOK(RegEnumKeyExW)( HKEY hKey, DWORD dwIndex, LPWSTR lpName, LPDWORD lpcbName, LPDWORD /* lpReserved */, LPWSTR /* lpClass */, LPDWORD /* lpcbClass */, PFILETIME /* lpftLastWriteTime */ ) { CRegLock Lock; return VRegistry.EnumKeyW(hKey, dwIndex, lpName, lpcbName); } /*++ Pass through to virtual registry to handle call. --*/ LONG APIHOOK(RegEnumKeyA)( HKEY hKey, DWORD dwIndex, LPSTR lpName, DWORD cbName ) { CRegLock Lock; return VRegistry.EnumKeyA(hKey, dwIndex, lpName, &cbName); } /*++ Calls down to RegEnumKeyExW --*/ LONG APIHOOK(RegEnumKeyW)( HKEY hKey, DWORD dwIndex, LPWSTR lpName, DWORD cbName ) { CRegLock Lock; return VRegistry.EnumKeyW(hKey, dwIndex, lpName, &cbName); } /*++ Pass through to virtual registry to handle call. --*/ LONG APIHOOK(RegQueryInfoKeyW)( HKEY hKey, LPWSTR lpClass, LPDWORD lpcbClass, LPDWORD lpReserved, LPDWORD lpcSubKeys, LPDWORD lpcbMaxSubKeyLen, LPDWORD lpcbMaxClassLen, LPDWORD lpcValues, LPDWORD lpcbMaxValueNameLen, LPDWORD lpcbMaxValueLen, LPDWORD lpcbSecurityDescriptor, PFILETIME lpftLastWriteTime ) { CRegLock Lock; return VRegistry.QueryInfoW( hKey, lpClass, lpcbClass, lpReserved, lpcSubKeys, lpcbMaxSubKeyLen, lpcbMaxClassLen, lpcValues, lpcbMaxValueNameLen, lpcbMaxValueLen, lpcbSecurityDescriptor, lpftLastWriteTime); } /*++ Pass through to virtual registry to handle call. --*/ LONG APIHOOK(RegQueryInfoKeyA)( HKEY hKey, LPSTR lpClass, LPDWORD lpcbClass, LPDWORD lpReserved, LPDWORD lpcSubKeys, LPDWORD lpcbMaxSubKeyLen, LPDWORD lpcbMaxClassLen, LPDWORD lpcValues, LPDWORD lpcbMaxValueNameLen, LPDWORD lpcbMaxValueLen, LPDWORD lpcbSecurityDescriptor, PFILETIME lpftLastWriteTime ) { CRegLock Lock; return VRegistry.QueryInfoA( hKey, lpClass, lpcbClass, lpReserved, lpcSubKeys, lpcbMaxSubKeyLen, lpcbMaxClassLen, lpcValues, lpcbMaxValueNameLen, lpcbMaxValueLen, lpcbSecurityDescriptor, lpftLastWriteTime); } /*++ Pass through to virtual registry to handle call. --*/ LONG APIHOOK(RegSetValueExA)( HKEY hKey, LPCSTR lpSubKey, DWORD /* Reserved */, DWORD dwType, CONST BYTE * lpData, DWORD cbData ) { LONG lRet = 0; if (!lpData && cbData) { lRet = ERROR_INVALID_PARAMETER; } else { CRegLock lock; lRet = VRegistry.SetValueA(hKey, lpSubKey, dwType, lpData, cbData); } return lRet; } /*++ Pass through to virtual registry to handle call. --*/ LONG APIHOOK(RegSetValueExW)( HKEY hKey, LPCWSTR lpSubKey, DWORD /* Reserved */, DWORD dwType, CONST BYTE * lpData, DWORD cbData ) { LONG lRet = 0; if (!lpData && cbData) { lRet = ERROR_INVALID_PARAMETER; } else { CRegLock lock; lRet = VRegistry.SetValueW(hKey, lpSubKey, dwType, lpData, cbData); } return lRet; } /*++ Pass through to virtual registry to handle call. --*/ LONG APIHOOK(RegDeleteKeyA)( HKEY hKey, LPCSTR lpSubKey ) { CRegLock Lock; return VRegistry.DeleteKeyA(hKey, lpSubKey); } /*++ Pass through to virtual registry to handle call. --*/ LONG APIHOOK(RegDeleteKeyW)( HKEY hKey, LPCWSTR lpSubKey ) { CRegLock Lock; return VRegistry.DeleteKeyW(hKey, lpSubKey); } LONG APIHOOK(RegConnectRegistryW)( LPCWSTR lpMachineName, HKEY hKey, PHKEY phkResult ) { CRegLock Lock; return VRegistry.OpenKeyW( hKey, NULL, 0, 0, MAXIMUM_ALLOWED, NULL, phkResult, 0, FALSE, TRUE, lpMachineName); } LONG APIHOOK(RegConnectRegistryA)( LPCSTR lpMachineName, HKEY hKey, PHKEY phkResult ) { WCHAR wMachineName[MAX_COMPUTERNAME_LENGTH + 1] = L""; if (lpMachineName) { if (MultiByteToWideChar( CP_ACP, 0, lpMachineName, -1, wMachineName, MAX_COMPUTERNAME_LENGTH + 1) == 0) { return ERROR_INVALID_PARAMETER; } } return APIHOOK(RegConnectRegistryW)(wMachineName, hKey, phkResult); } /*++ Parse the command line for fixes: FIXA(param); FIXB(param); FIXC(param) ... param is optional, and can be omitted (along with parenthesis's) --*/ BOOL ParseCommandLineA( LPCSTR lpCommandLine ) { const char szDefault[] = "Win9x"; // Add all the defaults if no command line is specified if (!lpCommandLine || (lpCommandLine[0] == '\0')) { // Default to win9x API emulation g_bWin9x = TRUE; lpCommandLine = szDefault; } CSTRING_TRY { CStringToken csCommandLine(lpCommandLine, " ,\t;"); CString csTok; int nLeftParam, nRightParam; CString csParam; VENTRY *ventry; // // Run the string, looking for fix names // DPFN( eDbgLevelInfo, "----------------------------------"); DPFN( eDbgLevelInfo, " Virtual registry "); DPFN( eDbgLevelInfo, "----------------------------------"); DPFN( eDbgLevelInfo, "Adding command line:"); while (csCommandLine.GetToken(csTok)) { PURPOSE ePurpose; // Get the parameter nLeftParam = csTok.Find(L'('); nRightParam = csTok.Find(L')'); if (nLeftParam != -1 && nRightParam != -1) { if ( (nLeftParam + 1) < (nRightParam - 1)) { csParam = csTok.Mid(nLeftParam+1, nRightParam-nLeftParam-1); } // Strip off the () from the token. csTok.Truncate(nLeftParam); } else { csParam = L""; } if (csTok.CompareNoCase(L"Win9x") == 0) { // Turn on all win9x fixes ePurpose = eWin9x; g_bWin9x = TRUE; } else if (csTok.CompareNoCase(L"WinNT") == 0) { // Turn on all NT fixes ePurpose = eWinNT; g_bWin9x = FALSE; } else if (csTok.CompareNoCase(L"Win2K") == 0) { // Turn on all Win2K fixes ePurpose = eWin2K; g_bWin9x = FALSE; } else if (csTok.CompareNoCase(L"WinXP") == 0) { // Turn on all Win2K fixes ePurpose = eWinXP; g_bWin9x = FALSE; } else { // A custom fix ePurpose = eCustom; } // Find the specified fix and run it's function ventry = g_pVList; while (ventry && (ventry->cName[0])) { if (((ePurpose != eCustom) && (ventry->ePurpose == ePurpose)) || ((ePurpose == eCustom) && (csTok.CompareNoCase(ventry->cName) == 0))) { if (ventry->bShouldCall == FALSE) { ventry->szParam = (char*) malloc(csParam.GetLength() + 1); if (ventry->szParam) { if (SUCCEEDED(StringCchCopyA(ventry->szParam, csParam.GetLength() + 1, csParam.GetAnsi()))) { ventry->bShouldCall = TRUE; } else { free(ventry->szParam); ventry->szParam = NULL; return FALSE; } } else { return FALSE; } } } ventry++; } } DPFN( eDbgLevelInfo, "----------------------------------"); } CSTRING_CATCH { DPFN(eDbgLevelError, szOutOfMemory); return FALSE; } return TRUE; } /*++ Initialize all the registry hooks --*/ BOOL NOTIFY_FUNCTION( DWORD fdwReason ) { if (fdwReason == DLL_PROCESS_ATTACH) { if (InitializeCriticalSectionAndSpinCount(&csRegCriticalSection, 0x80000000) == FALSE || VRegistry.Init() == FALSE || ParseCommandLineA(COMMAND_LINE) == FALSE) { DPFN(eDbgLevelError, szOutOfMemory); return FALSE; } } // Ignore cleanup because some apps call registry functions during process detach. /* if (fdwReason == DLL_PROCESS_DETACH) { if (g_bInitialized) { VRegistry.Free(); DeleteCriticalSection(&csRegCriticalSection); } DeleteCriticalSection(&csRegTestCriticalSection); return; } */ return TRUE; } HOOK_BEGIN CALL_NOTIFY_FUNCTION APIHOOK_ENTRY(ADVAPI32.DLL, RegConnectRegistryA); APIHOOK_ENTRY(ADVAPI32.DLL, RegConnectRegistryW); APIHOOK_ENTRY(ADVAPI32.DLL, RegOpenKeyExA); APIHOOK_ENTRY(ADVAPI32.DLL, RegOpenKeyExW); APIHOOK_ENTRY(ADVAPI32.DLL, RegQueryValueExA); APIHOOK_ENTRY(ADVAPI32.DLL, RegQueryValueExW); APIHOOK_ENTRY(ADVAPI32.DLL, RegCloseKey); APIHOOK_ENTRY(ADVAPI32.DLL, RegOpenKeyA); APIHOOK_ENTRY(ADVAPI32.DLL, RegOpenKeyW); APIHOOK_ENTRY(ADVAPI32.DLL, RegQueryValueA); APIHOOK_ENTRY(ADVAPI32.DLL, RegQueryValueW); APIHOOK_ENTRY(ADVAPI32.DLL, RegCreateKeyA); APIHOOK_ENTRY(ADVAPI32.DLL, RegCreateKeyW); APIHOOK_ENTRY(ADVAPI32.DLL, RegCreateKeyExA); APIHOOK_ENTRY(ADVAPI32.DLL, RegCreateKeyExW); APIHOOK_ENTRY(ADVAPI32.DLL, RegEnumValueA); APIHOOK_ENTRY(ADVAPI32.DLL, RegEnumValueW); APIHOOK_ENTRY(ADVAPI32.DLL, RegEnumKeyA); APIHOOK_ENTRY(ADVAPI32.DLL, RegEnumKeyW); APIHOOK_ENTRY(ADVAPI32.DLL, RegEnumKeyExA); APIHOOK_ENTRY(ADVAPI32.DLL, RegEnumKeyExW); APIHOOK_ENTRY(ADVAPI32.DLL, RegQueryInfoKeyA); APIHOOK_ENTRY(ADVAPI32.DLL, RegQueryInfoKeyW); APIHOOK_ENTRY(ADVAPI32.DLL, RegSetValueExA); APIHOOK_ENTRY(ADVAPI32.DLL, RegSetValueExW); APIHOOK_ENTRY(ADVAPI32.DLL, RegDeleteKeyA); APIHOOK_ENTRY(ADVAPI32.DLL, RegDeleteKeyW); HOOK_END IMPLEMENT_SHIM_END