/*++ Copyright (c) 2001 Microsoft Corporation Module Name: LogRegistryChanges.cpp Abstract: This AppVerifier shim hooks all the registry APIs that change the state of the system and logs their associated data to a text file. Notes: This is a general purpose shim. History: 08/17/2001 rparsons Created 09/20/2001 rparsons File I/O operations use NT APIs 09/23/2001 rparsons VLOG with log file location 10/06/2001 rparsons Open key handles are never removed from the list --*/ #include "precomp.h" #include "rtlutils.h" IMPLEMENT_SHIM_BEGIN(LogRegistryChanges) #include "ShimHookMacro.h" #include "ShimCString.h" #include "LogRegistryChanges.h" BEGIN_DEFINE_VERIFIER_LOG(LogRegistryChanges) VERIFIER_LOG_ENTRY(VLOG_LOGREGCHANGES_LOGLOC) END_DEFINE_VERIFIER_LOG(LogRegistryChanges) INIT_VERIFIER_LOG(LogRegistryChanges); // // Stores the NT path to the file system log file for the current session. // UNICODE_STRING g_strLogFilePath; // // Stores the DOS path to the file system log file for the current session. // This doesn't get freed. // LPWSTR g_pwszLogFilePath; // // Head of our open key handle linked list. // LIST_ENTRY g_OpenKeyListHead; // // Temporary buffer stored on the heap. // Used when creating XML elements to log. // This doesn't get freed. // LPWSTR g_pwszTempBuffer; // // Size of the temporary buffer above. // DWORD g_cbTempBufferSize; // // Stores the unique id used for NULL value names. // WCHAR g_wszUniqueId[MAX_PATH * 2]; // // Temporary buffers stored on the heap. // Used when extracting old & new data for logging. // These don't get freed. // LPWSTR g_pwszOriginalData; LPWSTR g_pwszFinalData; // // Size of the temporary buffers above. // DWORD g_cbOriginalDataBufferSize; DWORD g_cbFinalDataBufferSize; /*++ Writes an entry to the log file. --*/ void WriteEntryToLog( IN LPCWSTR pwszEntry ) { int nLen = 0; HANDLE hFile; OBJECT_ATTRIBUTES ObjectAttributes; IO_STATUS_BLOCK IoStatusBlock; LARGE_INTEGER liOffset; NTSTATUS status; // // Note that we have to use native APIs throughout this function // to avoid a problem with circular hooking. That is, if we simply // call WriteFile, which is exported from kernel32, it will call NtWriteFile, // which is a call that we hook, in turn leaving us in and endless loop. // // // Attempt to get a handle to our log file. // InitializeObjectAttributes(&ObjectAttributes, &g_strLogFilePath, OBJ_CASE_INSENSITIVE, NULL, NULL); status = NtCreateFile(&hFile, FILE_APPEND_DATA | SYNCHRONIZE, &ObjectAttributes, &IoStatusBlock, 0, FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0); if (!NT_SUCCESS(status)) { DPFN(eDbgLevelError, "[WriteEntryToLog] Failed to open log"); return; } // // Write the data out to the file. // nLen = wcslen(pwszEntry); IoStatusBlock.Status = 0; IoStatusBlock.Information = 0; liOffset.LowPart = 0; liOffset.HighPart = 0; status = NtWriteFile(hFile, NULL, NULL, NULL, &IoStatusBlock, (PVOID)pwszEntry, (ULONG)(nLen) * sizeof(WCHAR), &liOffset, NULL); if (!NT_SUCCESS(status)) { DPFN(eDbgLevelError, "[WriteEntryToLog] 0x%X Failed to make entry", status); goto exit; } exit: NtClose(hFile); } /*++ Creates our log files using the local time. It goes into %windir%\AppPatch\VLog. --*/ BOOL InitializeLogFile( void ) { BOOL fReturn = FALSE; HANDLE hFile; SYSTEMTIME st; UINT nLen = 0; WCHAR wszFileLog[64]; WCHAR* pwszLogFile = NULL; WCHAR* pSlash = NULL; WCHAR* pDot = NULL; WCHAR wszModPathName[MAX_PATH]; WCHAR wszShortName[MAX_PATH]; WCHAR wszLogHdr[512]; const WCHAR wszLogDir[] = L"\\AppPatch\\VLog"; WCHAR wszUnicodeHdr = 0xFEFF; UNICODE_STRING strLogFile; NTSTATUS status; OBJECT_ATTRIBUTES ObjectAttributes; IO_STATUS_BLOCK IoStatusBlock; // // Format the log header. // if (!GetModuleFileName(NULL, wszModPathName, ARRAYSIZE(wszModPathName))) { wcscpy(wszModPathName, L"unknown"); } if (_snwprintf(wszLogHdr, ARRAYSIZE(wszLogHdr) - 1, L"%lc\r\n\r\n", wszUnicodeHdr, wszModPathName) < 0) { DPFN(eDbgLevelError, "[InitializeLogFile] Buffer too small (1)"); return FALSE; } // // Get the current time and set up the log file name. // The format of the log file name is this: // 'processname_registry_yyyymmdd_hhmmss.xml' // GetLocalTime(&st); // // Extract the name of this module without the path and extension. // Basically we trim off the extension first (if there is one.) // Next we find out where the directory path ends, then grab // the file portion and save it away. // wszShortName[0] = 0; pDot = wcsrchr(wszModPathName, '.'); if (pDot) { *pDot = '\0'; } pSlash = wcsrchr(wszModPathName, '\\'); if (pSlash) { wcsncpy(wszShortName, ++pSlash, (wcslen(pSlash) + 1)); } if (_snwprintf(wszFileLog, ARRAYSIZE(wszFileLog) - 1, L"%ls_registry_%02hu%02hu%02hu_%02hu%02hu%02hu.xml", wszShortName, st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond) < 0) { DPFN(eDbgLevelError, "[InitializeLogFile] Buffer too small (2)"); return FALSE; } // // Set up the path our log file. // nLen = GetSystemWindowsDirectory(NULL, 0); if (0 == nLen) { DPFN(eDbgLevelError, "[InitializeLogFile] %lu Failed to get size for Windows directory path", GetLastError()); return FALSE; } nLen += wcslen(wszFileLog); nLen += wcslen(wszLogDir); pwszLogFile = (LPWSTR)MemAlloc((nLen + 2) * sizeof(WCHAR)); if (!pwszLogFile) { DPFN(eDbgLevelError, "[InitializeLogFile] No memory for log file"); return FALSE; } nLen = GetSystemWindowsDirectory(pwszLogFile, (nLen + 2) * sizeof(WCHAR)); if (0 == nLen) { DPFN(eDbgLevelError, "[InitializeLogFile] %lu Failed to get Windows directory path", GetLastError()); return FALSE; } wcscat(pwszLogFile, wszLogDir); // // Ensure that the %windir%\AppPatch\VLog directory exists. // If it doesn't, attempt to create it. // if (-1 == GetFileAttributes(pwszLogFile)) { if (!CreateDirectory(pwszLogFile, NULL)) { DPFN(eDbgLevelError, "[InitializeLogFile] %lu Failed to create VLog directory", GetLastError()); return FALSE; } } wcscat(pwszLogFile, L"\\"); wcscat(pwszLogFile, wszFileLog); // // Save the pointer as we'll need it later. // g_pwszLogFilePath = pwszLogFile; // // Attempt to create the new log file. // RtlInitUnicodeString(&strLogFile, pwszLogFile); status = RtlDosPathNameToNtPathName_U(strLogFile.Buffer, &strLogFile, NULL, NULL); if (!NT_SUCCESS(status)) { DPFN(eDbgLevelError, "[InitializeLogFile] DOS -> NT failed"); return FALSE; } InitializeObjectAttributes(&ObjectAttributes, &strLogFile, OBJ_CASE_INSENSITIVE, NULL, NULL); status = NtCreateFile(&hFile, GENERIC_WRITE | SYNCHRONIZE, &ObjectAttributes, &IoStatusBlock, NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0); if (!NT_SUCCESS(status)) { DPFN(eDbgLevelError, "[InitializeLogFile] 0x%X Failed to create log", status); goto cleanup; } NtClose(hFile); // // Save away the NT path to the file. // status = ShimDuplicateUnicodeString(RTL_DUPLICATE_UNICODE_STRING_NULL_TERMINATE | RTL_DUPLICATE_UNICODE_STRING_ALLOCATE_NULL_STRING, &strLogFile, &g_strLogFilePath); if (!NT_SUCCESS(status)) { DPFN(eDbgLevelError, "[InitializeLogFile] Failed to save log file path"); goto cleanup; } // // Write the header to the log. // WriteEntryToLog(wszLogHdr); fReturn = TRUE; cleanup: RtlFreeUnicodeString(&strLogFile); return fReturn; } /*++ Writes the closing element to the file and outputs the log file location. --*/ BOOL CloseLogFile( void ) { WCHAR wszBuffer[] = L""; WriteEntryToLog(wszBuffer); VLOG(VLOG_LEVEL_INFO, VLOG_LOGREGCHANGES_LOGLOC, "%ls", g_pwszLogFilePath); return TRUE; } /*++ Converts from ANSI to Unicode. The caller must free the buffer. --*/ BOOL ConvertAnsiToUnicode( IN LPCSTR pszAnsiString, OUT LPWSTR* pwszUnicodeString ) { int nLen = 0; nLen = lstrlenA(pszAnsiString); if (nLen) { *pwszUnicodeString = (LPWSTR)MemAlloc((nLen + 1) * sizeof(WCHAR)); if (!*pwszUnicodeString) { DPFN(eDbgLevelError, "[ConvertAnsiToUnicode] Failed to allocate memory"); return FALSE; } nLen = MultiByteToWideChar(CP_ACP, 0, pszAnsiString, -1, *pwszUnicodeString, nLen + 1); if (!nLen) { DPFN(eDbgLevelError, "[ConvertAnsiToUnicode] %lu Ansi -> Unicode failed", GetLastError()); MemFree(*pwszUnicodeString); return FALSE; } } return TRUE; } /*++ Converts a list of NULL terminated strings from ANSI to Unicode. The caller must free the buffer. --*/ BOOL ConvertMultiSzToUnicode( IN LPCSTR pszAnsiStringList, OUT LPWSTR* pwszWideStringList ) { int nLen = 0; UINT uSize = 0; UINT uWideSize = 0; UINT uTotalSize = 0; LPCSTR pszAnsi = NULL; LPWSTR pwszTemp = NULL; if (!pszAnsiStringList) { DPFN(eDbgLevelError, "[ConvertMultiSzToUnicode] Invalid parameter"); return FALSE; } pszAnsi = pszAnsiStringList; // // Determine how large of a buffer we need to allocate. // do { uSize = lstrlenA(pszAnsi) + 1; uTotalSize += uSize; pszAnsi += uSize; } while (uSize != 1); if (uTotalSize != 0) { pwszTemp = *pwszWideStringList = (LPWSTR)MemAlloc(uTotalSize * sizeof(WCHAR)); if (!*pwszWideStringList) { DPFN(eDbgLevelError, "[ConvertMultiSzToUnicode] No memory for buffer"); return FALSE; } } // // Perform the ANSI to Unicode conversion. // pszAnsi = pszAnsiStringList; do { nLen = lstrlenA(pszAnsi) + 1; uWideSize = MultiByteToWideChar( CP_ACP, 0, pszAnsi, -1, pwszTemp, nLen); pszAnsi += nLen; pwszTemp += uWideSize; } while (nLen != 1); return TRUE; } /*++ Given a predefined key handle such as HKEY_LOCAL_MACHINE, return a string. --*/ BOOL PredefinedKeyToString( IN HKEY hKey, OUT LPWSTR* pwszString ) { if (hKey == HKEY_CLASSES_ROOT) { wcscpy(*pwszString, L"HKEY_CLASSES_ROOT"); } else if (hKey == HKEY_CURRENT_CONFIG) { wcscpy(*pwszString, L"HKEY_CURRENT_CONFIG"); } else if (hKey == HKEY_CURRENT_USER) { wcscpy(*pwszString, L"HKEY_CURRENT_USER"); } else if (hKey == HKEY_LOCAL_MACHINE) { wcscpy(*pwszString, L"HKEY_LOCAL_MACHINE"); } else if (hKey == HKEY_USERS) { wcscpy(*pwszString, L"HKEY_USERS"); } else if (hKey == HKEY_DYN_DATA) { wcscpy(*pwszString, L"HKEY_DYN_DATA"); } else if (hKey == HKEY_PERFORMANCE_DATA) { wcscpy(*pwszString, L"HKEY_PERFORMANCE_DATA"); } else { wcscpy(*pwszString, L"Not recognized"); return FALSE; } return TRUE; } /*++ Displays the name associated with this object. --*/ #if DBG void PrintNameFromKey( IN HKEY hKey ) { NTSTATUS status; WCHAR wszBuffer[MAX_PATH]; OBJECT_NAME_INFORMATION *poni = NULL; wszBuffer[0] = 0; poni = (OBJECT_NAME_INFORMATION*)wszBuffer; status = NtQueryObject(hKey, ObjectNameInformation, poni, MAX_PATH, NULL); if (NT_SUCCESS(status)) { DPFN(eDbgLevelInfo, "Key 0x%08x has name: %ls", hKey, poni->Name.Buffer); } } #endif // DBG /*++ Given a pointer to a key node, get the original data. --*/ BOOL CLogRegistry::GetOriginalDataForKey( IN PLOG_OPEN_KEY pLogOpenKey, IN PKEY_DATA pKeyData, IN LPCWSTR pwszValueName ) { BOOL fReturn = FALSE; HKEY hKeyLocal; DWORD cbSize = 0, dwType = 0; LONG lRetVal; if (!pLogOpenKey || !pKeyData) { DPFN(eDbgLevelError, "[GetOriginalDataForKey] Invalid parameter(s)"); return FALSE; } lRetVal = RegOpenKeyEx(pLogOpenKey->hKeyRoot, pLogOpenKey->pwszSubKeyPath, 0, KEY_QUERY_VALUE, &hKeyLocal); if (ERROR_SUCCESS != lRetVal) { DPFN(eDbgLevelError, "[GetOriginalDataForKey] Failed to open key"); return FALSE; } // // Query the size of the data. If the data doesn't exist, return success. // lRetVal = RegQueryValueEx(hKeyLocal, pwszValueName, 0, &dwType, NULL, &cbSize); if (ERROR_SUCCESS != lRetVal) { if (ERROR_FILE_NOT_FOUND == lRetVal) { RegCloseKey(hKeyLocal); return TRUE; } else { DPFN(eDbgLevelError, "[GetOldDataForKey] Failed to get data size"); goto cleanup; } } // // Update the flags to indicate that the value already exists. // pKeyData->dwFlags |= LRC_EXISTING_VALUE; // // Allocate a buffer large enough to store the old data. // if (dwType != REG_DWORD && dwType != REG_BINARY) { pKeyData->pOriginalData = (PVOID)MemAlloc(cbSize * sizeof(WCHAR)); pKeyData->cbOriginalDataSize = cbSize * sizeof(WCHAR); } else { pKeyData->pOriginalData = (PVOID)MemAlloc(cbSize); pKeyData->cbOriginalDataSize = cbSize; } if (!pKeyData->pOriginalData) { DPFN(eDbgLevelError, "[GetOriginalDataForKey] Failed to allocate memory"); goto cleanup; } pKeyData->dwOriginalValueType = dwType; // // Now make the call again this time getting the actual data. // lRetVal = RegQueryValueEx(hKeyLocal, pwszValueName, 0, 0, (LPBYTE)pKeyData->pOriginalData, &cbSize); if (ERROR_SUCCESS != lRetVal) { DPFN(eDbgLevelError, "[GetOriginalDataForKey] Failed to get data"); goto cleanup; } fReturn = TRUE; cleanup: RegCloseKey(hKeyLocal); return fReturn; } /*++ Given a pointer to a key node, get the final data. --*/ BOOL CLogRegistry::GetFinalDataForKey( IN PLOG_OPEN_KEY pLogOpenKey, IN PKEY_DATA pKeyData, IN LPCWSTR pwszValueName ) { BOOL fReturn = FALSE; HKEY hKeyLocal; DWORD cbSize = 0, dwType = 0; LONG lRetVal; PVOID pTemp = NULL; if (!pLogOpenKey || !pKeyData) { DPFN(eDbgLevelError, "[GetFinalDataForKey] Invalid parameter(s)"); return FALSE; } lRetVal = RegOpenKeyEx(pLogOpenKey->hKeyRoot, pLogOpenKey->pwszSubKeyPath, 0, KEY_QUERY_VALUE, &hKeyLocal); if (ERROR_SUCCESS != lRetVal) { DPFN(eDbgLevelError, "[GetFinalDataForKey] Failed to open key"); return FALSE; } // // Query the size of the data. If the data doesn't exist, return success. // lRetVal = RegQueryValueEx(hKeyLocal, pwszValueName, 0, &dwType, NULL, &cbSize); if (ERROR_SUCCESS != lRetVal) { if (ERROR_FILE_NOT_FOUND == lRetVal) { RegCloseKey(hKeyLocal); return TRUE; } else { DPFN(eDbgLevelError, "[GetFinalDataForKey] Failed to get data size"); goto cleanup; } } // // It's possible that multiple queries were issued against the same // key. If this is the case, the buffer to hold the data has already // been allocated. Determine if the block is large enough. // if (pKeyData->pFinalData) { if (dwType != REG_DWORD && dwType != REG_BINARY) { // // If MemReAlloc fails, we would lose the pointer that // we already had in pKeyData->pFinalData. This preserves // the pointer. // if (pKeyData->cbFinalDataSize < (cbSize * sizeof(WCHAR))) { pKeyData->cbFinalDataSize = cbSize * sizeof(WCHAR); pTemp = MemReAlloc(pKeyData->pFinalData, cbSize * sizeof(WCHAR)); } } else { if (pKeyData->cbFinalDataSize < cbSize) { pKeyData->cbFinalDataSize = cbSize; pTemp = MemReAlloc(pKeyData->pFinalData, cbSize); } } if (pTemp) { pKeyData->pFinalData = pTemp; } else { DPFN(eDbgLevelError, "[GetFinalDataForKey] Failed to reallocate memory"); goto cleanup; } } else { if (dwType != REG_DWORD && dwType != REG_BINARY) { pKeyData->pFinalData = MemAlloc(cbSize * sizeof(WCHAR)); pKeyData->cbFinalDataSize = cbSize * sizeof(WCHAR); } else { pKeyData->pFinalData = MemAlloc(cbSize); pKeyData->cbFinalDataSize = cbSize; } } if (!pKeyData->pFinalData) { DPFN(eDbgLevelError, "[GetFinalDataForKey] Failed to allocate memory"); goto cleanup; } pKeyData->dwFinalValueType = dwType; // // Now make the call again this time getting the actual data. // lRetVal = RegQueryValueEx(hKeyLocal, pwszValueName, 0, 0, (LPBYTE)pKeyData->pFinalData, &cbSize); if (ERROR_SUCCESS != lRetVal) { DPFN(eDbgLevelError, "[GetFinalDataForKey] Failed to get data"); goto cleanup; } fReturn = TRUE; cleanup: RegCloseKey(hKeyLocal); return fReturn; } /*++ Given a value name, attempt to find it in the list. This function may not always return a pointer. --*/ PKEY_DATA CLogRegistry::FindValueNameInList( IN LPCWSTR pwszValueName, IN PLOG_OPEN_KEY pOpenKey ) { BOOL fFound = FALSE; PLIST_ENTRY pHead = NULL; PLIST_ENTRY pNext = NULL; PKEY_DATA pFindData = NULL; if (!pwszValueName || !pOpenKey) { DPFN(eDbgLevelError, "[FindValueNameInList] Invalid parameter(s)"); return NULL; } pHead = &pOpenKey->KeyData; pNext = pHead->Flink; while (pNext != pHead) { pFindData = CONTAINING_RECORD(pNext, KEY_DATA, Entry); if (!_wcsicmp(pwszValueName, pFindData->wszValueName)) { fFound = TRUE; break; } pNext = pNext->Flink; } return (fFound ? pFindData : NULL); } /*++ Given a key path, attempt to locate it in the list. This function may not always return a pointer. --*/ PLOG_OPEN_KEY CLogRegistry::FindKeyPathInList( IN LPCWSTR pwszKeyPath ) { BOOL fFound = FALSE; PLIST_ENTRY pCurrent = NULL; PLOG_OPEN_KEY pFindKey = NULL; if (!pwszKeyPath) { DPFN(eDbgLevelError, "[FindKeyPathInList] Invalid parameter"); return NULL; } // // Attempt to locate the entry in the list. // pCurrent = g_OpenKeyListHead.Flink; while (pCurrent != &g_OpenKeyListHead) { pFindKey = CONTAINING_RECORD(pCurrent, LOG_OPEN_KEY, Entry); if (pFindKey->pwszFullKeyPath) { if (!_wcsicmp(pwszKeyPath, pFindKey->pwszFullKeyPath)) { fFound = TRUE; break; } } pCurrent = pCurrent->Flink; } return (fFound ? pFindKey : NULL); } /*++ Given a key handle, remove it from the array in the list. --*/ PLOG_OPEN_KEY CLogRegistry::RemoveKeyHandleFromArray( IN HKEY hKey ) { UINT uCount; PLIST_ENTRY pCurrent = NULL; PLOG_OPEN_KEY pFindKey = NULL; if (!hKey) { DPFN(eDbgLevelError, "[RemoveKeyHandleFromArray] Invalid key handle passed!"); return NULL; } pCurrent = g_OpenKeyListHead.Flink; while (pCurrent != &g_OpenKeyListHead) { pFindKey = CONTAINING_RECORD(pCurrent, LOG_OPEN_KEY, Entry); // // Step through this guy's array looking for the handle. // for (uCount = 0; uCount < pFindKey->cHandles; uCount++) { // // If we find the handle, set the array element to NULL and // decrement the count of handles for this entry. // if (pFindKey->hKeyBase[uCount] == hKey) { DPFN(eDbgLevelInfo, "[RemoveKeyHandleFromArray] Removing handle 0x%08x", hKey); pFindKey->hKeyBase[uCount] = NULL; pFindKey->cHandles--; return pFindKey; } } pCurrent = pCurrent->Flink; } return NULL; } /*++ Finds a key handle in the array. --*/ PLOG_OPEN_KEY CLogRegistry::FindKeyHandleInArray( IN HKEY hKey ) { UINT uCount; BOOL fFound = FALSE; PLOG_OPEN_KEY pFindKey = NULL; PLIST_ENTRY pCurrent = NULL; if (!hKey) { DPFN(eDbgLevelError, "[FindKeyHandleInArray] Invalid key handle passed!"); return NULL; } pCurrent = g_OpenKeyListHead.Flink; while (pCurrent != &g_OpenKeyListHead) { pFindKey = CONTAINING_RECORD(pCurrent, LOG_OPEN_KEY, Entry); // // Step through this guy's array looking for the handle. // for (uCount = 0; uCount < pFindKey->cHandles; uCount++) { if (pFindKey->hKeyBase[uCount] == hKey) { fFound = TRUE; break; } } if (fFound) { break; } pCurrent = pCurrent->Flink; } #if DBG if (!fFound) { // // Dear God - the key handle is not in the list! // Break into the debugger on checked builds. // DPFN(eDbgLevelError, "[FindKeyHandleInArray] Key 0x%08x not in list!", hKey); PrintNameFromKey(hKey); DbgBreakPoint(); } #endif // DBG return (fFound ? pFindKey : NULL); } /*++ Given a predefined handle and a subkey path, open the key to force it into the list. --*/ HKEY CLogRegistry::ForceSubKeyIntoList( IN HKEY hKeyPredefined, IN LPCWSTR pwszSubKey ) { LONG lRetVal; HKEY hKeyRet; if (!pwszSubKey) { DPFN(eDbgLevelError, "[ForceSubKeyIntoList] Invalid parameter"); return NULL; } lRetVal = OpenKeyExW(hKeyPredefined, pwszSubKey, 0, KEY_WRITE, &hKeyRet); if (ERROR_SUCCESS != lRetVal) { DPFN(eDbgLevelError, "[ForceSubKeyIntoList] Failed to open key"); return NULL; } return hKeyRet; } /*++ Add a non-predefined key handle to the array. --*/ PLOG_OPEN_KEY CLogRegistry::AddKeyHandleToList( IN HKEY hKey, IN HKEY hKeyNew, IN LPCWSTR pwszSubKeyPath, IN BOOL fExisting ) { UINT uCount; DWORD dwLen = 0; PLOG_OPEN_KEY pFindKey = NULL; PLOG_OPEN_KEY pRetKey = NULL; PLOG_OPEN_KEY pExistingFindKey = NULL; // // If hKeyNew, which is the key handle the caller received // from the function, is a predefined handle, we simply // call FindKeyInArray, which will return a pointer to the // list entry that contains that key handle. These handles // were added during initialization, so there's no chance // that the caller won't get a pointer back. // if (IsPredefinedRegistryHandle(hKeyNew)) { return FindKeyHandleInArray(hKeyNew); } // // We've got a usual case where a key has been opened, and // now the caller is opening a subkey underneath it. // pFindKey = FindKeyHandleInArray(hKey); // // If pFindKey ever comes back as NULL, something is really // wrong. Every OpenKey/CreateKey comes through us and goes // into the list (with the exception of predefined handles // which are already stored there.) Dump out as much data // as we can to figure out what went wrong. // if (!pFindKey) { DPFN(eDbgLevelError, "[AddKeyHandleToList] Key not found in list! Key Handle = 0x%08x New key = 0x%08x Path = %ls", hKey, hKeyNew, pwszSubKeyPath); return NULL; } pRetKey = (PLOG_OPEN_KEY)MemAlloc(sizeof(LOG_OPEN_KEY)); if (!pRetKey) { DPFN(eDbgLevelError, "[AddKeyHandleToList] No memory available"); return NULL; } // // Make room for the subkey path that will go into the new // node. If the node that we found has a subkey path stored, // take that into account also. // if (pwszSubKeyPath) { dwLen = wcslen(pwszSubKeyPath); } if (pFindKey->pwszSubKeyPath) { dwLen += wcslen(pFindKey->pwszSubKeyPath); } if (pFindKey->pwszSubKeyPath || pwszSubKeyPath) { pRetKey->pwszSubKeyPath = (LPWSTR)MemAlloc((dwLen + 2) * sizeof(WCHAR)); if (!pRetKey->pwszSubKeyPath) { DPFN(eDbgLevelError, "[AddKeyHandleToList] No memory for subkey path"); goto cleanup; } } // // If the node that we found has a subkey path, take it // and copy it over to the new node and concatenate // the subkey path that we got passed. Otherwise just // store the path that was passed, if available. // if (pFindKey->pwszSubKeyPath && pwszSubKeyPath) { wcscpy(pRetKey->pwszSubKeyPath, pFindKey->pwszSubKeyPath); wcscat(pRetKey->pwszSubKeyPath, L"\\"); wcscat(pRetKey->pwszSubKeyPath, pwszSubKeyPath); } else if (pwszSubKeyPath) { wcscpy(pRetKey->pwszSubKeyPath, pwszSubKeyPath); } // // Make room for the full key path. This will store a path // of something like HKEY_LOCAL_MACHINE\Software\Microsoft... // that will be used for logging purposes. // if (pRetKey->pwszSubKeyPath) { dwLen = wcslen(pRetKey->pwszSubKeyPath); } pRetKey->pwszFullKeyPath = (LPWSTR)MemAlloc((dwLen + 2 + MAX_ROOT_LENGTH) * sizeof(WCHAR)); if (!pRetKey->pwszFullKeyPath) { DPFN(eDbgLevelError, "[AddKeyHandleToList] No memory for full key path"); goto cleanup; } // // Convert the predefined handle to a string and store it in // the node that we're about to add to the list. // if (!PredefinedKeyToString(pFindKey->hKeyRoot, &pRetKey->pwszFullKeyPath)) { DPFN(eDbgLevelError, "[AddKeyHandleToList] PredefinedKey -> String failed"); goto cleanup; } if (pwszSubKeyPath) { wcscat(pRetKey->pwszFullKeyPath, L"\\"); wcscat(pRetKey->pwszFullKeyPath, pRetKey->pwszSubKeyPath); } // // At this point we have a full key path. // We attempt to find the path in the linked list. // If we find it, we're going to update the handle array and count for this guy. // If we don't find it, we're going to add a new entry to the list. // pExistingFindKey = FindKeyPathInList(pRetKey->pwszFullKeyPath); if (!pExistingFindKey) { // // Fill in information about this key and add it to the list. // pRetKey->hKeyBase[0] = hKeyNew; pRetKey->hKeyRoot = pFindKey->hKeyRoot; pRetKey->cHandles = 1; pRetKey->dwFlags |= fExisting ? LRC_EXISTING_KEY : 0; InitializeListHead(&pRetKey->KeyData); DPFN(eDbgLevelInfo, "[AddKeyHandleToList] Adding key: %p", pRetKey); InsertHeadList(&g_OpenKeyListHead, &pRetKey->Entry); return pRetKey; } else { // // Store this handle in the array and increment the handle count. // Make sure we don't overstep the array bounds. // for (uCount = 0; uCount < pExistingFindKey->cHandles; uCount++) { if (NULL == pExistingFindKey->hKeyBase[uCount]) { break; } } if (uCount >= MAX_NUM_HANDLES) { DPFN(eDbgLevelError, "[AddKeyHandleToList] Handle count reached"); goto cleanup; } pExistingFindKey->hKeyBase[uCount] = hKeyNew; pExistingFindKey->dwFlags &= ~LRC_DELETED_KEY; pExistingFindKey->cHandles++; } cleanup: if (pRetKey->pwszFullKeyPath) { MemFree(pRetKey->pwszFullKeyPath); } if (pRetKey) { MemFree(pRetKey); } return pExistingFindKey; } /*++ Add a value to the list. --*/ PKEY_DATA CLogRegistry::AddValueNameToList( IN PLOG_OPEN_KEY pLogOpenKey, IN LPCWSTR pwszValueName ) { PKEY_DATA pKeyData = NULL; pKeyData = (PKEY_DATA)MemAlloc(sizeof(KEY_DATA)); if (!pKeyData) { DPFN(eDbgLevelError, "[AddValueNameToList] Failed to allocate memory"); return NULL; } if (!GetOriginalDataForKey(pLogOpenKey, pKeyData, pwszValueName)) { DPFN(eDbgLevelError, "[AddValueNameToList] Failed to get original data"); goto cleanup; } if (pwszValueName) { wcsncpy(pKeyData->wszValueName, pwszValueName, MAX_PATH); } else { // // If the valuename is NULL, assign our unique id. // wcsncpy(pKeyData->wszValueName, g_wszUniqueId, wcslen(g_wszUniqueId)); } InsertHeadList(&pLogOpenKey->KeyData, &pKeyData->Entry); return pKeyData; cleanup: if (pKeyData) { MemFree(pKeyData); } return NULL; } /*++ The entry point for modifying the linked list data. --*/ PLOG_OPEN_KEY CLogRegistry::UpdateKeyList( IN HKEY hKeyRoot, IN HKEY hKeyNew, IN LPCWSTR pwszSubKey, IN LPCWSTR pwszValueName, IN BOOL fExisting, IN UpdateType eType ) { PKEY_DATA pKeyData = NULL; PLOG_OPEN_KEY pRetKey = NULL; switch (eType) { case eAddKeyHandle: pRetKey = AddKeyHandleToList(hKeyRoot, hKeyNew, pwszSubKey, fExisting); break; case eRemoveKeyHandle: pRetKey = RemoveKeyHandleFromArray(hKeyNew); break; case eStartModifyValue: case eStartDeleteValue: pRetKey = FindKeyHandleInArray(hKeyNew); if (!pRetKey) { DPFN(eDbgLevelError, "[UpdateKeyList] Start Modify: Failed to find handle in array"); break; } if (!pwszValueName) { pKeyData = FindValueNameInList(g_wszUniqueId, pRetKey); } else { pKeyData = FindValueNameInList(pwszValueName, pRetKey); } if (pKeyData) { // // If the caller is attempting to modify the value, and we've // already gotten data for it, don't do it again. // Also, if they're attempting to delete the value, and it's // been modified, don't do it again. // if ((pKeyData->pOriginalData || pKeyData->pFinalData) || (pKeyData->dwFlags & LRC_MODIFIED_VALUE) && (eStartDeleteValue == eType)) { break; } if (!GetOriginalDataForKey(pRetKey, pKeyData, pwszValueName)) { DPFN(eDbgLevelError, "[UpdateKeyList] Start Modify: Failed to get original data"); break; } } else { // // We've never seen this value before. Insert it into the list. // if (!AddValueNameToList(pRetKey, pwszValueName)) { DPFN(eDbgLevelError, "[UpdateKeyList] Start Modify: Failed to insert value"); break; } } break; case eEndModifyValue: case eEndDeleteValue: pRetKey = FindKeyHandleInArray(hKeyNew); if (!pRetKey) { DPFN(eDbgLevelError, "[UpdateKeyList] End Modify: Failed to find handle in array"); break; } if (!pwszValueName) { pKeyData = FindValueNameInList(g_wszUniqueId, pRetKey); } else { pKeyData = FindValueNameInList(pwszValueName, pRetKey); } if (!pKeyData) { DPFN(eDbgLevelError, "[UpdateKeyList] End Modify: Failed to find value in list"); break; } if (eEndModifyValue == eType) { if (!GetFinalDataForKey(pRetKey, pKeyData, pwszValueName)) { DPFN(eDbgLevelError, "[UpdateKeyList] End Modify: Failed to get final data"); break; } pKeyData->dwFlags |= LRC_MODIFIED_VALUE; } else if (eEndDeleteValue == eType) { pKeyData->dwFlags |= LRC_DELETED_VALUE; } break; case eDeletedKey: pRetKey = FindKeyHandleInArray(hKeyNew); if (!pRetKey) { DPFN(eDbgLevelError, "[UpdateKeyList] DeleteKey: Failed to find handle in array"); break; } pRetKey->dwFlags |= LRC_DELETED_KEY; break; default: DPFN(eDbgLevelError, "[UpdateKeyList] Invalid enum type!"); break; } return pRetKey; } /*++ Formats the data to form an XML element and logs it. --*/ void FormatKeyDataIntoElement( IN LPCWSTR pwszOperation, IN PLOG_OPEN_KEY pLogOpenKey ) { UINT cbSize = 0; PVOID pTemp = NULL; // // To make it easier to replace & ' " < and > with their // XML entities, we convert the data to CString. // CString csFullKeyPath(pLogOpenKey->pwszFullKeyPath); csFullKeyPath.Replace(L"&", L"&"); csFullKeyPath.Replace(L"<", L"<"); csFullKeyPath.Replace(L">", L">"); csFullKeyPath.Replace(L"'", L"'"); csFullKeyPath.Replace(L"\"", L"""); // // To keep allocations to a minimum, we allocate a global // buffer one time, then reallocate if the data we're // logging is larger than the buffer. // if (!g_cbTempBufferSize) { g_pwszTempBuffer = (LPWSTR)MemAlloc(TEMP_BUFFER_SIZE * sizeof(WCHAR)); if (!g_pwszTempBuffer) { DPFN(eDbgLevelError, "[FormatKeyData] Failed to allocate memory"); return; } g_cbTempBufferSize = TEMP_BUFFER_SIZE * sizeof(WCHAR); } g_pwszTempBuffer[0] = 0; // // Determine how large of a buffer we'll need. // cbSize += csFullKeyPath.GetLength(); cbSize += MAX_OPERATION_LENGTH; cbSize += KEY_ELEMENT_SIZE; cbSize *= sizeof(WCHAR); if (cbSize > g_cbTempBufferSize) { // // Our global buffer is not large enough; reallocate. // pTemp = (LPWSTR)MemReAlloc(g_pwszTempBuffer, cbSize + BUFFER_ALLOCATION_DELTA); if (pTemp) { g_pwszTempBuffer = (LPWSTR)pTemp; } else { DPFN(eDbgLevelError, "[FormatKeyData] Failed to reallocate memory"); return; } g_cbTempBufferSize = cbSize + BUFFER_ALLOCATION_DELTA; } wsprintf(g_pwszTempBuffer, L" \r\n", pwszOperation, csFullKeyPath.Get()); WriteEntryToLog(g_pwszTempBuffer); } void FormatValueDataIntoElement( IN CString& csFullKeyPath, IN LPCWSTR pwszOperation, IN LPCWSTR pwszValueName, IN LPCWSTR pwszOriginalValueType, IN LPCWSTR pwszFinalValueType ) { UINT cbSize = 0; int nChars = 0; PVOID pTemp = NULL; // // To keep allocations to a minimum, we allocate a global // buffer one time, then reallocate if the data we're // logging is larger than the buffer. // if (!g_cbTempBufferSize) { g_pwszTempBuffer = (LPWSTR)MemAlloc(TEMP_BUFFER_SIZE * sizeof(WCHAR)); if (!g_pwszTempBuffer) { DPFN(eDbgLevelError, "[FormatValueDataIntoElement] Failed to allocate memory"); return; } g_cbTempBufferSize = TEMP_BUFFER_SIZE * sizeof(WCHAR); } // // Determine how large of a buffer we'll need. // cbSize += wcslen(pwszOperation); cbSize += wcslen(pwszOriginalValueType); cbSize += wcslen(pwszFinalValueType); cbSize += wcslen(g_pwszOriginalData); cbSize += wcslen(g_pwszFinalData); cbSize += csFullKeyPath.GetLength(); cbSize += VALUE_ELEMENT_SIZE; if (pwszValueName) { cbSize += wcslen(pwszValueName); } cbSize *= sizeof(WCHAR); if (cbSize > g_cbTempBufferSize) { // // Our global buffer is not large enough; reallocate. // pTemp = (LPWSTR)MemReAlloc(g_pwszTempBuffer, cbSize + BUFFER_ALLOCATION_DELTA); if (pTemp) { g_pwszTempBuffer = (LPWSTR)pTemp; } else { DPFN(eDbgLevelError, "[FormatValueDataIntoElement] Failed to reallocate memory"); return; } g_cbTempBufferSize = cbSize + BUFFER_ALLOCATION_DELTA; } // // Open the element. // nChars = wsprintf(g_pwszTempBuffer, L" \r\n", pwszOperation, csFullKeyPath.Get()); // // Write the element. // if (pwszValueName) { nChars += wsprintf(g_pwszTempBuffer + nChars, L" \r\n", pwszValueName); } else { nChars += wsprintf(g_pwszTempBuffer + nChars, L" \r\n"); } // // Write the element. // if (g_pwszOriginalData[0]) { nChars += wsprintf(g_pwszTempBuffer + nChars, L" \r\n", pwszOriginalValueType, g_pwszOriginalData); } else { nChars += wsprintf(g_pwszTempBuffer + nChars, L" \r\n"); } // // Write the element. // if (g_pwszFinalData[0]) { nChars += wsprintf(g_pwszTempBuffer + nChars, L" \r\n", pwszFinalValueType, g_pwszFinalData); } else { nChars += wsprintf(g_pwszTempBuffer + nChars, L" \r\n"); } // // Close the element. // wsprintf(g_pwszTempBuffer + nChars, L" \r\n"); WriteEntryToLog(g_pwszTempBuffer); return; } /*++ Converts binary data into a readable string. --*/ void ExtractBinaryData( IN PVOID pBinary, IN DWORD dwDataSize, IN OUT LPWSTR* pwszString ) { int nChars = 0; PBYTE pByte = NULL; DWORD dwLoop = 0; if (!pBinary || !pwszString) { DPFN(eDbgLevelError, "[ExtractBinaryData] Invalid parameter(s)"); return; } pByte = (BYTE*)pBinary; dwLoop = dwDataSize / sizeof(WCHAR); while (dwLoop) { nChars += wsprintf(*pwszString + nChars, L"%lx", *pByte++); dwLoop--; } } /*++ Converts a REG_MULTI_SZ to a readable string. --*/ void ExtractMultiSzStrings( IN PVOID pMultiSz, IN OUT LPWSTR* pwszString ) { int nChars = 0; UINT uSize = 0; LPWSTR pwszTmp = NULL; if (!pMultiSz || !pwszString) { DPFN(eDbgLevelError, "[ExtractMultiSzStrings] Invalid parameter(s)"); return; } // // Walk the list of NULL-terminated strings and put them in the buffer. // pwszTmp = (LPWSTR)pMultiSz; while (TRUE) { nChars += wsprintf(*pwszString + nChars, L" %ls", pwszTmp); uSize = wcslen(pwszTmp) + 1; pwszTmp += uSize; if (*pwszTmp == '\0') { break; } } } /*++ Formats the value data to form an XML element and logs it. --*/ void FormatValueData( IN LPCWSTR pwszOperation, IN PKEY_DATA pKeyData, IN PLOG_OPEN_KEY pLogOpenKey ) { WCHAR wszFinalValueType[MAX_DATA_TYPE_LENGTH]; WCHAR wszOriginalValueType[MAX_DATA_TYPE_LENGTH]; LPCWSTR pwszValueName = NULL; PVOID pOriginalTmp = NULL; PVOID pFinalTmp = NULL; // // If we haven't already, allocate buffers that we'll // use and reuse when getting original and final data. // if (!g_cbOriginalDataBufferSize) { g_pwszOriginalData = (LPWSTR)MemAlloc(TEMP_BUFFER_SIZE * sizeof(WCHAR)); if (!g_pwszOriginalData) { DPFN(eDbgLevelError, "[FormatValueData] Failed to allocate memory for old data"); return; } g_cbOriginalDataBufferSize = TEMP_BUFFER_SIZE * sizeof(WCHAR); } if (!g_cbFinalDataBufferSize) { g_pwszFinalData = (LPWSTR)MemAlloc(TEMP_BUFFER_SIZE * sizeof(WCHAR)); if (!g_pwszFinalData) { DPFN(eDbgLevelError, "[FormatValueData] Failed to allocate memory for new data"); return; } g_cbFinalDataBufferSize = TEMP_BUFFER_SIZE * sizeof(WCHAR); } g_pwszOriginalData[0] = 0; g_pwszFinalData[0] = 0; // // To make it easier to replace & ' " < and > with their // XML entities, we convert the data to CString. // CString csFullKeyPath(pLogOpenKey->pwszFullKeyPath); csFullKeyPath.Replace(L"&", L"&"); csFullKeyPath.Replace(L"<", L"<"); csFullKeyPath.Replace(L">", L">"); csFullKeyPath.Replace(L"'", L"'"); csFullKeyPath.Replace(L"\"", L"""); switch (pKeyData->dwOriginalValueType) { case REG_SZ: wcscpy(wszOriginalValueType, L"REG_SZ"); break; case REG_EXPAND_SZ: wcscpy(wszOriginalValueType, L"REG_EXPAND_SZ"); break; case REG_MULTI_SZ: wcscpy(wszOriginalValueType, L"REG_MULTI_SZ"); break; case REG_DWORD: wcscpy(wszOriginalValueType, L"REG_DWORD"); break; case REG_BINARY: wcscpy(wszOriginalValueType, L"REG_BINARY"); break; default: wcscpy(wszOriginalValueType, L"Unknown"); break; } switch (pKeyData->dwFinalValueType) { case REG_SZ: wcscpy(wszFinalValueType, L"REG_SZ"); break; case REG_EXPAND_SZ: wcscpy(wszFinalValueType, L"REG_EXPAND_SZ"); break; case REG_MULTI_SZ: wcscpy(wszFinalValueType, L"REG_MULTI_SZ"); break; case REG_DWORD: wcscpy(wszFinalValueType, L"REG_DWORD"); break; case REG_BINARY: wcscpy(wszFinalValueType, L"REG_BINARY"); break; default: wcscpy(wszFinalValueType, L"Unknown"); break; } // // If our temporary buffers are not large enough to store the data, reallocate. // if (pKeyData->cbOriginalDataSize > g_cbOriginalDataBufferSize) { pOriginalTmp = (LPWSTR)MemReAlloc(g_pwszOriginalData, pKeyData->cbOriginalDataSize + BUFFER_ALLOCATION_DELTA); if (pOriginalTmp) { g_pwszOriginalData = (LPWSTR)pOriginalTmp; } else { DPFN(eDbgLevelError, "[FormatValueData] Failed to reallocate for original data"); return; } g_cbOriginalDataBufferSize = pKeyData->cbOriginalDataSize + BUFFER_ALLOCATION_DELTA; } if (pKeyData->cbFinalDataSize > g_cbFinalDataBufferSize) { pFinalTmp = (LPWSTR)MemReAlloc(g_pwszFinalData, pKeyData->cbFinalDataSize + BUFFER_ALLOCATION_DELTA); if (pFinalTmp) { g_pwszFinalData = (LPWSTR)pFinalTmp; } else { DPFN(eDbgLevelError, "[FormatValueData] Failed to reallocate for new data"); return; } g_cbFinalDataBufferSize = pKeyData->cbFinalDataSize + BUFFER_ALLOCATION_DELTA; } // // Store the original and new data in the buffers. // Note that operations are performed differently based on the data type. // if (pKeyData->pOriginalData) { switch (pKeyData->dwOriginalValueType) { case REG_DWORD: wsprintf(g_pwszOriginalData, L"%lu", (*(DWORD*)pKeyData->pOriginalData)); break; case REG_SZ: case REG_EXPAND_SZ: wcscpy(g_pwszOriginalData, (const WCHAR*)pKeyData->pOriginalData); break; case REG_MULTI_SZ: ExtractMultiSzStrings(pKeyData->pOriginalData, &g_pwszOriginalData); break; case REG_BINARY: ExtractBinaryData(pKeyData->pOriginalData, pKeyData->cbOriginalDataSize, &g_pwszOriginalData); break; default: DPFN(eDbgLevelError, "[FormatValueData] Unsupported value type"); break; } } if (pKeyData->pFinalData) { switch (pKeyData->dwFinalValueType) { case REG_DWORD: wsprintf(g_pwszFinalData, L"%lu", (*(DWORD*)pKeyData->pFinalData)); break; case REG_SZ: case REG_EXPAND_SZ: wcscpy(g_pwszFinalData, (const WCHAR*)pKeyData->pFinalData); break; case REG_MULTI_SZ: ExtractMultiSzStrings(pKeyData->pFinalData, &g_pwszFinalData); break; case REG_BINARY: ExtractBinaryData(pKeyData->pFinalData, pKeyData->cbFinalDataSize, &g_pwszFinalData); break; default: DPFN(eDbgLevelError, "[FormatValueData] Unsupported value type"); break; } } // // Ensure that our unique id doesn't show up in the log. // if (_wcsicmp(pKeyData->wszValueName, g_wszUniqueId)) { pwszValueName = pKeyData->wszValueName; } // // Put the data into an XML element and log it. // FormatValueDataIntoElement(csFullKeyPath, pwszOperation, pwszValueName, wszOriginalValueType, wszFinalValueType); } /*++ Determines the changes that took place on the specified key and if applicable, writes it to the log. --*/ BOOL EvaluateKeyChanges( IN PLOG_OPEN_KEY pLogOpenKey ) { if (!pLogOpenKey) { DPFN(eDbgLevelError, "[EvaluateKeyChanges] Invalid parameter"); return FALSE; } // // 1. Check for deletion of an existing key. // if ((pLogOpenKey->dwFlags & LRC_DELETED_KEY) && (pLogOpenKey->dwFlags & LRC_EXISTING_KEY)) { FormatKeyDataIntoElement(L"Deleted Key", pLogOpenKey); return TRUE; } // // 2. Check for creation of a new key. // if (!(pLogOpenKey->dwFlags & LRC_EXISTING_KEY) && (!(pLogOpenKey->dwFlags & LRC_DELETED_KEY))) { FormatKeyDataIntoElement(L"Created Key", pLogOpenKey); return TRUE; } // // 3. Check for deletion of a non-existing key. // This is an indicator that we should not look for // changes to values below this key. // if (pLogOpenKey->dwFlags & LRC_DELETED_KEY) { return FALSE; } return TRUE; } /*++ Determines the changes that took place on the specified value and if applicable, writes it to the log. --*/ void EvaluateValueChanges( IN PKEY_DATA pKeyData, IN PLOG_OPEN_KEY pLogOpenKey ) { if (!pKeyData || !pLogOpenKey) { DPFN(eDbgLevelError, "[EvaluateValueChanges] Invalid parameter(s)"); return; } // // 1. Check for deletion of an existing value. // if ((pKeyData->dwFlags & LRC_DELETED_VALUE) && (pKeyData->dwFlags & LRC_EXISTING_VALUE)) { FormatValueData(L"Deleted Value", pKeyData, pLogOpenKey); return; } // // 2. Check for modification of an existing value. // if ((pKeyData->dwFlags & LRC_EXISTING_VALUE) && (pKeyData->dwFlags & LRC_MODIFIED_VALUE)) { FormatValueData(L"Modified Value", pKeyData, pLogOpenKey); return; } // // 3. Check for creation of a new value value. // if ((pKeyData->dwFlags & LRC_MODIFIED_VALUE) && (!(pKeyData->dwFlags & LRC_DELETED_VALUE) && (!(pKeyData->dwFlags & LRC_EXISTING_VALUE)))) { FormatValueData(L"Created Value", pKeyData, pLogOpenKey); return; } } /*++ Write the entire linked list out to the log file. --*/ BOOL WriteListToLogFile( void ) { PLIST_ENTRY pKeyNext = NULL; PLIST_ENTRY pValueHead = NULL; PLIST_ENTRY pValueNext = NULL; PKEY_DATA pKeyData = NULL; PLOG_OPEN_KEY pOpenKey = NULL; // // Write out modifications for keys. // pKeyNext = g_OpenKeyListHead.Blink; while (pKeyNext != &g_OpenKeyListHead) { pOpenKey = CONTAINING_RECORD(pKeyNext, LOG_OPEN_KEY, Entry); // // EvaluateKeyChanges will return TRUE if the key was not // deleted. If this is the case, continue the search and // evaluate changes to values within this key. // if (EvaluateKeyChanges(pOpenKey)) { // // Write out modifications for values. // pValueHead = &pOpenKey->KeyData; pValueNext = pValueHead->Blink; while (pValueNext != pValueHead) { pKeyData = CONTAINING_RECORD(pValueNext, KEY_DATA, Entry); EvaluateValueChanges(pKeyData, pOpenKey); pValueNext = pValueNext->Blink; } } pKeyNext = pKeyNext->Blink; } CloseLogFile(); return TRUE; } // // Begin implementation of the class. // LONG CLogRegistry::CreateKeyExA( HKEY hKey, LPCSTR pszSubKey, DWORD Reserved, LPSTR pszClass, DWORD dwOptions, REGSAM samDesired, LPSECURITY_ATTRIBUTES lpSecurityAttributes, PHKEY phkResult, LPDWORD lpdwDisposition ) { LPWSTR pwszSubKey = NULL; LPWSTR pwszClass = NULL; LONG lRetVal; // // Stub out to CreateKeyExW. // if (pszSubKey) { if (!ConvertAnsiToUnicode(pszSubKey, &pwszSubKey)) { DPFN(eDbgLevelError, "[CreateKeyExA] Ansi -> Unicode failed"); return ERROR_OUTOFMEMORY; } } if (pszClass) { if (!ConvertAnsiToUnicode(pszClass, &pwszClass)) { DPFN(eDbgLevelError, "[CreateKeyExA] Ansi to Unicode failed"); return ERROR_OUTOFMEMORY; } } lRetVal = CreateKeyExW( hKey, pwszSubKey, Reserved, pwszClass, dwOptions, samDesired, lpSecurityAttributes, phkResult, lpdwDisposition); if (pwszSubKey) { MemFree(pwszSubKey); } if (pwszClass) { MemFree(pwszClass); } return lRetVal; } LONG CLogRegistry::CreateKeyExW( HKEY hKey, LPCWSTR pwszSubKey, DWORD Reserved, LPWSTR pwszClass, DWORD dwOptions, REGSAM samDesired, LPSECURITY_ATTRIBUTES lpSecurityAttributes, PHKEY phkResult, LPDWORD lpdwDisposition ) { DWORD dwDisposition = 0; LONG lRetVal; BOOL fExisting = FALSE; if (!lpdwDisposition) { lpdwDisposition = &dwDisposition; } lRetVal = ORIGINAL_API(RegCreateKeyExW)( hKey, pwszSubKey, Reserved, pwszClass, dwOptions, samDesired, lpSecurityAttributes, phkResult, lpdwDisposition); if (lRetVal == ERROR_SUCCESS) { if (REG_OPENED_EXISTING_KEY == *lpdwDisposition) { fExisting = TRUE; } UpdateKeyList(hKey, *phkResult, pwszSubKey, NULL, fExisting, eAddKeyHandle); } return lRetVal; } LONG CLogRegistry::OpenKeyExA( HKEY hKey, LPCSTR pszSubKey, DWORD ulOptions, REGSAM samDesired, PHKEY phkResult ) { LPWSTR pwszSubKey = NULL; LONG lRetVal; // // Stub out to OpenKeyExW. // if (pszSubKey) { if (!ConvertAnsiToUnicode(pszSubKey, &pwszSubKey)) { DPFN(eDbgLevelError, "[OpenKeyExA] Ansi -> Unicode failed"); return ERROR_OUTOFMEMORY; } } lRetVal = OpenKeyExW( hKey, pwszSubKey, ulOptions, samDesired, phkResult); if (pwszSubKey) { MemFree(pwszSubKey); } return lRetVal; } LONG CLogRegistry::OpenKeyExW( HKEY hKey, LPCWSTR pwszSubKey, DWORD ulOptions, REGSAM samDesired, PHKEY phkResult ) { LONG lRetVal; lRetVal = ORIGINAL_API(RegOpenKeyExW)( hKey, pwszSubKey, ulOptions, samDesired, phkResult); if (lRetVal == ERROR_SUCCESS) { UpdateKeyList(hKey, *phkResult, pwszSubKey, NULL, TRUE, eAddKeyHandle); } return lRetVal; } LONG CLogRegistry::OpenCurrentUser( REGSAM samDesired, PHKEY phkResult ) { LONG lRetVal; lRetVal = ORIGINAL_API(RegOpenCurrentUser)( samDesired, phkResult); if (lRetVal == ERROR_SUCCESS) { UpdateKeyList(HKEY_CURRENT_USER, *phkResult, NULL, NULL, TRUE, eAddKeyHandle); } return lRetVal; } LONG CLogRegistry::OpenUserClassesRoot( HANDLE hToken, DWORD dwOptions, REGSAM samDesired, PHKEY phkResult ) { LONG lRetVal; lRetVal = ORIGINAL_API(RegOpenUserClassesRoot)( hToken, dwOptions, samDesired, phkResult); if (lRetVal == ERROR_SUCCESS) { UpdateKeyList(HKEY_CLASSES_ROOT, *phkResult, NULL, NULL, TRUE, eAddKeyHandle); } return lRetVal; } LONG CLogRegistry::SetValueA( HKEY hKey, LPCSTR pszSubKey, DWORD dwType, LPCSTR lpData, DWORD cbData ) { LPWSTR pwszSubKey = NULL; LPWSTR pwszData = NULL; LONG lRetVal; // // Stub out to SetValueW. // if (pszSubKey) { if (!ConvertAnsiToUnicode(pszSubKey, &pwszSubKey)) { DPFN(eDbgLevelError, "[SetValueA] Ansi -> Unicode failed"); return ERROR_OUTOFMEMORY; } } if (lpData) { if (!ConvertAnsiToUnicode(lpData, &pwszData)) { DPFN(eDbgLevelError, "[SetValueA] Ansi to Unicode failed"); return ERROR_OUTOFMEMORY; } } lRetVal = SetValueW( hKey, pwszSubKey, dwType, pwszData, cbData * sizeof(WCHAR)); if (pwszSubKey) { MemFree(pwszSubKey); } if (pwszData) { MemFree(pwszData); } return lRetVal; } LONG CLogRegistry::SetValueW( HKEY hKey, LPCWSTR pwszSubKey, DWORD dwType, LPCWSTR lpData, DWORD cbData ) { HKEY hKeyLocal; LONG lRetVal; // // Call OpenKeyEx to force this key to be added to the list. // if (pwszSubKey) { lRetVal = OpenKeyExW(hKey, pwszSubKey, 0, KEY_SET_VALUE, &hKeyLocal); if (ERROR_SUCCESS != lRetVal) { DPFN(eDbgLevelError, "[SetValueW] Failed to open key"); return lRetVal; } lRetVal = SetValueExW(hKeyLocal, NULL, 0, dwType, (const BYTE*)lpData, cbData); CloseKey(hKeyLocal); return lRetVal; } // // All other cases will be handled properly. // lRetVal = SetValueExW(hKey, NULL, 0, dwType, (const BYTE*)lpData, cbData); return lRetVal; } LONG CLogRegistry::SetValueExA( HKEY hKey, LPCSTR pszValueName, DWORD Reserved, DWORD dwType, CONST BYTE* lpData, DWORD cbData ) { LPWSTR pwszData = NULL; LPWSTR pwszValueName = NULL; LONG lRetVal; BOOL fString = FALSE; // // Stub out to SetValueExW. // if (pszValueName) { if (!ConvertAnsiToUnicode(pszValueName, &pwszValueName)) { DPFN(eDbgLevelError, "[SetValueExA] Ansi -> Unicode failed"); return ERROR_OUTOFMEMORY; } } if (REG_SZ == dwType || REG_EXPAND_SZ == dwType || REG_MULTI_SZ == dwType) { fString = TRUE; } // // If the data is of type string, convert it to Unicode. // if (lpData) { if (REG_MULTI_SZ == dwType) { if (!ConvertMultiSzToUnicode((LPCSTR)lpData, &pwszData)) { DPFN(eDbgLevelError, "[SetValueExA] Multi Sz to Unicode failed"); return ERROR_OUTOFMEMORY; } } else if (REG_SZ == dwType || REG_EXPAND_SZ == dwType) { if (!ConvertAnsiToUnicode((LPCSTR)lpData, &pwszData)) { DPFN(eDbgLevelError, "[SetValueExA] Ansi to Unicode failed"); return ERROR_OUTOFMEMORY; } } } if (fString) { lRetVal = SetValueExW( hKey, pwszValueName, Reserved, dwType, (const BYTE*)pwszData, cbData * sizeof(WCHAR)); } else { lRetVal = SetValueExW( hKey, pwszValueName, Reserved, dwType, lpData, cbData); } if (pwszValueName) { MemFree(pwszValueName); } if (pwszData) { MemFree(pwszData); } return lRetVal; } LONG CLogRegistry::SetValueExW( HKEY hKey, LPCWSTR pwszValueName, DWORD Reserved, DWORD dwType, CONST BYTE* lpData, DWORD cbData ) { LONG lRetVal; UpdateKeyList(NULL, hKey, NULL, pwszValueName, FALSE, eStartModifyValue); lRetVal = ORIGINAL_API(RegSetValueExW)( hKey, pwszValueName, Reserved, dwType, lpData, cbData); if (ERROR_SUCCESS == lRetVal) { UpdateKeyList(NULL, hKey, NULL, pwszValueName, FALSE, eEndModifyValue); } return lRetVal; } LONG CLogRegistry::CloseKey( HKEY hKey ) { UpdateKeyList(NULL, hKey, NULL, NULL, TRUE, eRemoveKeyHandle); return ORIGINAL_API(RegCloseKey)(hKey); } LONG CLogRegistry::DeleteKeyA( HKEY hKey, LPCSTR pszSubKey ) { LPWSTR pwszSubKey = NULL; LONG lRetVal; // // Stub out to DeleteKeyW. // if (pszSubKey) { if (!ConvertAnsiToUnicode(pszSubKey, &pwszSubKey)) { DPFN(eDbgLevelError, "[DeleteKeyA] Ansi -> Unicode failed"); return ERROR_OUTOFMEMORY; } } lRetVal = DeleteKeyW(hKey, pwszSubKey); if (pwszSubKey) { MemFree(pwszSubKey); } return lRetVal; } LONG CLogRegistry::DeleteKeyW( HKEY hKey, LPCWSTR pwszSubKey ) { LONG lRetVal; HKEY hKeyLocal; // // The caller can pass a predefined handle or an open key // handle. In all cases, we open the key they're passing // to force it into the list. Note that we mainly do // this for logging purposes only. // hKeyLocal = ForceSubKeyIntoList(hKey, pwszSubKey); lRetVal = ORIGINAL_API(RegDeleteKeyW)(hKey, pwszSubKey); if (ERROR_SUCCESS == lRetVal && hKeyLocal) { UpdateKeyList(NULL, hKeyLocal, pwszSubKey, NULL, TRUE, eDeletedKey); } if (hKeyLocal) { CloseKey(hKeyLocal); } return lRetVal; } LONG CLogRegistry::DeleteValueA( HKEY hKey, LPCSTR pszValueName ) { LPWSTR pwszValueName = NULL; LONG lRetVal; // // Stub out to DeleteValueW. // if (pszValueName) { if (!ConvertAnsiToUnicode(pszValueName, &pwszValueName)) { DPFN(eDbgLevelError, "[DeleteValueA] Ansi -> Unicode failed"); return ERROR_OUTOFMEMORY; } } lRetVal = DeleteValueW(hKey, pwszValueName); if (pwszValueName) { MemFree(pwszValueName); } return lRetVal; } LONG CLogRegistry::DeleteValueW( HKEY hKey, LPCWSTR pwszValueName ) { LONG lRetVal; UpdateKeyList(NULL, hKey, NULL, pwszValueName, TRUE, eStartDeleteValue); lRetVal = ORIGINAL_API(RegDeleteValueW)(hKey, pwszValueName); if (ERROR_SUCCESS == lRetVal) { UpdateKeyList(NULL, hKey, NULL, pwszValueName, TRUE, eEndDeleteValue); } return lRetVal; } CLogRegistry clr; // // Implemenation of the actual Registry API hooks. // LONG APIHOOK(RegOpenKeyA)( HKEY hKey, LPSTR lpSubKey, PHKEY phkResult ) { CLock cLock; return clr.OpenKeyExA( hKey, lpSubKey, 0, MAXIMUM_ALLOWED, phkResult); } LONG APIHOOK(RegOpenKeyW)( HKEY hKey, LPWSTR lpSubKey, PHKEY phkResult ) { CLock cLock; return clr.OpenKeyExW( hKey, lpSubKey, 0, MAXIMUM_ALLOWED, phkResult); } LONG APIHOOK(RegOpenKeyExA)( HKEY hKey, LPCSTR lpSubKey, DWORD ulOptions, REGSAM samDesired, PHKEY phkResult ) { CLock cLock; return clr.OpenKeyExA( hKey, lpSubKey, ulOptions, samDesired, phkResult); } LONG APIHOOK(RegOpenKeyExW)( HKEY hKey, LPCWSTR lpSubKey, DWORD ulOptions, REGSAM samDesired, PHKEY phkResult ) { CLock cLock; return clr.OpenKeyExW( hKey, lpSubKey, ulOptions, samDesired, phkResult); } LONG APIHOOK(RegOpenCurrentUser)( REGSAM samDesired, PHKEY phkResult ) { CLock cLock; return clr.OpenCurrentUser( samDesired, phkResult); } LONG APIHOOK(RegOpenUserClassesRoot)( HANDLE hToken, DWORD dwOptions, REGSAM samDesired, PHKEY phkResult ) { CLock cLock; return clr.OpenUserClassesRoot( hToken, dwOptions, samDesired, phkResult); } LONG APIHOOK(RegCreateKeyA)( HKEY hKey, LPCSTR lpSubKey, PHKEY phkResult ) { CLock cLock; return clr.CreateKeyExA( hKey, lpSubKey, 0, NULL, REG_OPTION_NON_VOLATILE, MAXIMUM_ALLOWED, NULL, phkResult, NULL); } LONG APIHOOK(RegCreateKeyW)( HKEY hKey, LPCWSTR lpSubKey, PHKEY phkResult ) { CLock cLock; return clr.CreateKeyExW( hKey, lpSubKey, 0, NULL, REG_OPTION_NON_VOLATILE, MAXIMUM_ALLOWED, NULL, phkResult, NULL); } LONG APIHOOK(RegCreateKeyExA)( HKEY hKey, LPCSTR lpSubKey, DWORD Reserved, LPSTR lpClass, DWORD dwOptions, REGSAM samDesired, LPSECURITY_ATTRIBUTES lpSecurityAttributes, PHKEY phkResult, LPDWORD lpdwDisposition ) { CLock cLock; return clr.CreateKeyExA( hKey, lpSubKey, Reserved, lpClass, dwOptions, samDesired, lpSecurityAttributes, phkResult, lpdwDisposition); } LONG APIHOOK(RegCreateKeyExW)( HKEY hKey, LPCWSTR lpSubKey, DWORD Reserved, LPWSTR lpClass, DWORD dwOptions, REGSAM samDesired, LPSECURITY_ATTRIBUTES lpSecurityAttributes, PHKEY phkResult, LPDWORD lpdwDisposition ) { CLock cLock; return clr.CreateKeyExW( hKey, lpSubKey, Reserved, lpClass, dwOptions, samDesired, lpSecurityAttributes, phkResult, lpdwDisposition); } LONG APIHOOK(RegSetValueA)( HKEY hKey, LPCSTR lpSubKey, DWORD dwType, LPCSTR lpData, DWORD cbData ) { CLock cLock; return clr.SetValueA( hKey, lpSubKey, dwType, lpData, cbData); } LONG APIHOOK(RegSetValueW)( HKEY hKey, LPCWSTR lpSubKey, DWORD dwType, LPCWSTR lpData, DWORD cbData ) { CLock cLock; return clr.SetValueW( hKey, lpSubKey, dwType, lpData, cbData); } LONG APIHOOK(RegSetValueExA)( HKEY hKey, LPCSTR lpSubKey, DWORD Reserved, DWORD dwType, CONST BYTE* lpData, DWORD cbData ) { CLock cLock; return clr.SetValueExA( hKey, lpSubKey, Reserved, dwType, lpData, cbData); } LONG APIHOOK(RegSetValueExW)( HKEY hKey, LPCWSTR lpSubKey, DWORD Reserved, DWORD dwType, CONST BYTE* lpData, DWORD cbData ) { CLock cLock; return clr.SetValueExW( hKey, lpSubKey, Reserved, dwType, lpData, cbData); } LONG APIHOOK(RegCloseKey)( HKEY hKey ) { CLock cLock; return clr.CloseKey( hKey); } LONG APIHOOK(RegDeleteKeyA)( HKEY hKey, LPCSTR lpSubKey ) { CLock cLock; return clr.DeleteKeyA( hKey, lpSubKey); } LONG APIHOOK(RegDeleteKeyW)( HKEY hKey, LPCWSTR lpSubKey ) { CLock cLock; return clr.DeleteKeyW( hKey, lpSubKey); } LONG APIHOOK(RegDeleteValueA)( HKEY hKey, LPCSTR lpValueName ) { CLock cLock; return clr.DeleteValueA( hKey, lpValueName); } LONG APIHOOK(RegDeleteValueW)( HKEY hKey, LPCWSTR lpValueName ) { CLock cLock; return clr.DeleteValueW( hKey, lpValueName); } /*++ Creates a unique id used to represent NULL values on registry calls. --*/ void InitializeNullValueId( void ) { SYSTEMTIME st; WCHAR* pwszSlash = NULL; WCHAR wszModPathName[MAX_PATH]; WCHAR wszShortName[MAX_PATH]; // // Because there is a NULL valuename for every key in the registry, // we need a unique key that we can use to represent NULL in our list. // if (!GetModuleFileName(NULL, wszModPathName, ARRAYSIZE(wszModPathName))) { wcscpy(wszModPathName, L"uniqueexeidentifier"); } pwszSlash = wcsrchr(wszModPathName, '\\'); if (pwszSlash) { wcsncpy(wszShortName, ++pwszSlash, (wcslen(pwszSlash)+1)); } GetLocalTime(&st); // // The format of our unique id will look like this: // processname.xxx-lrc-yymmdd-default // wsprintf(g_wszUniqueId, L"%ls-lrc-%02hu%02hu%02hu-default", wszShortName, st.wYear, st.wMonth, st.wDay); } /*++ Adds the predefined key handles to the list. --*/ BOOL AddPredefinedHandlesToList( void ) { UINT uCount; PLOG_OPEN_KEY pKey = NULL; HKEY rgKeys[NUM_PREDEFINED_HANDLES] = { HKEY_LOCAL_MACHINE, HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_USERS, HKEY_CURRENT_CONFIG, HKEY_DYN_DATA, HKEY_PERFORMANCE_DATA }; for (uCount = 0; uCount < NUM_PREDEFINED_HANDLES; uCount++) { pKey = (PLOG_OPEN_KEY)MemAlloc(sizeof(LOG_OPEN_KEY)); if (!pKey) { DPFN(eDbgLevelError, "[AddPredefinedHandlesToList] No memory available"); return FALSE; } pKey->pwszFullKeyPath = (LPWSTR)MemAlloc(MAX_ROOT_LENGTH * sizeof(WCHAR)); if (!pKey->pwszFullKeyPath) { DPFN(eDbgLevelError, "[AddPredefinedHandlesToList] Failed to allocate memory"); return FALSE; } if (!PredefinedKeyToString(rgKeys[uCount], &pKey->pwszFullKeyPath)) { DPFN(eDbgLevelError, "[AddPredefinedHandlesToList] PredefinedKey -> String failed"); return FALSE; } pKey->hKeyRoot = rgKeys[uCount]; pKey->hKeyBase[0] = rgKeys[uCount]; pKey->pwszSubKeyPath = NULL; pKey->dwFlags = LRC_EXISTING_KEY; pKey->cHandles = 1; InitializeListHead(&pKey->KeyData); InsertHeadList(&g_OpenKeyListHead, &pKey->Entry); } return TRUE; } /*++ Initialize the the list head and the log file. --*/ BOOL InitializeShim( void ) { CLock cLock; // // Initialize our open key handle list head and the // key data list head. // InitializeListHead(&g_OpenKeyListHead); // // Add the predefined handles to the list. // if (!AddPredefinedHandlesToList()) { return FALSE; } InitializeNullValueId(); // // Initialize our log file. // return InitializeLogFile(); } /*++ Handle process attach notification. --*/ BOOL NOTIFY_FUNCTION( DWORD fdwReason ) { if (fdwReason == DLL_PROCESS_ATTACH) { return InitializeShim(); } else if (fdwReason == DLL_PROCESS_DETACH) { return WriteListToLogFile(); } return TRUE; } SHIM_INFO_BEGIN() SHIM_INFO_DESCRIPTION(AVS_LOGREGCHANGES_DESC) SHIM_INFO_FRIENDLY_NAME(AVS_LOGREGCHANGES_FRIENDLY) SHIM_INFO_VERSION(1, 5) SHIM_INFO_FLAGS(AVRF_FLAG_NO_DEFAULT) SHIM_INFO_END() /*++ Register hooked functions. --*/ HOOK_BEGIN CALL_NOTIFY_FUNCTION DUMP_VERIFIER_LOG_ENTRY(VLOG_LOGREGCHANGES_LOGLOC, AVS_LOGREGCHANGES_LOGLOC, AVS_LOGREGCHANGES_LOGLOC_R, AVS_LOGREGCHANGES_LOGLOC_URL) APIHOOK_ENTRY(ADVAPI32.DLL, RegOpenKeyA) APIHOOK_ENTRY(ADVAPI32.DLL, RegOpenKeyW) APIHOOK_ENTRY(ADVAPI32.DLL, RegOpenKeyExA) APIHOOK_ENTRY(ADVAPI32.DLL, RegOpenKeyExW) APIHOOK_ENTRY(ADVAPI32.DLL, RegOpenCurrentUser) APIHOOK_ENTRY(ADVAPI32.DLL, RegOpenUserClassesRoot) APIHOOK_ENTRY(ADVAPI32.DLL, RegCreateKeyA) APIHOOK_ENTRY(ADVAPI32.DLL, RegCreateKeyW) APIHOOK_ENTRY(ADVAPI32.DLL, RegCreateKeyExA) APIHOOK_ENTRY(ADVAPI32.DLL, RegCreateKeyExW) APIHOOK_ENTRY(ADVAPI32.DLL, RegSetValueA) APIHOOK_ENTRY(ADVAPI32.DLL, RegSetValueW) APIHOOK_ENTRY(ADVAPI32.DLL, RegSetValueExA) APIHOOK_ENTRY(ADVAPI32.DLL, RegSetValueExW) APIHOOK_ENTRY(ADVAPI32.DLL, RegDeleteKeyA) APIHOOK_ENTRY(ADVAPI32.DLL, RegDeleteKeyW) APIHOOK_ENTRY(ADVAPI32.DLL, RegDeleteValueA) APIHOOK_ENTRY(ADVAPI32.DLL, RegDeleteValueW) APIHOOK_ENTRY(ADVAPI32.DLL, RegCloseKey) HOOK_END IMPLEMENT_SHIM_END