/*++ Copyright (c) 1995 Microsoft Corporation Module Name: qfecheck.c Abstract: Author: Alan Back (alanbac) 11-30-00 Environment: Windows 2000 Revision History: --*/ #include #include #include #include #include #include // includes basic windows functionality #include // includes the string functions #include #include #include "setupapi.h" // includes the inf setup api #include "spapip.h" #include #include #include #include "qfecheck.h" #define MISC_BUF_SIZE 4096 // // Globals // CHAR MiscBuffer2[ MISC_BUF_SIZE * 2]; CHAR System32Directory[ MAX_PATH ]; CHAR CmdLineLocation[MAX_PATH * 2 ]; CHAR ComputerName[ MAX_PATH ]; HANDLE LogFile = NULL; BOOL QuietMode = FALSE; BOOL Verbose = FALSE; BOOL DoLogging = FALSE; OSVERSIONINFOEX osvi; GUID DriverVerifyGuid = DRIVER_ACTION_VERIFY; // // pointers to the crypto functions we call // PCRYPTCATADMINACQUIRECONTEXT pCryptCATAdminAcquireContext; PCRYPTCATADMINRELEASECONTEXT pCryptCATAdminReleaseContext; PCRYPTCATADMINCALCHASHFROMFILEHANDLE pCryptCATAdminCalcHashFromFileHandle; PCRYPTCATADMINENUMCATALOGFROMHASH pCryptCATAdminEnumCatalogFromHash; PCRYPTCATCATALOGINFOFROMCONTEXT pCryptCATCatalogInfoFromContext; PCRYPTCATADMINRELEASECATALOGCONTEXT pCryptCATAdminReleaseCatalogContext; PWINVERIFYTRUST pWinVerifyTrust; PMULTIBYTETOUNICODE pMultiByteToUnicode; typedef HANDLE (WINAPI *CONNECTTOSFCSERVER)(PCWSTR); typedef BOOL (WINAPI *ISFILEPROTECTED)(HANDLE, LPCWSTR); typedef VOID (WINAPI *CLOSESFC)(HANDLE); VOID _cdecl main( int argc, char * argv[] ) { CHAR MiscBuffer[ MISC_BUF_SIZE ]; LPSTR p; CHAR szSourcePath[ MAX_PATH ]; CHAR LogName[ MAX_PATH ]; DWORD LogNameSize = sizeof( LogName ); // // Get the command line stuff // if ( !ParseArgs( argc, argv )) { LoadString(NULL, STR_USAGE, MiscBuffer, sizeof(MiscBuffer)); PrintStringToConsole( MiscBuffer ); return; } // // Get Version and SP info // osvi.dwOSVersionInfoSize = sizeof( osvi ); GetVersionEx( (LPOSVERSIONINFO)&osvi ); // // Don't run on anything less than Windows 2000 // if ( osvi.dwMajorVersion < 5 ) { LoadString(NULL, STR_USAGE, MiscBuffer, sizeof(MiscBuffer)); PrintStringToConsole( MiscBuffer ); return; } if ( !GetSystemDirectory( System32Directory, sizeof( System32Directory ))) { LoadString(NULL, STR_NO_SYSDIR, MiscBuffer, sizeof(MiscBuffer)); PrintStringToConsole( MiscBuffer ); return; } // // Get the computername and use it for the log file name. // If the user input a path location for the logfile, use it. // Otherwise, use the current directory (or failing that, system32). // if ( !GetComputerName( ComputerName, &LogNameSize )) { LoadString(NULL, STR_GETCOMPUTERNAME_FAILED, MiscBuffer, sizeof(MiscBuffer)); PrintStringToConsole( MiscBuffer ); return; } if ( DoLogging ) { strcpy( LogName, ComputerName ); strcat( LogName, ".log" ); if ( CmdLineLocation[0] ) { if (( GetFileAttributes( CmdLineLocation ) & FILE_ATTRIBUTE_DIRECTORY ) == 0 ) { LoadString(NULL, STR_USAGE, MiscBuffer, sizeof(MiscBuffer)); PrintStringToConsole( MiscBuffer ); return; } CombinePaths( CmdLineLocation, LogName, szSourcePath ); } else { GetModuleFileName( NULL, szSourcePath, sizeof( szSourcePath )); p = strrchr( szSourcePath, '\\' ); if ( p ) { *p = 0; } else { strcpy( szSourcePath, System32Directory ); } CombinePaths( szSourcePath, LogName, szSourcePath ); } // // Initialize the logfile using the machinename for the logfilename. // if ( !InitializeLog( TRUE, szSourcePath ) ) { LoadString(NULL, STR_LOGFILE_INIT_FAILED, MiscBuffer, sizeof(MiscBuffer)); PrintStringToConsole( MiscBuffer ); return; } } // // OK, now check that each of the files on the system match // what the hotfix info says should be there. // LogHeader(); if ( !ListNonMatchingHotfixes()) { LogItem( STR_NO_HOTFIXES_FOUND, NULL ); } LogItem( 0, "\r\n" ); if ( DoLogging ) { TerminateLog(); } return; } DWORD GetCSDVersion(VOID) /*++ Routine Description: Get the CSD Version number (to compare Service Pack number with) Return Value: The CSD Version number from the registry. --*/ { NTSTATUS status; HKEY hCSDKey; DWORD cbValue; DWORD dwType; DWORD dwLocalCSDVersion; TCHAR szWindows[] = TEXT("SYSTEM\\CurrentControlSet\\Control\\Windows"); TCHAR szCSD[] = TEXT("CSDVersion"); status = RegOpenKeyEx( HKEY_LOCAL_MACHINE, szWindows, 0, KEY_READ, &hCSDKey ); if (status != ERROR_SUCCESS){ return(0); } cbValue = sizeof(DWORD); status = RegQueryValueEx( hCSDKey, szCSD, NULL, // Reserved &dwType, (PVOID)&dwLocalCSDVersion, &cbValue // size in bytes returned ); RegCloseKey(hCSDKey); if ((status != ERROR_SUCCESS) || (dwType != REG_DWORD)) { return(0); } return (dwLocalCSDVersion); } VOID LogHeader( VOID ) { CHAR szInstallDate[MAX_PATH]; LARGE_INTEGER CurrentTime; LARGE_INTEGER LocalTime; TIME_FIELDS TimeFields; // // Log the product name. Prior check for < W2K prevents // need for additional checks here. // if (( osvi.dwMajorVersion == 5 ) && ( osvi.dwMinorVersion == 0 )) { LogItem( STR_VALIDATION_REPORT_W2K , NULL ); } else if (( osvi.dwMajorVersion == 5 ) && ( osvi.dwMinorVersion == 1 )) { LogItem( STR_VALIDATION_REPORT_XP, NULL ); } LogItem( 0, ComputerName ); // // Get the current system time // NtQuerySystemTime ( &CurrentTime ); RtlSystemTimeToLocalTime( &CurrentTime, &LocalTime ); RtlTimeToTimeFields( &LocalTime, &TimeFields ); sprintf( szInstallDate, "%d/%d/%d %d:%02d%s", TimeFields.Month, TimeFields.Day, TimeFields.Year, (TimeFields.Hour % 12 == 0 ) ? 12 : TimeFields.Hour % 12, TimeFields.Minute, TimeFields.Hour >= 12 ? "pm" : "am" ); LogItem( STR_REPORT_DATE, NULL ); LogItem( 0, szInstallDate ); LogItem( STR_SP_LEVEL, NULL ); if ( GetCSDVersion() != 0 ) { LogItem( 0, osvi.szCSDVersion ); } else { LogItem( STR_NO_SP_INSTALLED, NULL ); } LogItem( STR_HOTFIXES_ID, NULL ); } LPSTR CombinePaths( IN LPCSTR ParentPath, IN LPCSTR ChildPath, OUT LPSTR TargetPath // can be same as ParentPath if want to append ) { ULONG ParentLength = strlen( ParentPath ); LPSTR p; if ( ParentPath != TargetPath ) { memcpy( TargetPath, ParentPath, ParentLength ); } p = TargetPath + ParentLength; if (( ParentLength > 0 ) && ( *( p - 1 ) != '\\' ) && ( *( p - 1 ) != '/' )) { *p++ = '\\'; } strcpy( p, (ChildPath[0] != '\\') ? ChildPath : ChildPath+1 ); return TargetPath; } BOOL ListNonMatchingHotfixes( VOID ) /*++ Routine Description: Check out each of the files listed under each SP and hotfix, and 1) Check that the files present on the system have correct version info and 2) Check that the files present on the system have valid signatures in the installed catalogs. Arguments: None Return Value: FALSE if no hotfixes were found on the system. --*/ { char psz[MAX_PATH]; DWORD cch = MAX_PATH; FILETIME ft; NTSTATUS status; DWORD i; DWORD j; DWORD h; DWORD cbValue; DWORD dwType; char lpFileName[MAX_PATH]; char lpFileLocation[MAX_PATH]; char lpFileVersion[MAX_PATH]; char szRegW2K[MAX_PATH]; char szRegSP[MAX_PATH]; char szRegHotfix[MAX_PATH]; char szFilelist[MAX_PATH]; char szHotfixNumber[MAX_PATH]; char szSPNumber[MAX_PATH]; HKEY hW2KMainKey = 0; HKEY hHotfixMainKey = 0; HKEY hHotfixKey = 0; HKEY hFilelistKey = 0; DWORDLONG FileVersion; DWORDLONG TargetVersion; CHAR MiscBuffer1[ MISC_BUF_SIZE ]; CHAR LogBuffer[ MISC_BUF_SIZE ]; BOOL bIsHotfixWhacked; BOOL bIsSigInvalid; BOOL bAnyHotfixesInstalled = FALSE; LPWSTR FileName; LPWSTR FileLocation; HCATADMIN hCat = NULL; HANDLE hFileHandle = NULL; HANDLE hSfcServer = NULL; HINSTANCE hLibSfc = NULL; HMODULE hModuleWinTrust = NULL; HMODULE hModuleMsCat = NULL; HMODULE hModuleSetupApi = NULL; ISFILEPROTECTED IsFileProtected; CONNECTTOSFCSERVER ConnectToSfcServer; if (( osvi.dwMajorVersion == 5 ) && ( osvi.dwMinorVersion == 0 )) { strcpy( szRegW2K, "SOFTWARE\\Microsoft\\Updates\\Windows 2000\\" ); } else if (( osvi.dwMajorVersion == 5 ) && ( osvi.dwMinorVersion == 1 )) { strcpy( szRegW2K, "SOFTWARE\\Microsoft\\Updates\\Windows XP\\" ); } if (RegOpenKeyEx( HKEY_LOCAL_MACHINE, szRegW2K, 0, KEY_READ, &hW2KMainKey ) != ERROR_SUCCESS) { goto BailOut; } // // Get function names for all the crypto routines that we're going to call // hModuleWinTrust = LoadLibrary( "wintrust.dll" ); if ( hModuleWinTrust == NULL ) { goto BailOut; } hModuleMsCat = LoadLibrary( "mscat32.dll" ); if ( hModuleMsCat == NULL ) { goto BailOut; } hModuleSetupApi = LoadLibrary("setupapi.dll"); if(hModuleSetupApi == NULL) { goto BailOut; } pWinVerifyTrust = (PWINVERIFYTRUST)GetProcAddress( hModuleWinTrust, "WinVerifyTrust" ); pCryptCATAdminAcquireContext = (PCRYPTCATADMINACQUIRECONTEXT)GetProcAddress( hModuleMsCat, "CryptCATAdminAcquireContext" ); pCryptCATAdminReleaseContext = (PCRYPTCATADMINRELEASECONTEXT)GetProcAddress( hModuleMsCat, "CryptCATAdminReleaseContext" ); pCryptCATAdminCalcHashFromFileHandle = (PCRYPTCATADMINCALCHASHFROMFILEHANDLE)GetProcAddress( hModuleMsCat, "CryptCATAdminCalcHashFromFileHandle" ); pCryptCATAdminEnumCatalogFromHash = (PCRYPTCATADMINENUMCATALOGFROMHASH)GetProcAddress( hModuleMsCat, "CryptCATAdminEnumCatalogFromHash" ); pCryptCATCatalogInfoFromContext = (PCRYPTCATCATALOGINFOFROMCONTEXT)GetProcAddress( hModuleMsCat, "CryptCATCatalogInfoFromContext" ); pCryptCATAdminReleaseCatalogContext = (PCRYPTCATADMINRELEASECATALOGCONTEXT)GetProcAddress( hModuleMsCat, "CryptCATAdminReleaseCatalogContext" ); // Attempt to get Win2k version pMultiByteToUnicode = (PMULTIBYTETOUNICODE)GetProcAddress(hModuleSetupApi, "MultiByteToUnicode"); // Attempt to get Whistler version if(pMultiByteToUnicode == NULL) { pMultiByteToUnicode = (PMULTIBYTETOUNICODE)GetProcAddress(hModuleSetupApi, "pSetupMultiByteToUnicode"); } // Fail if(pMultiByteToUnicode == NULL) { goto BailOut; } pCryptCATAdminAcquireContext( &hCat, &DriverVerifyGuid, 0 ); // // Get the addresses for the SFC function calls // if ( (hLibSfc = LoadLibrary("SFC.DLL")) != NULL ) { ConnectToSfcServer = (CONNECTTOSFCSERVER)GetProcAddress( hLibSfc, (LPCSTR)0x00000003 ); if ( ConnectToSfcServer == NULL ) { goto BailOut; } hSfcServer = ConnectToSfcServer( NULL ); if ( hSfcServer == NULL ) { goto BailOut; } IsFileProtected = (ISFILEPROTECTED)GetProcAddress( hLibSfc, "SfcIsFileProtected" ); if ( IsFileProtected == NULL ) { goto BailOut; } } else { goto BailOut; } // // Now cycle through all the hotfixes stored in the registry // h = 0; while ( RegEnumKeyEx( hW2KMainKey, h, szSPNumber, &cch, NULL, NULL, NULL, &ft ) == ERROR_SUCCESS ) { strcpy( szRegSP, szRegW2K ); strcat( szRegSP, szSPNumber ); strcat( szRegSP, "\\" ); if (RegOpenKeyEx( HKEY_LOCAL_MACHINE, szRegSP, 0, KEY_READ, &hHotfixMainKey ) != ERROR_SUCCESS) { goto BailOut; } i = 0; cch = MAX_PATH; while ( RegEnumKeyEx( hHotfixMainKey, i, szHotfixNumber, &cch, NULL, NULL, NULL, &ft ) == ERROR_SUCCESS ) { strcpy( szRegHotfix, szRegSP ); strcat( szRegHotfix, szHotfixNumber ); strcat( szRegHotfix, "\\Filelist\\" ); strcpy( LogBuffer, szHotfixNumber ); strcat( LogBuffer, ": " ); LogItem( 0, "\r\n" ); LogItem( 0, LogBuffer ); bIsHotfixWhacked = FALSE; bIsSigInvalid = FALSE; bAnyHotfixesInstalled = TRUE; if ( RegOpenKeyEx( HKEY_LOCAL_MACHINE, szRegHotfix, 0, KEY_READ, &hHotfixKey ) == ERROR_SUCCESS) { j = 0; cch = MAX_PATH; while ( RegEnumKeyEx( hHotfixKey, j, psz, &cch, NULL, NULL, NULL, &ft ) == ERROR_SUCCESS ) { // // At this point, szFilelist is something like: // HKLM\SOFTWARE\Microsoft\Updates\Windows 2000\SP2\Q123456\Filelist // strcpy( szFilelist, szRegHotfix ); strcat( szFilelist, psz ); if ( RegOpenKeyEx( HKEY_LOCAL_MACHINE, szFilelist, 0, KEY_READ, &hFilelistKey ) == ERROR_SUCCESS) { // // Get all the values out of the registry we care about: // File location, name, and version string. // cbValue = MAX_PATH; status = RegQueryValueEx( hFilelistKey, "FileName", NULL, // Reserved &dwType, lpFileName, // Buffer &cbValue // size in bytes returned ); if ( status != ERROR_SUCCESS ) { goto BailOut; } cbValue = MAX_PATH; status = RegQueryValueEx( hFilelistKey, "Location", NULL, // Reserved &dwType, lpFileLocation, // Buffer &cbValue // size in bytes returned ); if ( status != ERROR_SUCCESS ) { goto BailOut; } cbValue = MAX_PATH; status = RegQueryValueEx( hFilelistKey, "Version", NULL, // Reserved &dwType, lpFileVersion, // Buffer &cbValue // size in bytes returned ); if ( status != ERROR_SUCCESS ) { continue; } // // Now see if the file in question got whacked by SFC // if ( ConvertVersionStringToQuad( lpFileVersion, &FileVersion )) { CombinePaths( lpFileLocation, lpFileName, lpFileLocation ); if (( MyGetFileVersion( lpFileLocation, &TargetVersion )) && ( TargetVersion < FileVersion )) { if ( !bIsHotfixWhacked ) { LogItem( STR_REINSTALL_HOTFIX, NULL ); if ( Verbose ) { LogItem( STR_FILES_MISSING, NULL ); } } if ( Verbose ) { LogItem( 0, "\t\t" ); LogItem( 0, _strupr( lpFileLocation )); LogItem( 0, "\r\n" ); } bIsHotfixWhacked = TRUE; } } } j++; cch = MAX_PATH; } // // Now do it again, but this time check each files' hash // against the installed catalog files. // j = 0; cch = MAX_PATH; while ( RegEnumKeyEx( hHotfixKey, j, psz, &cch, NULL, NULL, NULL, &ft ) == ERROR_SUCCESS ) { // // At this point, szFilelist is something like: // HKLM\SOFTWARE\Microsoft\Updates\Windows 2000\SP2\Q123456\Filelist // strcpy( szFilelist, szRegHotfix ); strcat( szFilelist, psz ); if ( RegOpenKeyEx( HKEY_LOCAL_MACHINE, szFilelist, 0, KEY_READ, &hFilelistKey ) == ERROR_SUCCESS) { // // Get all the values out of the registry we care about: // File location, name, and version string. // cbValue = MAX_PATH; status = RegQueryValueEx( hFilelistKey, "FileName", NULL, // Reserved &dwType, lpFileName, // Buffer &cbValue // size in bytes returned ); if ( status != ERROR_SUCCESS ) { goto BailOut; } cbValue = MAX_PATH; status = RegQueryValueEx( hFilelistKey, "Location", NULL, // Reserved &dwType, lpFileLocation, // Buffer &cbValue // size in bytes returned ); if ( status != ERROR_SUCCESS ) { goto BailOut; } // // Now see if the file in question has a hash // in an installed cat file. // CombinePaths( lpFileLocation, lpFileName, lpFileLocation ); FileName = pMultiByteToUnicode( (LPSTR)lpFileName, CP_ACP ); FileLocation = pMultiByteToUnicode( (LPSTR)lpFileLocation, CP_ACP ); hFileHandle = CreateFile( lpFileLocation, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL ); if ( hFileHandle == INVALID_HANDLE_VALUE ) { continue; } if ( IsFileProtected( hSfcServer, FileLocation )) { if ( !ValidateFileSignature( hCat, hFileHandle, FileName, FileLocation )) { if ( !bIsHotfixWhacked && !bIsSigInvalid ) { LogItem( STR_REINSTALL_HOTFIX, NULL ); } if ( Verbose && !bIsSigInvalid ) { LogItem( STR_NO_MATCHING_SIG, NULL ); } if ( Verbose ) { LogItem( 0, "\t\t" ); LogItem( 0, _strupr( lpFileLocation )); LogItem( 0, "\r\n" ); } bIsSigInvalid = TRUE; } } if ( hFileHandle ) { CloseHandle( hFileHandle ); } } j++; cch = MAX_PATH; } } i++; cch = MAX_PATH; if ( !bIsHotfixWhacked && !bIsSigInvalid ) { LogItem( STR_HOTFIX_CURRENT, NULL ); } } h++; cch = MAX_PATH; } BailOut: if ( hHotfixMainKey ) { CloseHandle( hHotfixMainKey ); } if ( hHotfixKey ) { CloseHandle( hHotfixKey ); } if ( hFilelistKey ) { CloseHandle( hFilelistKey ); } if ( hSfcServer ) { CLOSESFC CloseSfc = (CLOSESFC)GetProcAddress( hLibSfc, (LPCSTR)0x00000004 ); if ( CloseSfc != NULL ) { CloseSfc( hSfcServer ); } } if ( hCat ) { pCryptCATAdminReleaseContext( hCat, 0 ); } if ( hLibSfc ) { FreeLibrary( hLibSfc ); } if ( hModuleWinTrust ) { FreeLibrary( hModuleWinTrust ); } if ( hModuleMsCat ) { FreeLibrary( hModuleMsCat ); } if(hModuleSetupApi) { FreeLibrary(hModuleSetupApi); } return bAnyHotfixesInstalled; } BOOL MyGetFileVersion( IN LPCSTR FileName, OUT DWORDLONG *Version ) { BOOL Success = FALSE; DWORD Blah; DWORD Size; Size = GetFileVersionInfoSize( (LPSTR)FileName, &Blah ); if ( Size ) { PVOID Buffer = malloc( Size ); if ( Buffer ) { if ( GetFileVersionInfo( (LPSTR)FileName, 0, Size, Buffer )) { VS_FIXEDFILEINFO *VersionInfo; if ( VerQueryValue( Buffer, "\\", &VersionInfo, &Blah )) { *Version = (DWORDLONG)( VersionInfo->dwFileVersionMS ) << 32 | (DWORDLONG)( VersionInfo->dwFileVersionLS ); Success = TRUE; } } free( Buffer ); } } return Success; } BOOL ConvertVersionStringToQuad( IN LPCSTR lpFileVersion, OUT DWORDLONG *FileVersion ) { WORD FileverMSUW = 0; WORD FileverMSLW = 0; WORD FileverLSUW = 0; WORD FileverLSLW = 0; CHAR *p, *q, *r, *s, *t, *u, *w; p = strchr( lpFileVersion, '.' ); if ( p ) { q = p + 1; *p = 0; FileverMSUW = (WORD)atoi( lpFileVersion ); r = strchr( q, '.' ); if ( r ) { s = r + 1; *r = 0; FileverMSLW = (WORD)atoi( q ); t = strchr( s, '.' ); if ( t ) { u = t + 1; *t = 0; FileverLSUW = (WORD)atoi( s ); FileverLSLW = (WORD)atoi( u ); } else { return FALSE; } } else { return FALSE; } } else { return FALSE; } *FileVersion = (DWORDLONG)( FileverMSUW ) << 48 | (DWORDLONG)( FileverMSLW ) << 32 | (DWORDLONG)( FileverLSUW ) << 16 | (DWORDLONG)( FileverLSLW ); return TRUE; } BOOL InitializeLog( BOOL WipeLogFile, LPCSTR NameOfLogFile ) { // // If we're wiping the logfile clean, attempt to delete // what's there. // if ( WipeLogFile ) { SetFileAttributes( NameOfLogFile, FILE_ATTRIBUTE_NORMAL ); DeleteFile( NameOfLogFile ); } // // Open/create the file. // LogFile = CreateFile( NameOfLogFile, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); if ( LogFile == INVALID_HANDLE_VALUE ) { LogFile = NULL; } return( LogFile != NULL ); } VOID TerminateLog( VOID ) { if( LogFile ) { CloseHandle( LogFile ); LogFile = NULL; } } BOOL LogItem( IN DWORD Description, IN LPCSTR LogString ) { BOOL b = FALSE; DWORD BytesWritten; CHAR TextBuffer[ MISC_BUF_SIZE ]; if ( !Description ) { // // Description of 0 means use the passed in LogString instead // strcpy( TextBuffer, LogString ); } else { // // Get the string from the resource // LoadString( NULL, Description, TextBuffer, sizeof(TextBuffer) ); } PrintStringToConsole( TextBuffer ); if ( LogFile ) { // // Make sure we write to current end of file. // SetFilePointer( LogFile, 0, NULL, FILE_END ); // // Write the text. // b = WriteFile( LogFile, TextBuffer, strlen(TextBuffer), &BytesWritten, NULL ); } else { // // No logging, just return success // b = TRUE; } return( b ); } void MyLowerString( IN PWSTR String, IN ULONG StringLength // in characters ) { ULONG i; for (i=0; i