From ade8e3f8cbfd1143fcd2e4bfbd6c387b8b64f732 Mon Sep 17 00:00:00 2001 From: Mounir IDRASSI Date: Tue, 3 Dec 2019 22:23:09 +0100 Subject: Windows: Fix possible local privilege escalation vulnerability during execution of VeraCrypt Expander (CVE-2019-19501) --- src/Common/Dlgcode.c | 251 +++++++++++++++++++++++++++++++++++++++---- src/Common/Dlgcode.h | 1 + src/ExpandVolume/WinMain.cpp | 2 +- 3 files changed, 232 insertions(+), 22 deletions(-) (limited to 'src') diff --git a/src/Common/Dlgcode.c b/src/Common/Dlgcode.c index c84e94ac..a1e64456 100644 --- a/src/Common/Dlgcode.c +++ b/src/Common/Dlgcode.c @@ -300,6 +300,7 @@ HMODULE hntmartadll = NULL; HMODULE hwinscarddll = NULL; HMODULE hmsvcrtdll = NULL; HMODULE hWinTrustLib = NULL; +HMODULE hAdvapi32Dll = NULL; #define FREE_DLL(h) if (h) { FreeLibrary (h); h = NULL;} @@ -336,6 +337,18 @@ typedef HRESULT (STDAPICALLTYPE *SHStrDupWPtr)(LPCWSTR psz, LPWSTR *ppwsz); // ChangeWindowMessageFilter typedef BOOL (WINAPI *ChangeWindowMessageFilterPtr) (UINT, DWORD); +typedef BOOL (WINAPI *CreateProcessWithTokenWFn)( + __in HANDLE hToken, + __in DWORD dwLogonFlags, + __in_opt LPCWSTR lpApplicationName, + __inout_opt LPWSTR lpCommandLine, + __in DWORD dwCreationFlags, + __in_opt LPVOID lpEnvironment, + __in_opt LPCWSTR lpCurrentDirectory, + __in LPSTARTUPINFOW lpStartupInfo, + __out LPPROCESS_INFORMATION lpProcessInformation + ); + SetDllDirectoryPtr SetDllDirectoryFn = NULL; SetSearchPathModePtr SetSearchPathModeFn = NULL; SetDefaultDllDirectoriesPtr SetDefaultDllDirectoriesFn = NULL; @@ -350,6 +363,7 @@ SetupOpenInfFileWPtr SetupOpenInfFileWFn = NULL; SHDeleteKeyWPtr SHDeleteKeyWFn = NULL; SHStrDupWPtr SHStrDupWFn = NULL; ChangeWindowMessageFilterPtr ChangeWindowMessageFilterFn = NULL; +CreateProcessWithTokenWFn CreateProcessWithTokenWPtr = NULL; typedef LONG (WINAPI *WINVERIFYTRUST)(HWND hwnd, GUID *pgActionID, LPVOID pWVTData); typedef CRYPT_PROVIDER_DATA* (WINAPI *WTHELPERPROVDATAFROMSTATEDATA)(HANDLE hStateData); @@ -758,6 +772,7 @@ void AbortProcessDirect (wchar_t *abortMsg) FREE_DLL (hntmartadll); FREE_DLL (hwinscarddll); FREE_DLL (hmsvcrtdll); + FREE_DLL (hAdvapi32Dll); exit (1); } @@ -808,6 +823,7 @@ void AbortProcessSilent (void) FREE_DLL (hntmartadll); FREE_DLL (hwinscarddll); FREE_DLL (hmsvcrtdll); + FREE_DLL (hAdvapi32Dll); // Note that this function also causes localcleanup() to be called (see atexit()) exit (1); @@ -3014,6 +3030,7 @@ void InitApp (HINSTANCE hInstance, wchar_t *lpszCommandLine) AbortProcess ("INIT_DLL"); LoadSystemDll (L"Riched20.dll", &hRichEditDll, FALSE, SRC_POS); + LoadSystemDll (L"Advapi32.dll", &hAdvapi32Dll, FALSE, SRC_POS); #if !defined(SETUP) if (!VerifyModuleSignature (modPath)) @@ -3047,6 +3064,9 @@ void InitApp (HINSTANCE hInstance, wchar_t *lpszCommandLine) #endif } + // Get CreateProcessWithTokenW function pointer + CreateProcessWithTokenWPtr = (CreateProcessWithTokenWFn) GetProcAddress(hAdvapi32Dll, "CreateProcessWithTokenW"); + /* Save the instance handle for later */ hInst = hInstance; @@ -3271,6 +3291,7 @@ void InitApp (HINSTANCE hInstance, wchar_t *lpszCommandLine) FREE_DLL (hntmartadll); FREE_DLL (hwinscarddll); FREE_DLL (hmsvcrtdll); + FREE_DLL (hAdvapi32Dll); exit (1); } #endif @@ -3316,6 +3337,7 @@ void FinalizeApp (void) FREE_DLL (hntmartadll); FREE_DLL (hwinscarddll); FREE_DLL (hmsvcrtdll); + FREE_DLL (hAdvapi32Dll); } void InitHelpFileName (void) @@ -5101,7 +5123,7 @@ void OpenVolumeExplorerWindow (int driveNo) // Force explorer to discover the drive SHGetFileInfo (dosName, 0, &fInfo, sizeof (fInfo), 0); - ShellExecute (NULL, L"open", dosName, NULL, NULL, SW_SHOWNORMAL); + SafeOpenURL (dosName); } static BOOL explorerCloseSent; @@ -10499,25 +10521,37 @@ void ConfigReadCompareString (char *configKey, char *defaultValue, char *str, in void OpenPageHelp (HWND hwndDlg, int nPage) { - int r = (int)ShellExecuteW (NULL, L"open", szHelpFile, NULL, NULL, SW_SHOWNORMAL); - - if (r == ERROR_FILE_NOT_FOUND) + if (IsAdmin ()) { - // Try the secondary help file - r = (int)ShellExecuteW (NULL, L"open", szHelpFile2, NULL, NULL, SW_SHOWNORMAL); + if (FileExists (szHelpFile)) + SafeOpenURL (szHelpFile); + else if (FileExists (szHelpFile2)) + SafeOpenURL (szHelpFile2); + else + Applink ("help"); + } + else + { + int r = (int)ShellExecuteW (NULL, L"open", szHelpFile, NULL, NULL, SW_SHOWNORMAL); if (r == ERROR_FILE_NOT_FOUND) { - // Open local HTML help. It will fallback to online help if not found. - Applink ("help"); - return; + // Try the secondary help file + r = (int)ShellExecuteW (NULL, L"open", szHelpFile2, NULL, NULL, SW_SHOWNORMAL); + + if (r == ERROR_FILE_NOT_FOUND) + { + // Open local HTML help. It will fallback to online help if not found. + Applink ("help"); + return; + } } - } - if (r == SE_ERR_NOASSOC) - { - if (AskYesNo ("HELP_READER_ERROR", MainDlg) == IDYES) - OpenOnlineHelp (); + if (r == SE_ERR_NOASSOC) + { + if (AskYesNo ("HELP_READER_ERROR", MainDlg) == IDYES) + OpenOnlineHelp (); + } } } @@ -11021,14 +11055,30 @@ void Applink (const char *dest) CorrectURL (url); } - r = (int) ShellExecuteW (NULL, L"open", url, NULL, NULL, SW_SHOWNORMAL); - - if (((r == ERROR_FILE_NOT_FOUND) || (r == ERROR_PATH_NOT_FOUND)) && buildUrl) + if (IsAdmin ()) + { + if (buildUrl && !FileExists (url)) + { + // fallbacl to online resources + StringCbPrintfW (url, sizeof (url), L"https://www.veracrypt.fr/en/%s", page); + SafeOpenURL (url); + } + else + { + SafeOpenURL (url); + } + } + else { - // fallbacl to online resources - StringCbPrintfW (url, sizeof (url), L"https://www.veracrypt.fr/en/%s", page); - ShellExecuteW (NULL, L"open", url, NULL, NULL, SW_SHOWNORMAL); - } + r = (int) ShellExecuteW (NULL, L"open", url, NULL, NULL, SW_SHOWNORMAL); + + if (((r == ERROR_FILE_NOT_FOUND) || (r == ERROR_PATH_NOT_FOUND)) && buildUrl) + { + // fallbacl to online resources + StringCbPrintfW (url, sizeof (url), L"https://www.veracrypt.fr/en/%s", page); + ShellExecuteW (NULL, L"open", url, NULL, NULL, SW_SHOWNORMAL); + } + } Sleep (200); NormalCursor (); @@ -14036,6 +14086,165 @@ Cleanup: return bSuccess; } +// Based on sample code from: +// https://blogs.msdn.microsoft.com/aaron_margosis/2009/06/06/faq-how-do-i-start-a-program-as-the-desktop-user-from-an-elevated-app/ +// start a program non-elevated as the desktop user from an elevated app + +static bool RunAsDesktopUser( + __in const wchar_t * szApp, + __in wchar_t * szCmdLine) +{ + HANDLE hThreadToken = NULL, hShellProcess = NULL, hShellProcessToken = NULL, hPrimaryToken = NULL; + HWND hwnd = NULL; + DWORD dwPID = 0; + BOOL ret; + DWORD dwLastErr; + STARTUPINFOW si; + PROCESS_INFORMATION pi; + bool retval = false; + SecureZeroMemory(&si, sizeof(si)); + SecureZeroMemory(&pi, sizeof(pi)); + si.cb = sizeof(si); + + // locate CreateProcessWithTokenW in Advapi32.dll + if (!CreateProcessWithTokenWPtr) + { + return false; + } + + if (!ImpersonateSelf (SecurityImpersonation)) + { + return false; + } + + if (!OpenThreadToken (GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, FALSE, &hThreadToken)) + { + return false; + } + else + { + TOKEN_PRIVILEGES tkp; + tkp.PrivilegeCount = 1; + LookupPrivilegeValueW(NULL, SE_INCREASE_QUOTA_NAME, &tkp.Privileges[0].Luid); + tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + AdjustTokenPrivileges(hThreadToken, FALSE, &tkp, 0, NULL, NULL); + dwLastErr = GetLastError(); + if (ERROR_SUCCESS != dwLastErr) + { + goto cleanup; + } + } + + // From this point down, we have handles to close, so make sure to clean up. + + // Get an HWND representing the desktop shell. + // CAVEATS: This will fail if the shell is not running (crashed or terminated), or the default shell has been + // replaced with a custom shell. This also won't return what you probably want if Explorer has been terminated and + // restarted elevated. + hwnd = GetShellWindow(); + if (NULL == hwnd) + { + dwLastErr = GetLastError(); + goto cleanup; + } + + // Get the PID of the desktop shell process. + GetWindowThreadProcessId(hwnd, &dwPID); + if (0 == dwPID) + { + dwLastErr = GetLastError(); + goto cleanup; + } + + // Open the desktop shell process in order to query it (get the token) + hShellProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwPID); + if (!hShellProcess) + { + dwLastErr = GetLastError(); + goto cleanup; + } + + // Get the process token of the desktop shell. + ret = OpenProcessToken(hShellProcess, TOKEN_DUPLICATE, &hShellProcessToken); + if (!ret) + { + dwLastErr = GetLastError(); + goto cleanup; + } + + // Duplicate the shell's process token to get a primary token. + // Based on experimentation, this is the minimal set of rights required for CreateProcessWithTokenW (contrary to current documentation). + const DWORD dwTokenRights = TOKEN_QUERY | TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID; + ret = DuplicateTokenEx(hShellProcessToken, dwTokenRights, NULL, SecurityImpersonation, TokenPrimary, &hPrimaryToken); + if (!ret) + { + dwLastErr = GetLastError(); + goto cleanup; + } + + // Start the target process with the new token. + ret = CreateProcessWithTokenWPtr( + hPrimaryToken, + 0, + szApp, + szCmdLine, + 0, + NULL, + NULL, + &si, + &pi); + if (!ret) + { + dwLastErr = GetLastError(); + goto cleanup; + } + + // Make sure to close HANDLEs return in the PROCESS_INFORMATION. + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + + retval = true; + +cleanup: + // Clean up resources + if (hShellProcessToken) CloseHandle(hShellProcessToken); + if (hPrimaryToken) CloseHandle(hPrimaryToken); + if (hShellProcess) CloseHandle(hShellProcess); + if (hThreadToken) CloseHandle(hThreadToken); + RevertToSelf (); + if (!retval) + SetLastError (dwLastErr); + return retval; +} + +// This function always loads a URL in a non-privileged mode +// If current process has admin privileges, we execute the command "rundll32 url.dll,FileProtocolHandler URL" as non-elevated +void SafeOpenURL (LPCWSTR szUrl) +{ + if (IsAdmin ()) + { + WCHAR szRunDllPath[TC_MAX_PATH]; + WCHAR szUrlDllPath[TC_MAX_PATH]; + WCHAR szSystemPath[TC_MAX_PATH]; + LPWSTR szCommandLine = new WCHAR[1024]; + + if (!GetSystemDirectory(szSystemPath, MAX_PATH)) + StringCbCopyW(szSystemPath, sizeof(szSystemPath), L"C:\\Windows\\System32"); + + StringCbPrintfW(szRunDllPath, sizeof(szRunDllPath), L"%s\\%s", szSystemPath, L"rundll32.exe"); + StringCbPrintfW(szUrlDllPath, sizeof(szUrlDllPath), L"%s\\%s", szSystemPath, L"url.dll"); + StringCchPrintfW(szCommandLine, 1024, L"%s,FileProtocolHandler %s", szUrlDllPath, szUrl); + + RunAsDesktopUser (szRunDllPath, szCommandLine); + + delete [] szCommandLine; + } + else + { + ShellExecuteW (NULL, L"open", szUrl, NULL, NULL, SW_SHOWNORMAL); + } +} + #if !defined(SETUP) && defined(_WIN64) #define RtlGenRandom SystemFunction036 diff --git a/src/Common/Dlgcode.h b/src/Common/Dlgcode.h index ff45fef6..5da717fe 100644 --- a/src/Common/Dlgcode.h +++ b/src/Common/Dlgcode.h @@ -545,6 +545,7 @@ void GetInstallationPath (HWND hwndDlg, wchar_t* szInstallPath, DWORD cchSize, B BOOL GetSetupconfigLocation (wchar_t* path, DWORD cchSize); BOOL BufferHasPattern (const unsigned char* buffer, size_t bufferLen, const void* pattern, size_t patternLen); BOOL EnableProcessProtection(); +void SafeOpenURL (LPCWSTR szUrl); #ifdef _WIN64 void GetAppRandomSeed (unsigned char* pbRandSeed, size_t cbRandSeed); #endif diff --git a/src/ExpandVolume/WinMain.cpp b/src/ExpandVolume/WinMain.cpp index 2710cc4e..ffeabe01 100644 --- a/src/ExpandVolume/WinMain.cpp +++ b/src/ExpandVolume/WinMain.cpp @@ -957,7 +957,7 @@ BOOL CALLBACK MainDialogProc (HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lPa if (lw == IDM_HOMEPAGE ) { ArrowWaitCursor (); - ShellExecute (NULL, L"open", L"https://www.veracrypt.fr", NULL, NULL, SW_SHOWNORMAL); + SafeOpenURL (L"https://www.veracrypt.fr"); Sleep (200); NormalCursor (); -- cgit v1.2.3