//================================================================= // // ImpLogonUser.CPP -- Class to perform impersonation of logged on user. // // Copyright (c) 1997-2002 Microsoft Corporation, All Rights Reserved // // Revisions: 09/09/97 a-sanjes Created // //================================================================= //#define _WIN32_DCOM // For CoImpersonateUser and CoRevertToSelf //#include #include "precomp.h" #ifdef NTONLY #include #include #include #include #include "Sid.h" #include "AccessEntry.h" // CAccessEntry class #include "AccessEntryList.h" #include "DACL.h" // CDACL class #include "SACL.h" #include "securitydescriptor.h" #include "CToken.h" #include "SecureKernelObj.h" #include "implogonuser.h" #include "cluidhelper.h" static DWORD s_dwProcessID = 0; static CCritSec g_csImpersonate; ////////////////////////////////////////////////////////////////////////////// // // implogonuser.cpp - Class implementation of CImpersonateLoggedOnUser. // // This class is intended to provide a way for a process to identify the shell // process on a Windows NT system, and using the access token of that process // to attempt to impersonate the user logged onto the Interactive Desktop of // a workstation. // ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// // // FUNCTION : CImpersonateLoggedOnUser::CImpersonateLoggedOnUser // // DESCRIPTION : Constructor // // INPUTS : NONE // // OUTPUTS : none // // RETURNS : nothing // // COMMENTS : Constructs empty instance to prepare for impersonation of user. // ////////////////////////////////////////////////////////////////////////////// CImpersonateLoggedOnUser::CImpersonateLoggedOnUser() : m_hShellProcess(NULL), m_hUserToken(NULL), m_fImpersonatingUser(FALSE) , m_hThreadToken ( INVALID_HANDLE_VALUE ) { } ////////////////////////////////////////////////////////////////////////////// // // FUNCTION : CImpersonateLoggedOnUser::~CImpersonateLoggedOnUser // // DESCRIPTION : Destructor // // INPUTS : none // // OUTPUTS : none // // RETURNS : nothing // // COMMENTS : Class destructor // ////////////////////////////////////////////////////////////////////////////// CImpersonateLoggedOnUser::~CImpersonateLoggedOnUser( void ) { // Stop any current impersonation End(); } ////////////////////////////////////////////////////////////////////////////// // // FUNCTION : CImpersonateLoggedOnUser::Begin // // DESCRIPTION : Attempts to begin impersonation of user. // // INPUTS : none // // OUTPUTS : none // // RETURNS : BOOL TRUE/FALSE - Success/Failure // // COMMENTS : Uses helper functions to try to impersonate the // currently logged on user. The process must have // the proper level of access to perform the operation. // ////////////////////////////////////////////////////////////////////////////// BOOL CImpersonateLoggedOnUser::Begin( void ) { BOOL fReturn = FALSE; TCHAR szShellProcessName[256]; LogMessage(_T("CImpersonateLoggedOnUser::Begin")); // Only continue if we are not already impersonating a user if (!m_fImpersonatingUser ) { //Store the current thread token, assuming that the thread is impersonating somebody (DCOM client) if ( !OpenThreadToken ( GetCurrentThread(), TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE, TRUE, &m_hThreadToken ) ) { m_hThreadToken = INVALID_HANDLE_VALUE; } // We will need a copy of PSAPI.DLL and a bunch of entry point // addresses before we can continue, so let our base class take // care of this. // We need a handle for the Shell Process in order to // successfully impersonate the user. if ( NULL == m_hShellProcess ) { if ( LoadShellName( szShellProcessName, sizeof(szShellProcessName) ) ) FindShellProcess( szShellProcessName); else LogErrorMessage(_T("LoadShellName failed")); } if ( NULL != m_hShellProcess ) { fReturn = ImpersonateUser(); } else { // We didn't find the Shell Process Name that we extracted from the // registry. We saw this happening on Alphas that seem to get "fx32strt.exe" // dumped in the shell. In these cases, it seems to cause explorer to run. // So with that in mind, if we drop down in this branch of code, we're going // to retry the locate shell process operation using Explorer.Exe. if ( IsErrorLoggingEnabled() ) { CHString sTemp; sTemp.Format(_T("Shell Name %s in Registry not found in process list."), szShellProcessName); LogErrorMessage(sTemp); } FindShellProcess( IDS_WINNT_SHELLNAME_EXPLORER ) ; // m_hShellProcess will be non-NULL if and only if we got one. if ( NULL != m_hShellProcess ) { fReturn = ImpersonateUser(); } else { LogErrorMessage(_T("Unable to locate Shell Process, Impersonation failed.")); SetLastError(0); } } } else { LogMessage(_T("CImpersonateLoggedOnUser::Begin - Already impersonated")); fReturn = TRUE; // Already initialized } // We don't yet have a way to know whether explorer is really alive // because we're impersonating someone and I can't find a way to // revert back to LocalSystem. So, for now just set it to 0. SetLastError(0); return fReturn; } ////////////////////////////////////////////////////////////////////////////// // // FUNCTION : CImpersonateLoggedOnUser::End // // DESCRIPTION : Ends impersonation of logged on user // // INPUTS : none // // OUTPUTS : none // // RETURNS : BOOL TRUE/FALSE - Success/Failure // // COMMENTS : Ends impersonation of logged on user. Clears all elements // of class as a byproduct. // ////////////////////////////////////////////////////////////////////////////// BOOL CImpersonateLoggedOnUser::End( void ) { BOOL fReturn = FALSE; // Only initiate a Revert if we are impersonating the user. if ( m_fImpersonatingUser ) { LogMessage(_T("CImpersonateLoggedOnUser::End")); fReturn = Revert(); } else { fReturn = TRUE; } // Clear the handles out if ( NULL != m_hUserToken ) { CloseHandle( m_hUserToken ); m_hUserToken = NULL; } if ( NULL != m_hShellProcess ) { CloseHandle( m_hShellProcess ); m_hShellProcess = NULL; } if ( m_hThreadToken != INVALID_HANDLE_VALUE ) { CloseHandle( m_hThreadToken ); m_hThreadToken = INVALID_HANDLE_VALUE ; } return fReturn; } ////////////////////////////////////////////////////////////////////////////// // // FUNCTION : CImpersonateLoggedOnUser::LoadShellName // // DESCRIPTION : Loads Windows NT Shell name from registry // // INPUTS : DWORD cbShellNameBuffer - Shell Name Buffer Size (in bytes) // // OUTPUTS : LPTSTR pszShellName - Buffer to contain shell name. // // RETURNS : BOOL TRUE/FALSE - Success/Failure // // COMMENTS : Jumps into Windows Registry and attempts to determine the // NT Shell Name. // ////////////////////////////////////////////////////////////////////////////// BOOL CImpersonateLoggedOnUser::LoadShellName( LPTSTR pszShellName, DWORD cbShellNameBuffer ) { BOOL fReturn = FALSE; LONG lErrReturn = ERROR_SUCCESS; // Only continue if we have a buffer to work with first if ( NULL != pszShellName ) { HKEY hReg = NULL; // Open the key in HKEY_LOCAL_MACHINE, if that succeeds, get the // value associated with "Shell". if ( ( lErrReturn = RegOpenKeyEx(HKEY_LOCAL_MACHINE, WINNT_WINLOGON_KEY, 0, KEY_READ, &hReg ) ) == ERROR_SUCCESS ) { try { DWORD dwType = REG_SZ; if ( ( lErrReturn = RegQueryValueEx( hReg, WINNT_SHELL_VALUE, 0, &dwType, (LPBYTE) pszShellName, &cbShellNameBuffer ) ) == ERROR_SUCCESS ) { fReturn = TRUE; } else { LogErrorMessage(_T("RegQueryValueEx FAILED")); } } catch ( ... ) { RegCloseKey( hReg ); throw ; } RegCloseKey( hReg ); } // RegOpenKeyEx else { LogErrorMessage(_T("RegOpenKeyEx FAILED")); } } // NULL != pszShellName return fReturn; } ////////////////////////////////////////////////////////////////////////////// // // FUNCTION : CImpersonateLoggedOnUser::FindShellProcess // // DESCRIPTION : Enumerates the processes to locate the Shell Process. // // INPUTS : LPCTSTR pszShellName - Name of the process to locate. // // OUTPUTS : None. // // RETURNS : BOOL TRUE/FALSE - Success/Failure // // COMMENTS : Enumerates the processes on the local system using PSAPI.DLL // functions, attempting to locate the one that corresponds to // the WINNT Shell passed into this function. // ////////////////////////////////////////////////////////////////////////////// BOOL CImpersonateLoggedOnUser::FindShellProcess( LPCTSTR pszShellName ) { BOOL fReturn = FALSE; HANDLE hProcess = NULL; HMODULE *phModules = NULL; DWORD dwModuleArraySize = 0; DWORD* pdwProcessIds = NULL; if ( NULL != pszShellName ) { CPSAPI *t_psapi = (CPSAPI*) CResourceManager::sm_TheResourceManager.GetResource ( guidPSAPI, NULL ) ; if ( t_psapi ) { try { CLuidHelper luid ; LUID processLUID ; // This locks access to the s_dwProcessID value. WATCH THE SCOPING HERE! CLockWrapper t_lockImp(g_csImpersonate); // First check to see if we have a cached value. If so, check to see if it's still valid. if (s_dwProcessID != 0) { if ( ( hProcess = OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, s_dwProcessID ) ) != NULL ) { try { // Now search the process modules for a match to the supplied // shell name. fReturn = FindShellModuleInProcess( pszShellName, hProcess, phModules, dwModuleArraySize, t_psapi ); if ( TRUE == fReturn ) { if ( STATUS_SUCCESS == luid.GetLUIDFromProcess ( hProcess, &processLUID ) ) { if ( ! luid.IsInteractiveSession ( &processLUID ) ) { fReturn = FALSE ; } } else { fReturn = FALSE ; } } } catch ( ... ) { CloseHandle (hProcess); throw ; } // Close the process handle if it's not the shell (in which // case we'll save the value and close it as part of the // Clear() function. if ( !fReturn ) { CloseHandle( hProcess ); hProcess = NULL; // Not valid anymore s_dwProcessID = 0; } else { m_hShellProcess = hProcess; LogMessage(L"Using cached handle for impersonation"); hProcess = NULL; } } // if OpenProcess else { // We didn't open the process, so we need to set the value to zero so that // we will look for a new process below. s_dwProcessID = 0; } } // Did we find a cached value? if (s_dwProcessID == 0) { // Nope. Scan all processes to see if we can find the explorer DWORD dwProcessIdArraySize = 0, dwNumProcesses = 0, cbDataReturned = 0; BOOL fEnumSucceeded = FALSE; // Perform initial allocations of our arrays. Since // pointers and values are 0, this will just fill out // said values. do { ReallocProcessIdArray( pdwProcessIds, dwProcessIdArraySize ); fEnumSucceeded = t_psapi->EnumProcesses( pdwProcessIds, dwProcessIdArraySize, &cbDataReturned ); } while ( (dwProcessIdArraySize == cbDataReturned) && fEnumSucceeded); // Only walk the array if we sucessfully populated it if ( fEnumSucceeded ) { // Count of Bytes returned / sizeof(DWORD) tells us how many // processes are out in the world. dwNumProcesses = cbDataReturned / sizeof(DWORD); DWORD dwId = 0; // Enum processes until we obtain a shell process or run out // of processes to query. while ( dwId < dwNumProcesses && !fReturn ) { if ( ( hProcess = OpenProcess( PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pdwProcessIds[dwId] ) ) != NULL ) { try { // Now search the process modules for a match to the supplied // shell name. fReturn = FindShellModuleInProcess( pszShellName, hProcess, phModules, dwModuleArraySize, t_psapi ); if ( TRUE == fReturn ) { if ( STATUS_SUCCESS == luid.GetLUIDFromProcess ( hProcess, &processLUID ) ) { if ( ! luid.IsInteractiveSession ( &processLUID ) ) { fReturn = FALSE ; } } else { fReturn = FALSE ; } } } catch ( ... ) { CloseHandle (hProcess); throw ; } // Close the process handle if it's not the shell (in which // case we'll save the value and close it as part of the // Clear() function. if ( !fReturn ) { CloseHandle( hProcess ); hProcess = NULL; } else { m_hShellProcess = hProcess; s_dwProcessID = pdwProcessIds[dwId]; hProcess = NULL; } } // if OpenProcess // Increment the Id counter ++dwId; } // While OpenProcesses } // If !fRetryEnumProcesses } } catch ( ... ) { if (phModules) { delete [] phModules; } if (pdwProcessIds) { delete [] pdwProcessIds; } if ( t_psapi ) { CResourceManager::sm_TheResourceManager.ReleaseResource ( guidPSAPI, t_psapi ) ; t_psapi = NULL ; } throw ; } } if (pdwProcessIds) { delete [] pdwProcessIds; } if (phModules) { delete [] phModules; } if ( t_psapi ) { CResourceManager::sm_TheResourceManager.ReleaseResource ( guidPSAPI, t_psapi ) ; // t_psapi = NULL ; } } return fReturn; } ////////////////////////////////////////////////////////////////////////////// // // FUNCTION : CImpersonateLoggedOnUser::FindShellModuleInProcess // // DESCRIPTION : Enumerates the modules in a process to find our // shell. // // INPUTS : LPCTSTR pszShellName - Name of the process to locate. // HANDLE hProcess - Process we are enumerating modules in. // // OUTPUTS : HMODULE*& phModules - Array of module handle pointers. // DWORD& dwModuleArraySize - Size of Module Array (in bytes) // // RETURNS : BOOL TRUE/FALSE - Success/Failure // // COMMENTS : Enumerates the modules specified by a process identifier and // attemptsto locate the one that corresponds to the WINNT Shell // passed into this function. // ////////////////////////////////////////////////////////////////////////////// BOOL CImpersonateLoggedOnUser::FindShellModuleInProcess( LPCTSTR pszShellName, HANDLE hProcess, HMODULE*& phModules, DWORD& dwModuleArraySize, CPSAPI *a_psapi ) { BOOL fReturn = FALSE, fRetryEnumModules = FALSE; DWORD cbDataReturned = 0; TCHAR szModuleName[MAX_PATH]; if (dwModuleArraySize == 0) { ReallocModuleHandleArray( phModules, dwModuleArraySize ); } do { // Get a list of the process HMODULEs and for each HMODULE, get // the base file name. if ( a_psapi->EnumProcessModules( hProcess, phModules, dwModuleArraySize, &cbDataReturned ) ) { // Because m_pfnEnumProcessModules will NOT fail if there are more process // modules than bytes available in the array, if the amount returned is // the same size as the array, realloc the array and retry the Enum. if ( dwModuleArraySize == cbDataReturned ) { fRetryEnumModules = ReallocModuleHandleArray( phModules, dwModuleArraySize ); } else { fRetryEnumModules = FALSE; } // Only walk the array if we don't need to retry the enum if ( !fRetryEnumModules ) { DWORD dwModuleCtr = 0; // Executable name always returned in entry 0 if ( a_psapi->GetModuleBaseName( hProcess, phModules[dwModuleCtr], szModuleName, sizeof(szModuleName) ) ) { fReturn = ( lstrcmpi( pszShellName, szModuleName ) == 0 ); } } // If !fRetryEnumModules } // if EnumProcessModules } while ( fRetryEnumModules ); return fReturn; } DWORD CImpersonateLoggedOnUser::AdjustSecurityDescriptorOfImpersonatedToken( CSid& csidSidOfCurrentProcess ) { DWORD dwRet = E_FAIL; // Get the thread token... CThreadToken ctt; if ( ctt.IsValidToken () ) { // Obtain access to its security descriptor... CSecureKernelObj sko(ctt.GetTokenHandle(), FALSE); // Modify the security descriptor... if(sko.AddDACLEntry( csidSidOfCurrentProcess, ENUM_ACCESS_ALLOWED_ACE_TYPE, TOKEN_ALL_ACCESS, 0, NULL, NULL)) { dwRet = sko.ApplySecurity( DACL_SECURITY_INFORMATION); } } return dwRet ; } ////////////////////////////////////////////////////////////////////////////// // // FUNCTION : CImpersonateLoggedOnUser::ImpersonateUser // // DESCRIPTION : Attempts to impersonate the user. // // INPUTS : None. // // OUTPUTS : None. // // RETURNS : BOOL TRUE/FALSE - Success/Failure // // COMMENTS : Opens the security token of the Shell Process and // uses it to try to impersonate the user. // ////////////////////////////////////////////////////////////////////////////// BOOL CImpersonateLoggedOnUser::ImpersonateUser( void ) { BOOL fRet = FALSE; // Make sure we have a shell process if (m_hShellProcess) { CSid csidCurrentProcess; if(GetCurrentProcessSid(csidCurrentProcess)) { // Get the Process User token if we don't have one (token of the explorer process). //Removed the TOKEN_ALL_ACCESS desired access mask to this call as Winmgmt(Local system) can't open the token of the //shell process (with all access rights)if the logged-in user is an Admin. So open the token with 'desired access' sufficient //enough to use it for impersonation only. if (m_hUserToken || OpenProcessToken(m_hShellProcess, TOKEN_READ | TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_IMPERSONATE, &m_hUserToken)) { // Now we should have what we need. Impersonate the user. HANDLE hCurThread = ::GetCurrentThread () ; CProcessToken cpt ( m_hUserToken ); if ( cpt.IsValidToken () ) { TOKEN_TYPE type; if ( cpt.GetTokenType ( type ) ) { if ( TokenPrimary == type ) { CToken ct; if ( ct.Duplicate ( cpt, FALSE ) ) { if( ::SetThreadToken ( &hCurThread, ct.GetTokenHandle () ) ) { m_fImpersonatingUser = TRUE ; } } } else { if( ::SetThreadToken ( &hCurThread, cpt.GetTokenHandle () ) ) { m_fImpersonatingUser = TRUE ; } } if ( m_fImpersonatingUser ) { if(AdjustSecurityDescriptorOfImpersonatedToken ( csidCurrentProcess ) == ERROR_SUCCESS) { fRet = TRUE; } else { Revert () ; } } } } } } } return (m_fImpersonatingUser = fRet); } ////////////////////////////////////////////////////////////////////////////// // // FUNCTION : CImpersonateLoggedOnUser::Revert // // DESCRIPTION : Attempts to revert to self. // // INPUTS : None. // // OUTPUTS : None. // // RETURNS : BOOL TRUE/FALSE - Success/Failure // // COMMENTS : If we're impersonating a user, we now revert to // ourselves. // ////////////////////////////////////////////////////////////////////////////// BOOL CImpersonateLoggedOnUser::Revert( void ) { HRESULT hRes = WBEM_E_FAILED ; BOOL bRet = FALSE ; // See if we're currently impersonating prior to reverting. if (m_fImpersonatingUser) { // Now get back to to the previous impersonation or impersonate the DCOM client. if ( m_hThreadToken != INVALID_HANDLE_VALUE ) { HANDLE hCurThread = ::GetCurrentThread () ; CThreadToken cpt ( m_hThreadToken ); if ( cpt.IsValidToken () ) { TOKEN_TYPE type; if ( cpt.GetTokenType ( type ) ) { if ( TokenPrimary == type ) { CToken ct; if ( ct.Duplicate ( cpt, FALSE ) ) { bRet = ::SetThreadToken ( &hCurThread, ct.GetTokenHandle () ); } } else { bRet = ::SetThreadToken ( &hCurThread, cpt.GetTokenHandle () ) ; } if (!bRet) { throw CFramework_Exception(L"SetThreadToken failed", GetLastError()); } } } } else { hRes = WbemCoImpersonateClient(); if (FAILED(hRes)) { throw CFramework_Exception(L"WbemCoImpersonateClient failed", hRes); } } if (SUCCEEDED(hRes) || hRes == E_NOTIMPL || bRet ) { m_fImpersonatingUser = FALSE; } } return ( !m_fImpersonatingUser ); } ////////////////////////////////////////////////////////////////////////////// // // FUNCTION : CImpersonateLoggedOnUser::ReallocProcessIdArray // // DESCRIPTION : Helper function to alloc a process id array. // // INPUTS : None. // // OUTPUTS : PDWORD& pdwProcessIds - Process Id Array pointer // DWORD& dwArraySize - Size of array in bytes // // RETURNS : BOOL TRUE/FALSE - Success/Failure // // COMMENTS : Call when we need to realloc our process id array. // This will grow the array by a fixed size, but not // preserve values. // ////////////////////////////////////////////////////////////////////////////// BOOL CImpersonateLoggedOnUser::ReallocProcessIdArray( PDWORD& pdwProcessIds, DWORD& dwArraySize ) { DWORD dwNewArraySize = dwArraySize + ( PROCESSID_ARRAY_BLOCKSIZE * sizeof(DWORD) ); PDWORD pdwNewArray = new DWORD[dwNewArraySize]; // Make sure the allocation succeeded before overwriting any existing values. if ( NULL != pdwNewArray ) { if ( NULL != pdwProcessIds ) { delete [] pdwProcessIds; } pdwProcessIds = pdwNewArray; dwArraySize = dwNewArraySize; } else { throw CHeap_Exception ( CHeap_Exception :: E_ALLOCATION_ERROR ) ; } return ( NULL != pdwNewArray ); } ////////////////////////////////////////////////////////////////////////////// // // FUNCTION : CImpersonateLoggedOnUser::ReallocModuleHandleArray // // DESCRIPTION : Helper function to alloc a module handle array. // // INPUTS : None. // // OUTPUTS : HMODULE*& phModules - Module Handle Array pointer // DWORD& dwArraySize - size of array in bytes // // RETURNS : BOOL TRUE/FALSE - Success/Failure // // COMMENTS : Call when we need to realloc our module handle array. // This will grow the array by a fixed size, but not // preserve values. // ////////////////////////////////////////////////////////////////////////////// BOOL CImpersonateLoggedOnUser::ReallocModuleHandleArray( HMODULE*& phModules, DWORD& dwArraySize ) { DWORD dwNewArraySize = dwArraySize + ( HMODULE_ARRAY_BLOCKSIZE * sizeof(HMODULE) ); HMODULE* phNewArray = new HMODULE[dwNewArraySize]; // Make sure the allocation succeeded before overwriting any existing values. if ( NULL != phNewArray ) { if ( NULL != phModules ) { delete [] phModules; } phModules = phNewArray; dwArraySize = dwNewArraySize; } else { throw CHeap_Exception ( CHeap_Exception :: E_ALLOCATION_ERROR ) ; } return ( NULL != phNewArray ); } bool CImpersonateLoggedOnUser::GetCurrentProcessSid(CSid& sidCurrentProcess) { bool fRet = false; PBYTE pbuff = NULL; // I am going to revert here in order to access the process's // sid. This is not privileged information, so this doesn't // present a security breach. WbemCoRevertToSelf(); try { CProcessToken cpt(NULL, true, TOKEN_QUERY); DWORD dwLen = 0; if(!::GetTokenInformation( cpt.GetTokenHandle(), // the PR0CESS token TokenUser, NULL, 0L, &dwLen) && (::GetLastError() == ERROR_INSUFFICIENT_BUFFER)) { pbuff = new BYTE[dwLen]; if(pbuff) { if(::GetTokenInformation( cpt.GetTokenHandle(), TokenUser, pbuff, dwLen, &dwLen)) { PTOKEN_USER ptu = (PTOKEN_USER)pbuff; CSid sidTemp(ptu->User.Sid); if(sidTemp.IsOK() && sidTemp.IsValid()) { sidCurrentProcess = sidTemp; fRet = true; } } delete pbuff; pbuff = NULL; } } } catch(...) { //on our way out not returning anything to user except failure //can't do anything on this impersonation failure... WbemCoImpersonateClient(); delete pbuff; pbuff = NULL; throw; } HRESULT hr = WbemCoImpersonateClient() ; if (FAILED(hr)) { throw CFramework_Exception(L"WbemCoImpersonateClient failed", hr); } return fRet; } #endif