//+--------------------------------------------------------------------------- // // Microsoft Windows // Copyright(C) 2002 Microsoft Corporation // // File: sysprep.cxx // //---------------------------------------------------------------------------- #include "..\pch\headers.hxx" #include "security.hxx" #include "sysprep.hxx" static WCHAR gwszSysprepKey[] = L"TSSK"; // Task Scheduler Sysprep Key static WCHAR gwszSysprepIdentity[] = L"TSSI"; // Task Scheduler Sysprep Identity Data // needed by ScavengeSASecurityDBase extern SERVICE_STATUS g_SvcStatus; #define SERVICE_RUNNING 0x00000004 //+--------------------------------------------------------------------------- // // Function: GetUniqueSPSName // // Synopsis: calls NewWorkItem a few times trying to get a unique file name out of it // // Arguments: ITaskScheduler *pITaskScheduler, IUnknown** pITask // WCHAR* pwszTaskName, to receive the name that was actually // used in the creation of the task // // Returns: Various HRESULTs // // Notes: None. // //---------------------------------------------------------------------------- HRESULT GetUniqueSPSName(ITaskScheduler* pITaskScheduler, ITask** ppITask, WCHAR* pwszTaskName) { HRESULT hr = E_FAIL; // okay, so we're not distinguishing between errors, code's simpler // and the only expectable error is "already exists" for (int i = 0; (i < 16) && FAILED(hr); i++) { if (FAILED(StringCchPrintf(pwszTaskName, 20, L"$~$Sys%X$", i))) break; hr = pITaskScheduler->NewWorkItem(pwszTaskName, CLSID_CTask, IID_ITask, (IUnknown**)ppITask); } return hr; } //+--------------------------------------------------------------------------- // // Function: PrepSysPrepTask // // Synopsis: Creates a task to be run which will call run the sysprep // code in the local system account // // Arguments: Task** ppITaskToRun, to receive pointer to ITask interface // task is to activated by calling the Run() method // WCHAR* pwszTaskName, to receive the name that was actually // used in the creation of the task // // Returns: Various HRESULTs // // Notes: None. // //---------------------------------------------------------------------------- HRESULT PrepSysPrepTask(ITask** ppITaskToRun, WCHAR* pwszTaskName) { HRESULT hr = E_FAIL; WCHAR applicationName[MAX_PATH +1]; WCHAR argument[MAX_PATH +20]; DWORD expandedSize; expandedSize = ExpandEnvironmentStrings(L"\"%SystemRoot%\\System32\\rundll32.exe\"", applicationName, MAX_PATH +1); if ((0 == expandedSize) || expandedSize > (MAX_PATH +1)) return HRESULT_FROM_WIN32(GetLastError()); expandedSize = ExpandEnvironmentStrings(L"\"%SystemRoot%\\System32\\SchedSvc.dll\",SysPrepCallback", argument, MAX_PATH +20); if ((0 == expandedSize) || expandedSize > (MAX_PATH +1)) return HRESULT_FROM_WIN32(GetLastError()); TASK_TRIGGER tigger; tigger.wStartHour = 10; tigger.wStartMinute = 20; tigger.wBeginYear = 1957; tigger.wBeginMonth = 6; tigger.wBeginDay = 14; tigger.wEndYear = 2001; tigger.wEndMonth = 10; tigger.wEndDay = 8 ; tigger.MinutesDuration = 0; tigger.MinutesInterval = 0; tigger.rgFlags = 0; tigger.TriggerType = TASK_TIME_TRIGGER_ONCE; tigger.Reserved2 = 0; tigger.wRandomMinutesInterval = 0; tigger.cbTriggerSize = sizeof(TASK_TRIGGER); tigger.Reserved1 = 0; ITaskScheduler *pITaskScheduler = NULL; ITask *pITask = NULL; IPersistFile *pIPersistFile = NULL; ITaskTrigger* pTrigger = NULL; WORD idontcare; hr = CoCreateInstance( CLSID_CTaskScheduler, NULL, CLSCTX_INPROC_SERVER, IID_ITaskScheduler, (void**)&pITaskScheduler); if (SUCCEEDED(hr) && SUCCEEDED(hr = GetUniqueSPSName(pITaskScheduler, &pITask, pwszTaskName)) && SUCCEEDED(hr = pITask->SetApplicationName(applicationName)) && SUCCEEDED(hr = pITask->SetParameters(argument)) && SUCCEEDED(hr = pITask->SetFlags(TASK_FLAG_DELETE_WHEN_DONE | TASK_FLAG_DISABLED)) && SUCCEEDED(hr = pITask->SetAccountInformation(L"", NULL)) && SUCCEEDED(hr = pITask->CreateTrigger(&idontcare, &pTrigger)) && SUCCEEDED(hr = pTrigger->SetTrigger(&tigger)) && SUCCEEDED(hr = pITask->QueryInterface(IID_IPersistFile, (void **)&pIPersistFile)) && SUCCEEDED(hr = pIPersistFile->Save(NULL, TRUE))) { // return it to the caller *ppITaskToRun = pITask; } else if (pITask) pITask->Release(); if (pITaskScheduler) pITaskScheduler->Release(); if (pIPersistFile) pIPersistFile->Release(); if (pTrigger) pTrigger->Release(); return hr; } //+--------------------------------------------------------------------------- // // Function: SaveSysprepInfo // // Synopsis: Saves job identity and credential key information prior to Sysprep // so that it can be used to convert old data after Sysprep. // // Arguments: None. // // Returns: HRESULT // //---------------------------------------------------------------------------- HRESULT SaveSysprepInfo(void) { // // Initialize security // InitSS(); SetMysteryDWORDValue(); HRESULT hr = PreProcessNetScheduleJobs(); if (FAILED(hr)) { // ignore, we still need to do the other processing } // // Obtain a provider handle to the CSP (for use with Crypto API). // HCRYPTPROV hCSP = NULL; hr = GetCSPHandle(&hCSP); if (SUCCEEDED(hr)) { if (hCSP) { // // We've got the handle, now save the important stuff // hr = SaveSysprepKeyInfo(hCSP); if (SUCCEEDED(hr)) { hr = SaveSysprepIdentityInfo(hCSP); } CloseCSPHandle(hCSP); } else { hr = E_FAIL; } } // // Done doing security stuff // UninitSS(); if (FAILED(hr)) { CHECK_HRESULT(hr); } return hr; } //+--------------------------------------------------------------------------- // // Function: SaveSysprepKeyInfo // // Synopsis: Stores the computed CredentialKey as a secret prior to sysprep so that it can be // retrieved and used to decrypt credentials after sysprep, so those credentials may // be encrypted again using the post-sysprep key. // // Arguments: hCSP - handle to the crypto service provider // // Returns: HRESULT // //---------------------------------------------------------------------------- HRESULT SaveSysprepKeyInfo(HCRYPTPROV hCSP) { // // Generate the encryption key // RC2_KEY_INFO RC2KeyInfo; HRESULT hr = ComputeCredentialKey(hCSP, &RC2KeyInfo); if (SUCCEEDED(hr)) { // // Write the key to LSA // hr = WriteLsaData(sizeof(gwszSysprepKey), gwszSysprepKey, sizeof(RC2KeyInfo), (BYTE*)&RC2KeyInfo); } return hr; } //+--------------------------------------------------------------------------- // // Function: SaveSysprepIdentityInfo // // Synopsis: Obtains the credential index and NULL password setting for each // existing job identity and stores that information along with the // associated filename in an LSA secret so that the identities can // be rehashed and stored in the identity database, postsysprep, // still associated with the correct credential. // // Arguments: hCSP - handle to the crypto service provider // // Returns: HRESULT // //---------------------------------------------------------------------------- HRESULT SaveSysprepIdentityInfo(HCRYPTPROV hCSP) { WCHAR* pwszTasksFolder = NULL; HANDLE hFileEnum = INVALID_HANDLE_VALUE; DWORD cbSAI; DWORD cbSAC; BYTE* pbSAI = NULL; BYTE* pbSAC = NULL; // // First, read the security database so we can do the lookups // HRESULT hr = ReadSecurityDBase(&cbSAI, &pbSAI, &cbSAC, &pbSAC); if (SUCCEEDED(hr)) { if (cbSAI <= SAI_HEADER_SIZE) { // // Database empty, nothing to do. // } else { // // Enumerate job objects in the task's folder directory. // hr = GetTasksFolder(&pwszTasksFolder); if (SUCCEEDED(hr)) { WCHAR wszSearchPath[MAX_PATH + 1]; hr = StringCchCopy(wszSearchPath, MAX_PATH + 1, pwszTasksFolder); if (SUCCEEDED(hr)) { hr = StringCchCat(wszSearchPath, MAX_PATH + 1, EXTENSION_WILDCARD TSZ_JOB); if (SUCCEEDED(hr)) { WIN32_FIND_DATA fd; DWORD dwRet = 0; if ((hFileEnum = FindFirstFile(wszSearchPath, &fd)) == INVALID_HANDLE_VALUE) { // // Either no jobs (this is OK), or an error occurred. // dwRet = GetLastError(); if (dwRet != ERROR_FILE_NOT_FOUND) hr = _HRESULT_FROM_WIN32(dwRet); } else { // // Must concatenate the filename returned from the enumeration onto the folder path // before computing the hash. Prepare for doing that repeatedly by taking the path, // adding a slash, and remembering the next character position. // hr = StringCchCopy(wszSearchPath, MAX_PATH + 1, pwszTasksFolder); if (SUCCEEDED(hr)) { DWORD iConcatenation = lstrlenW(pwszTasksFolder); wszSearchPath[iConcatenation++] = L'\\'; // // Allocate buffer used to collect identity data that will be written to the LSA secret // BYTE rgbIdentityData[MAX_SECRET_SIZE]; BYTE* pbCurrentData = (BYTE*)&rgbIdentityData; DWORD cbIdentityData = 0; // // Process each found file // BYTE rgbIdentity[HASH_DATA_SIZE]; BOOL bIsPasswordNull; DWORD dwCredentialIndex; DWORD cbFileName; while (dwRet != ERROR_NO_MORE_FILES) { // // Truncate the existing name after the folder path, // then concatenate the new filename onto it. // wszSearchPath[iConcatenation] = L'\0'; if (SUCCEEDED(StringCchCat(wszSearchPath, MAX_PATH + 1, fd.cFileName))) { // // Hash the job into a unique identity. // if (SUCCEEDED(HashJobIdentity(hCSP, wszSearchPath, rgbIdentity))) { // // Find the identity in the SAI for this job // hr = SAIFindIdentity(rgbIdentity, cbSAI, pbSAI, &dwCredentialIndex, &bIsPasswordNull, NULL, NULL, NULL); // // S_OK means the identity was found; S_FALSE means it wasn't // Other codes are errors. We process only the S_OKs, of course. // if (S_OK == hr) { cbFileName = (lstrlenW(fd.cFileName) + 1) * sizeof(WCHAR); if ((cbIdentityData + cbFileName + sizeof(BOOL) + sizeof(DWORD)) > MAX_SECRET_SIZE) { // // this should _never_ happen, as we shouldn't be able to exceed the size // given that we only add data for jobs that have already been found in // the SAI, and we are collecting less data than was stored there // hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); break; } CopyMemory(pbCurrentData, fd.cFileName, cbFileName); pbCurrentData += cbFileName; CopyMemory(pbCurrentData, &bIsPasswordNull, sizeof(BOOL)); pbCurrentData += sizeof(BOOL); CopyMemory(pbCurrentData, &dwCredentialIndex, sizeof(DWORD)); pbCurrentData += sizeof(DWORD); cbIdentityData += (cbFileName + sizeof(BOOL) + sizeof(DWORD)); } else { hr = S_OK; // OK, we failed this one, go on to the next } } } if (!FindNextFile(hFileEnum, &fd)) { dwRet = GetLastError(); if (dwRet != ERROR_NO_MORE_FILES) hr = _HRESULT_FROM_WIN32(dwRet); break; } } if (HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) != hr) { hr = WriteLsaData(sizeof(gwszSysprepIdentity), gwszSysprepIdentity, cbIdentityData, (BYTE*)&rgbIdentityData); } } } } } } } } if (pbSAI) LocalFree(pbSAI); if (pbSAC) LocalFree(pbSAC); if (pwszTasksFolder) delete [] pwszTasksFolder; if (hFileEnum != INVALID_HANDLE_VALUE) FindClose(hFileEnum); return hr; } //+--------------------------------------------------------------------------- // // Function: PreProcessNetScheduleJobs // // Synopsis: Netschedule jobs (AT jobs) are signed internally with a hash so // that the service can validate their authenticity at run time. // Sysprep changes important data used by the Crypto API such that // the generated hash will be different and can never match the hash // stored in the AT job. In order to be runnable again, the AT jobs // must be signed again with the new hash. // // *** WARNING *** // This means that if someone could drop a bogus AT job into the tasks // folder, it would automatically get signed as a result of running sysprep. // In order to prevent this, check all AT jobs prior to sysprep and // eliminate any that are not valid. // // Arguments: None // // Returns: HRESULT // //---------------------------------------------------------------------------- HRESULT PreProcessNetScheduleJobs(void) { HANDLE hFileEnum = INVALID_HANDLE_VALUE; WCHAR* pwszTasksFolder = NULL; HRESULT hr = GetTasksFolder(&pwszTasksFolder); if (SUCCEEDED(hr)) { WCHAR wszSearchPath[MAX_PATH + 1]; hr = StringCchCopy(wszSearchPath, MAX_PATH + 1, pwszTasksFolder); if (SUCCEEDED(hr)) { hr = StringCchCat(wszSearchPath, MAX_PATH + 1, L"\\" TSZ_AT_JOB_PREFIX L"*" TSZ_DOTJOB); if (SUCCEEDED(hr)) { WIN32_FIND_DATA fd; DWORD dwRet = 0; if ((hFileEnum = FindFirstFile(wszSearchPath, &fd)) == INVALID_HANDLE_VALUE) { // // Either no jobs (this is OK), or an error occurred. // dwRet = GetLastError(); if (dwRet != ERROR_FILE_NOT_FOUND) hr = _HRESULT_FROM_WIN32(dwRet); } else { // // Must concatenate the filename returned from the enumeration onto the folder path // before loading the job. Prepare for doing that repeatedly by taking the path, // adding a slash, and remembering the next character position. // hr = StringCchCopy(wszSearchPath, MAX_PATH + 1, pwszTasksFolder); if (SUCCEEDED(hr)) { DWORD iConcatenation = lstrlenW(pwszTasksFolder); wszSearchPath[iConcatenation++] = L'\\'; // // Process each found file // while (dwRet != ERROR_NO_MORE_FILES) { // // Truncate the existing name after the folder path, // then concatenate the new filename onto it. // wszSearchPath[iConcatenation] = L'\0'; if (SUCCEEDED(StringCchCat(wszSearchPath, MAX_PATH + 1, fd.cFileName))) { // // Load and check signature of each job // CJob* pJob = CJob::Create(); if (!pJob) { hr = E_OUTOFMEMORY; break; } if (SUCCEEDED(pJob->Load(wszSearchPath, 0))) { if (!pJob->VerifySignature()) { if (!DeleteFile(wszSearchPath)) { dwRet = GetLastError(); // go on anyway } } } pJob->Release(); } if (!FindNextFile(hFileEnum, &fd)) { dwRet = GetLastError(); if (dwRet != ERROR_NO_MORE_FILES) hr = _HRESULT_FROM_WIN32(dwRet); break; } } } } if (hFileEnum != INVALID_HANDLE_VALUE) FindClose(hFileEnum); } } } if (pwszTasksFolder) delete [] pwszTasksFolder; return hr; } //+--------------------------------------------------------------------------- // // Function: GetSysprepIdentityInfo // // Synopsis: Retrieves the stored job identity data so new hashes can be calculated. // // Arguments: pcbIdentityData - pointer to count of bytes in the identity data // ppIdentityData - pointer to pointer to retrieved identity data // // Returns: HRESULT // //---------------------------------------------------------------------------- HRESULT GetSysprepIdentityInfo(DWORD* pcbIdentityData, BYTE** ppIdentityData) { *ppIdentityData = NULL; // // Retrieve job identity data from LSA, if it exists // HRESULT hr = ReadLsaData(sizeof(gwszSysprepIdentity), gwszSysprepIdentity, pcbIdentityData, ppIdentityData); if (FAILED(hr)) { if (*ppIdentityData != NULL) { LocalFree(*ppIdentityData); *ppIdentityData = NULL; } } return hr; } //+--------------------------------------------------------------------------- // // Function: GetSysprepKeyInfo // // Synopsis: Retrieves the stored pre-sysprep CredentialKey so that it can be used to decrypt // credentials after sysprep, so those credentials may be encrypted again using the // post-sysprep key. // // Arguments: pcbRC2KeyInfo - pointer to count of bytes in key data // ppRC2KeyInfo - pointer to pointer to retrieved key // // Returns: HRESULT // //---------------------------------------------------------------------------- HRESULT GetSysprepKeyInfo(DWORD* pcbRC2KeyInfo, RC2_KEY_INFO** ppRC2KeyInfo) { *ppRC2KeyInfo = NULL; // // Get the key from LSA // HRESULT hr = ReadLsaData(sizeof(gwszSysprepKey), gwszSysprepKey, pcbRC2KeyInfo, (BYTE**)ppRC2KeyInfo); if (SUCCEEDED(hr)) { // // Check the size. We know exactly how big this should be, so make sure it is. // if (*pcbRC2KeyInfo != sizeof(RC2_KEY_INFO)) { *pcbRC2KeyInfo = 0; LocalFree(*ppRC2KeyInfo); *ppRC2KeyInfo = NULL; hr = E_FAIL; } } else // failed! { if (*ppRC2KeyInfo != NULL) { LocalFree(*ppRC2KeyInfo); *ppRC2KeyInfo = NULL; } } return hr; } //+--------------------------------------------------------------------------- // // Function: ConvertSysprepInfo // // Synopsis: Converts credentials stored prior to sysprep so they may be decrypted after sysprep. // It does this by retrieving the pre-sysprep key, decrypting the credentials with it, and then // encrypting them using the new post-sysprep key. // // Arguments: None // // Returns: HRESULT // //---------------------------------------------------------------------------- HRESULT ConvertSysprepInfo(void) { // // Initialize these up here so they will have values if we exit early // HCRYPTPROV hCSP = NULL; BYTE* pbSAI = NULL; DWORD cbSAI = 0; BYTE* pbSAC = NULL; DWORD cbSAC = 0; RC2_KEY_INFO* pRC2KeyPreSysprep = NULL; DWORD cbRC2KeyPreSysprep = 0; RC2_KEY_INFO RC2KeyPostSysprep; // // Initialize security // InitSS(); SetMysteryDWORDValue(); // // Set this as it is relied on by ScavengeSASecurityDBase // g_SvcStatus.dwCurrentState = SERVICE_RUNNING; // // Get the tasks folder for use by ConvertNetScheduleJobs, ConvertIdentityData, and ScavengeSASecurityDBase // HRESULT hr = GetTasksFolder(&g_TasksFolderInfo.ptszPath); if (FAILED(hr)) { goto ErrorExit; } // // Obtain a provider handle to the CSP (for use with Crypto API) // hr = GetCSPHandle(&hCSP); if (FAILED(hr)) { goto ErrorExit; } // // Get the pre-sysprep key from LSA // hr = GetSysprepKeyInfo(&cbRC2KeyPreSysprep, &pRC2KeyPreSysprep); if (FAILED(hr)) { goto ErrorExit; } // // Generate the post-sysprep key // hr = ComputeCredentialKey(hCSP, &RC2KeyPostSysprep); if (FAILED(hr)) { goto ErrorExit; } // // The Net Schedule conversions are independent of the rest of the logic, // so do them first and get them out of the way. // hr = ConvertNetScheduleJobs(); if (FAILED(hr)) { // ignore, we still can do our other conversions hr = S_OK; } hr = ConvertNetScheduleCredentialData(pRC2KeyPreSysprep, &RC2KeyPostSysprep); if (FAILED(hr)) { // ignore, we still can do our other conversions hr = S_OK; } // // Read SAI & SAC databases. // It is not necessary to guard security db access with a critsec as elsewhere in the code, // as the service will not be running during MiniSetup and there will be no other threads accessing this data. // hr = ReadSecurityDBase(&cbSAI, &pbSAI, &cbSAC, &pbSAC); if (FAILED(hr)) { goto ErrorExit; } // // Do some validations on the database // if (cbSAI <= SAI_HEADER_SIZE || pbSAI == NULL || cbSAC <= SAC_HEADER_SIZE || pbSAC == NULL) { goto ErrorExit; } // // Place updated entries into SAI for all jobs // hr = ConvertIdentityData(hCSP, &cbSAI, &pbSAI, &cbSAC, &pbSAC); if (FAILED(hr)) { goto ErrorExit; } // // Place updated entries into SAC for all credentials // hr = ConvertCredentialData(pRC2KeyPreSysprep, &RC2KeyPostSysprep, &cbSAI, &pbSAI, &cbSAC, &pbSAC); // // Clearing key content at earliest opportunity // SecureZeroMemory(&RC2KeyPostSysprep, sizeof(RC2KeyPostSysprep)); if (SUCCEEDED(hr)) { // // Update security database with new identities and converted credentials // hr = WriteSecurityDBase(cbSAI, pbSAI, cbSAC, pbSAC); if (SUCCEEDED(hr)) { // // Allow scavenger cleanup to delete old identities // ScavengeSASecurityDBase(); } } ErrorExit: // // Delete the secrets // DeleteLsaData(sizeof(gwszSysprepKey), gwszSysprepKey); DeleteLsaData(sizeof(gwszSysprepIdentity), gwszSysprepIdentity); // // Clean up // if (g_TasksFolderInfo.ptszPath) delete [] g_TasksFolderInfo.ptszPath; if (hCSP) CloseCSPHandle(hCSP); if (pbSAI) LocalFree(pbSAI); if (pbSAC) LocalFree(pbSAC); if (pRC2KeyPreSysprep) LocalFree(pRC2KeyPreSysprep); // // Log an error & reset the SA security dbases SAI & SAC if corruption is detected. // if (hr == SCHED_E_ACCOUNT_DBASE_CORRUPT) { // // Reset SAI & SAC by writing four bytes of zeros into each. // Ignore the return code. No recourse if this fails. // DWORD dwZero = 0; WriteSecurityDBase(sizeof(dwZero), (BYTE*)&dwZero, sizeof(dwZero), (BYTE*)&dwZero); } // // Done doing security stuff // UninitSS(); return(hr); } //+--------------------------------------------------------------------------- // // Function: ConvertNetScheduleJobs // // Synopsis: Netschedule jobs (AT jobs) are signed internally with a hash so // that the service can validate their authenticity at run time. // Sysprep changes important data used by the Crypto API such that // the generated hash will be different and can never match the hash // stored in the AT job. In order to be runnable again, the AT jobs // must be signed again with the new hash. // // *** WARNING *** // This means that if someone could drop a bogus AT job into the tasks // folder, it would automatically get signed as a result of running sysprep. // In order to prevent this, PreProcessNetScheduleJobs has already checked // all AT jobs prior to sysprep and eliminated any that were not valid. // Arguments: None // // Returns: HRESULT // //---------------------------------------------------------------------------- HRESULT ConvertNetScheduleJobs(void) { HRESULT hr = S_OK; HANDLE hFileEnum = INVALID_HANDLE_VALUE; WCHAR wszSearchPath[MAX_PATH + 1]; hr = StringCchCopy(wszSearchPath, MAX_PATH + 1, g_TasksFolderInfo.ptszPath); if (SUCCEEDED(hr)) { hr = StringCchCat(wszSearchPath, MAX_PATH + 1, L"\\" TSZ_AT_JOB_PREFIX L"*" TSZ_DOTJOB); if (SUCCEEDED(hr)) { WIN32_FIND_DATA fd; DWORD dwRet = 0; if ((hFileEnum = FindFirstFile(wszSearchPath, &fd)) == INVALID_HANDLE_VALUE) { // // Either no jobs (this is OK), or an error occurred. // dwRet = GetLastError(); if (dwRet != ERROR_FILE_NOT_FOUND) hr = _HRESULT_FROM_WIN32(dwRet); } else { // // Must concatenate the filename returned from the enumeration onto the folder path // before loading the job. Prepare for doing that repeatedly by taking the path, // adding a slash, and remembering the next character position. // hr = StringCchCopy(wszSearchPath, MAX_PATH + 1, g_TasksFolderInfo.ptszPath); if (SUCCEEDED(hr)) { DWORD iConcatenation = lstrlenW(g_TasksFolderInfo.ptszPath); wszSearchPath[iConcatenation++] = L'\\'; // // Process each found file // while (dwRet != ERROR_NO_MORE_FILES) { // // Truncate the existing name after the folder path, // then concatenate the new filename onto it. // wszSearchPath[iConcatenation] = L'\0'; if (SUCCEEDED(StringCchCat(wszSearchPath, MAX_PATH + 1, fd.cFileName))) { // // Load, sign, save, and release the job // CJob* pJob = CJob::Create(); if (!pJob) { hr = E_OUTOFMEMORY; break; } if (SUCCEEDED(pJob->Load(wszSearchPath, 0))) { if (SUCCEEDED(pJob->Sign())) { pJob->SaveWithRetry(pJob->GetFileName(), FALSE, SAVEP_VARIABLE_LENGTH_DATA | SAVEP_PRESERVE_NET_SCHEDULE); } } pJob->Release(); } if (!FindNextFile(hFileEnum, &fd)) { dwRet = GetLastError(); if (dwRet != ERROR_NO_MORE_FILES) hr = _HRESULT_FROM_WIN32(dwRet); break; } } } } if (hFileEnum != INVALID_HANDLE_VALUE) FindClose(hFileEnum); } } return hr; } //+--------------------------------------------------------------------------- // // Function: ConvertIdentityData // // Synopsis: Create new job identity entries in SAI representing the existing // jobs on the system. It does this by retrieving information stored // pre-sysprep associating each existing job with its credential, then // creating and storing a new identity to reference that credential. // // Arguments: hCSP - handle to crypto service provider // pcbSAI - pointer to dword containing count of bytes in SAI // ppbSAI - pointer to pointer to SAI data // pcbSAC - pointer to dword containing count of bytes in SAC // ppbSAC - pointer to pointer to SAC data // // Returns: HRESULT // //---------------------------------------------------------------------------- HRESULT ConvertIdentityData(HCRYPTPROV hCSP, DWORD* pcbSAI, BYTE** ppbSAI, DWORD* pcbSAC, BYTE** ppbSAC) { // // Get the job identity data from LSA // BYTE* pbIdentityData = NULL; DWORD cbIdentityData = 0; HRESULT hr = GetSysprepIdentityInfo(&cbIdentityData, &pbIdentityData); if (FAILED(hr)) { goto ErrorExit; } // // Insert updated hashes into SAI for pre-sysprep jobs // if (pbIdentityData) { // // Get the tasks folder and prepare buffer for use in producing job path // WCHAR wszJobPath[MAX_PATH + 1]; hr = StringCchCopy(wszJobPath, MAX_PATH + 1, g_TasksFolderInfo.ptszPath); if (SUCCEEDED(hr)) { DWORD iConcatenation = lstrlenW(g_TasksFolderInfo.ptszPath); wszJobPath[iConcatenation++] = L'\\'; // // Process each stored task // BYTE* pbCurrentData = pbIdentityData; BYTE* pbLastByte = (BYTE*)(pbIdentityData + cbIdentityData - 1); WCHAR* pwszFileName = NULL; BOOL bIsPasswordNull; DWORD dwCredentialIndex; BYTE rgbIdentity[HASH_DATA_SIZE]; BYTE* pbIdentitySet = NULL; while (pbCurrentData <= pbLastByte) { pwszFileName = (WCHAR*) pbCurrentData; pbCurrentData += ((lstrlenW(pwszFileName) + 1) * sizeof(WCHAR)); CopyMemory(&bIsPasswordNull, pbCurrentData, sizeof(BOOL)); pbCurrentData += sizeof(BOOL); CopyMemory(&dwCredentialIndex, pbCurrentData, sizeof(DWORD)); if ((pbCurrentData + sizeof(DWORD)) > (pbLastByte + 1)) { // // the first DWORD after pbCurrentData is beyond the first BYTE after pbLastByte; // this means that the DWORD pointed to by pbCurrentData is partially or wholly // beyond the end of the data -- something is corrupt // hr = E_FAIL; goto ErrorExit; } // // Truncate the existing name after the folder path, // then concatenate the new filename onto it. // wszJobPath[iConcatenation] = L'\0'; if (SUCCEEDED(StringCchCat(wszJobPath, MAX_PATH + 1, pwszFileName))) { // // Hash the job into a unique identity. // if (SUCCEEDED(HashJobIdentity(hCSP, (LPCWSTR)&wszJobPath, rgbIdentity))) { // // Store a NULL password by flipping the last bit of the hash data. // if (bIsPasswordNull) { LAST_HASH_BYTE(rgbIdentity) ^= 1; } // // Insert the job identity into the SAI identity set associated // with this credential. // hr = SAIIndexIdentity(*pcbSAI, *ppbSAI, dwCredentialIndex, 0, NULL, NULL, &pbIdentitySet); if (hr == S_FALSE) { // // The SAC & SAI databases are out of sync. Should *never* occur. // ASSERT_SECURITY_DBASE_CORRUPT(); hr = SCHED_E_ACCOUNT_DBASE_CORRUPT; goto ErrorExit; } else if (SUCCEEDED(hr)) { hr = SAIInsertIdentity(rgbIdentity, pbIdentitySet, pcbSAI, ppbSAI); } else { hr = S_OK; // OK, we failed this one, go on to the next } } } pbCurrentData += sizeof(DWORD); } } } ErrorExit: if (pbIdentityData) LocalFree(pbIdentityData); return(hr); } //+--------------------------------------------------------------------------- // // Function: ConvertCredentialData // // Synopsis: Converts credentials stored prior to sysprep so they may be decrypted after sysprep. // It does this by retrieving the pre-sysprep key, decrypting the credentials with it, and then // encrypting them using the new post-sysprep key. // // Arguments: pRC2KeyPreSysprep - pointer to presysprep key // pRC2KeyPostSysprep - pointer to postsysprep key // pcbSAI - pointer to dword containing count of bytes in SAI // ppbSAI - pointer to pointer to SAI data // pcbSAC - pointer to dword containing count of bytes in SAC // ppbSAC - pointer to pointer to SAC data // // Returns: HRESULT // //---------------------------------------------------------------------------- HRESULT ConvertCredentialData(RC2_KEY_INFO* pRC2KeyPreSysprep, RC2_KEY_INFO* pRC2KeyPostSysprep, DWORD* pcbSAI, BYTE** ppbSAI, DWORD* pcbSAC, BYTE** ppbSAC) { HRESULT hr = S_OK; HRESULT hRes2 = S_OK; // for hresults that we don't want to affect our return code BYTE* pbEncryptedData = NULL; DWORD cbEncryptedData = 0; BYTE* pbSACEnd = *ppbSAC + *pcbSAC; BYTE* pbCredential = *ppbSAC + USN_SIZE; // Advance past USN. DWORD cbCredential = 0; // // Read credential count // DWORD dwCredentialCount = 0; CopyMemory(&dwCredentialCount, pbCredential, sizeof(dwCredentialCount)); pbCredential += sizeof(dwCredentialCount); // // Loop through all credentials, decrypting, encrypting, and updating each one // JOB_CREDENTIALS jc; RC2_KEY_INFO RC2KeyPreSysprepCopy; RC2_KEY_INFO RC2KeyPostSysprepCopy; for (DWORD dwCredentialIndex = 0; (dwCredentialIndex < dwCredentialCount) && ((DWORD)(pbCredential - *ppbSAC) < *pcbSAC); dwCredentialIndex++ ) { // // Ensure sufficient space remains in the buffer. // if ((pbCredential + sizeof(cbCredential)) > pbSACEnd) { ASSERT_SECURITY_DBASE_CORRUPT(); hr = SCHED_E_ACCOUNT_DBASE_CORRUPT; goto ErrorExit; } CopyMemory(&cbCredential, pbCredential, sizeof(cbCredential)); pbCredential += sizeof(cbCredential); // // Check remaining buffer size again // if ((pbCredential + HASH_DATA_SIZE) > pbSACEnd) { ASSERT_SECURITY_DBASE_CORRUPT(); hr = SCHED_E_ACCOUNT_DBASE_CORRUPT; goto ErrorExit; } // // Check remaining buffer size yet again // if ((pbCredential + cbCredential) > pbSACEnd) { ASSERT_SECURITY_DBASE_CORRUPT(); hr = SCHED_E_ACCOUNT_DBASE_CORRUPT; goto ErrorExit; } // // The start of the credential refers to the credential identity. // Skip over this to refer to the encrypted bits. Copy the pbCredential // and cbCredential for this dwCredentialIndex so that it can be // manipulated without altering the original buffer. // cbEncryptedData = cbCredential - HASH_DATA_SIZE; pbEncryptedData = new BYTE[cbEncryptedData]; CopyMemory(pbEncryptedData, pbCredential + HASH_DATA_SIZE, cbEncryptedData); // // The decryption process (CBC call) clobbers the key, so make a fresh // copy from the source each time through the loop // CopyMemory(&RC2KeyPreSysprepCopy, pRC2KeyPreSysprep, sizeof(RC2_KEY_INFO)); // // Decrypt credential using the pre-sysprep key // // *** Important *** // // The encrypted credentials passed are decrypted *in-place*. // Therefore, buffer content has been compromised; // plus, the decrypted data must be zeroed immediately // following decryption (even in a failure case). // hRes2 = DecryptCredentials(RC2KeyPreSysprepCopy, cbEncryptedData, pbEncryptedData, &jc); // // Don't leave the plain-text password on the heap. // SecureZeroMemory(pbEncryptedData, cbEncryptedData); if (SUCCEEDED(hRes2)) { // // The encryption process (CBC call) clobbers the key, so make a fresh // copy from the source each time through the loop // CopyMemory(&RC2KeyPostSysprepCopy, pRC2KeyPostSysprep, sizeof(RC2_KEY_INFO)); // // Encrypt credential using the post-sysprep key // hRes2 = EncryptCredentials(RC2KeyPostSysprepCopy, jc.wszAccount, jc.wszDomain, jc.wszPassword, NULL, &cbEncryptedData, &pbEncryptedData); // // Don't leave the plain-text password on the stack // ZERO_PASSWORD(jc.wszPassword); jc.ccPassword = 0; if (SUCCEEDED(hRes2)) { // // Update the old encrypted credential with the newly encrypted credential // hr = SACUpdateCredential(cbEncryptedData, pbEncryptedData, cbCredential, pbCredential, pcbSAC, ppbSAC); if (FAILED(hr)) { goto ErrorExit; // a failure to update leaves the SAC data // in an unknown state, so we need to bail } } } // // Clean up // delete pbEncryptedData; pbEncryptedData = NULL; // // Advance to next credential. // pbCredential += (HASH_DATA_SIZE + cbEncryptedData); } // // Still more integrity checking. // Did we reach the end of the buffer exactly when we thought we should? // If not, something is wrong somewhere. // if ((dwCredentialIndex == dwCredentialCount) && ((DWORD)(pbCredential - *ppbSAC) != *pcbSAC) || (dwCredentialIndex != dwCredentialCount) && ((DWORD)(pbCredential - *ppbSAC) > *pcbSAC)) { // // The database appears to be truncated. // ASSERT_SECURITY_DBASE_CORRUPT(); hr = SCHED_E_ACCOUNT_DBASE_CORRUPT; goto ErrorExit; } ErrorExit: if (pbEncryptedData) LocalFree(pbEncryptedData); return(hr); } //+--------------------------------------------------------------------------- // // Function: ConvertNetScheduleCredentialData // // Synopsis: Converts Net Schedule credential stored prior to sysprep so it may be decrypted after sysprep. // It does this by using the pre-sysprep key to decrypt the credential with it, and then // encrypting it using the new post-sysprep key. // // Arguments: pRC2KeyPreSysprep - pointer to presysprep key // pRC2KeyPostSysprep - pointer to postsysprep key // // Returns: HRESULT // //---------------------------------------------------------------------------- HRESULT ConvertNetScheduleCredentialData(RC2_KEY_INFO* pRC2KeyPreSysprep, RC2_KEY_INFO* pRC2KeyPostSysprep) { BYTE* pbEncryptedData = NULL; DWORD cbEncryptedData = 0; // // Read the Net Schedule account from LSA // HRESULT hr = ReadLsaData(sizeof(WSZ_SANSC), WSZ_SANSC, &cbEncryptedData, &pbEncryptedData); if (FAILED(hr)) { goto ErrorExit; } if (hr == S_FALSE || cbEncryptedData <= sizeof(DWORD)) { // // The information was specified previously but has been reset since. // NOTE: This will be the case if the value has been reset back to LocalSystem, // as it merely stores a dword = 0x00000000 in that case // // // Do nothing -- there is no data to convert // } else { // // The decryption process (CBC call) clobbers the key, so make a copy for use // RC2_KEY_INFO RC2KeyPreSysprepCopy; CopyMemory(&RC2KeyPreSysprepCopy, pRC2KeyPreSysprep, sizeof(RC2_KEY_INFO)); JOB_CREDENTIALS jc; hr = DecryptCredentials(RC2KeyPreSysprepCopy, cbEncryptedData, pbEncryptedData, &jc); SecureZeroMemory(&RC2KeyPreSysprepCopy, sizeof(RC2_KEY_INFO)); if (FAILED(hr)) { goto ErrorExit; } // // Don't leave the plain-text password on the heap. // SecureZeroMemory(pbEncryptedData, cbEncryptedData); // // The encryption process (CBC call) clobbers the key, so make a copy for use // RC2_KEY_INFO RC2KeyPostSysprepCopy; CopyMemory(&RC2KeyPostSysprepCopy, pRC2KeyPostSysprep, sizeof(RC2_KEY_INFO)); // // Encrypt credential using the post-sysprep key // hr = EncryptCredentials(RC2KeyPostSysprepCopy, jc.wszAccount, jc.wszDomain, jc.wszPassword, NULL, &cbEncryptedData, &pbEncryptedData); SecureZeroMemory(&RC2KeyPostSysprepCopy, sizeof(RC2_KEY_INFO)); if (FAILED(hr)) { goto ErrorExit; } // // Don't leave the plain-text password on the stack // ZERO_PASSWORD(jc.wszPassword); hr = WriteLsaData(sizeof(WSZ_SANSC), WSZ_SANSC, cbEncryptedData, pbEncryptedData); } ErrorExit: if (pbEncryptedData != NULL) LocalFree(pbEncryptedData); return(hr); }