From: Pali Rohár Date: Mon, 8 May 2023 12:04:40 +0000 (+0200) Subject: windows: Move common non-I/O port code from i386-io-windows.h to win32-helpers.c X-Git-Tag: v3.11.0~21 X-Git-Url: http://mj.ucw.cz/gitweb/?a=commitdiff_plain;h=899a7ac5d34e25a62ac231333250ec887cbc8fae;p=pciutils.git windows: Move common non-I/O port code from i386-io-windows.h to win32-helpers.c --- diff --git a/lib/i386-io-windows.h b/lib/i386-io-windows.h index 69ed821..eff1901 100644 --- a/lib/i386-io-windows.h +++ b/lib/i386-io-windows.h @@ -11,7 +11,6 @@ */ #include -#include #include "win32-helpers.h" #include "i386-io-access.h" @@ -71,42 +70,11 @@ __readeflags(void) /* Read IOPL of the current process, IOPL is stored in eflag bits [13:12]. */ #define read_iopl() ((__readeflags() >> 12) & 0x3) -/* Unfortunately i586-mingw32msvc toolchain does not provide this constant. */ -#ifndef PROCESS_QUERY_LIMITED_INFORMATION -#define PROCESS_QUERY_LIMITED_INFORMATION 0x1000 -#endif - /* Unfortunately some toolchains do not provide this constant. */ #ifndef SE_IMPERSONATE_NAME #define SE_IMPERSONATE_NAME TEXT("SeImpersonatePrivilege") #endif -/* - * These psapi functions are available in kernel32.dll library with K32 prefix - * on Windows 7 and higher systems. On older Windows systems these functions are - * available in psapi.dll libary without K32 prefix. So resolve pointers to - * these functions dynamically at runtime from the available system library. - * Function GetProcessImageFileNameW() is not available on Windows 2000 and - * older systems. - */ -typedef BOOL (WINAPI *EnumProcessesProt)(DWORD *lpidProcess, DWORD cb, DWORD *cbNeeded); -typedef DWORD (WINAPI *GetProcessImageFileNameWProt)(HANDLE hProcess, LPWSTR lpImageFileName, DWORD nSize); -typedef DWORD (WINAPI *GetModuleFileNameExWProt)(HANDLE hProcess, HMODULE hModule, LPWSTR lpImageFileName, DWORD nSize); - -/* - * These aclapi functions are available in advapi.dll library on Windows NT 4.0 - * and higher systems. - */ -typedef DWORD (WINAPI *GetSecurityInfoProt)(HANDLE handle, SE_OBJECT_TYPE ObjectType, SECURITY_INFORMATION SecurityInfo, PSID *ppsidOwner, PSID *ppsidGroup, PACL *ppDacl, PACL *ppSacl, PSECURITY_DESCRIPTOR *ppSecurityDescriptor); -typedef DWORD (WINAPI *SetSecurityInfoProt)(HANDLE handle, SE_OBJECT_TYPE ObjectType, SECURITY_INFORMATION SecurityInfo, PSID psidOwner, PSID psidGroup, PACL pDacl, PACL pSacl); -typedef DWORD (WINAPI *SetEntriesInAclProt)(ULONG cCountOfExplicitEntries, PEXPLICIT_ACCESS pListOfExplicitEntries, PACL OldAcl, PACL *NewAcl); - -/* - * This errhandlingapi function is available in kernel32.dll library on - * Windows 7 and higher systems. - */ -typedef BOOL (WINAPI *SetThreadErrorModeProt)(DWORD dwNewMode, LPDWORD lpOldMode); - /* * Unfortunately NtSetInformationProcess() function, ProcessUserModeIOPL * constant and all other helpers for its usage are not specified in any @@ -132,859 +100,6 @@ typedef BOOL (WINAPI *SetThreadErrorModeProt)(DWORD dwNewMode, LPDWORD lpOldMode typedef NTSTATUS (NTAPI *NtSetInformationProcessProt)(HANDLE ProcessHandle, PROCESSINFOCLASS ProcessInformationClass, PVOID ProcessInformation, ULONG ProcessInformationLength); typedef ULONG (NTAPI *RtlNtStatusToDosErrorProt)(NTSTATUS Status); -/* - * Check if the current thread has particular privilege in current active access - * token. Case when it not possible to determinate it (e.g. current thread does - * not have permission to open its own current active access token) is evaluated - * as thread does not have that privilege. - */ -static BOOL -have_privilege(LUID luid_privilege) -{ - PRIVILEGE_SET priv; - HANDLE token; - BOOL ret; - - /* - * If the current thread does not have active access token then thread - * uses primary process access token for all permission checks. - */ - if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &token) && - (GetLastError() != ERROR_NO_TOKEN || - !OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token))) - return FALSE; - - priv.PrivilegeCount = 1; - priv.Control = PRIVILEGE_SET_ALL_NECESSARY; - priv.Privilege[0].Luid = luid_privilege; - priv.Privilege[0].Attributes = SE_PRIVILEGE_ENABLED; - - if (!PrivilegeCheck(token, &priv, &ret)) - return FALSE; - - return ret; -} - -/* - * Enable or disable particular privilege in specified access token. - * - * Note that it is not possible to disable privilege in access token with - * SE_PRIVILEGE_ENABLED_BY_DEFAULT attribute. This function does not check - * this case and incorrectly returns no error even when disabling failed. - * Rationale for this decision: Simplification of this function as WinAPI - * call AdjustTokenPrivileges() does not signal error in this case too. - */ -static BOOL -set_privilege(HANDLE token, LUID luid_privilege, BOOL enable) -{ - TOKEN_PRIVILEGES token_privileges; - - token_privileges.PrivilegeCount = 1; - token_privileges.Privileges[0].Luid = luid_privilege; - token_privileges.Privileges[0].Attributes = enable ? SE_PRIVILEGE_ENABLED : 0; - - /* - * WinAPI function AdjustTokenPrivileges() success also when not all - * privileges were enabled. It is always required to check for failure - * via GetLastError() call. AdjustTokenPrivileges() always sets error - * also when it success, as opposite to other WinAPI functions. - */ - if (!AdjustTokenPrivileges(token, FALSE, &token_privileges, sizeof(token_privileges), NULL, NULL) || - GetLastError() != ERROR_SUCCESS) - return FALSE; - - return TRUE; -} - -/* - * Change access token for the current thread to new specified access token. - * Previously active access token is stored in old_token variable and can be - * used for reverting to this access token. It is set to NULL if the current - * thread previously used primary process access token. - */ -static BOOL -change_token(HANDLE new_token, HANDLE *old_token) -{ - HANDLE token; - - if (!OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE, TRUE, &token)) - { - if (GetLastError() != ERROR_NO_TOKEN) - return FALSE; - token = NULL; - } - - if (!ImpersonateLoggedOnUser(new_token)) - { - if (token) - CloseHandle(token); - return FALSE; - } - - *old_token = token; - return TRUE; -} - -/* - * Change access token for the current thread to the primary process access - * token. This function fails also when the current thread already uses primary - * process access token. - */ -static BOOL -change_token_to_primary(HANDLE *old_token) -{ - HANDLE token; - - if (!OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE, TRUE, &token)) - return FALSE; - - RevertToSelf(); - - *old_token = token; - return TRUE; -} - -/* - * Revert to the specified access token for the current thread. When access - * token is specified as NULL then revert to the primary process access token. - * Use to revert after change_token() or change_token_to_primary() call. - */ -static VOID -revert_to_token(HANDLE token) -{ - /* - * If SetThreadToken() call fails then there is no option to revert to - * the specified previous thread access token. So in this case revert to - * the primary process access token. - */ - if (!token || !SetThreadToken(NULL, token)) - RevertToSelf(); - if (token) - CloseHandle(token); -} - -/* - * Enable particular privilege for the current thread. And set method how to - * revert this privilege (if to revert whole token or only privilege). - */ -static BOOL -enable_privilege(LUID luid_privilege, HANDLE *revert_token, BOOL *revert_only_privilege) -{ - HANDLE thread_token; - HANDLE new_token; - - if (OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, TRUE, &thread_token)) - { - if (set_privilege(thread_token, luid_privilege, TRUE)) - { - /* - * Indicate that correct revert method is just to - * disable privilege in access token. - */ - if (revert_token && revert_only_privilege) - { - *revert_token = thread_token; - *revert_only_privilege = TRUE; - } - else - { - CloseHandle(thread_token); - } - return TRUE; - } - CloseHandle(thread_token); - /* - * If enabling privilege failed then try to enable it via - * primary process access token. - */ - } - - /* - * If the current thread has already active thread access token then - * open it with just impersonate right as it would be used only for - * future revert. - */ - if (revert_token && revert_only_privilege) - { - if (!OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE, TRUE, &thread_token)) - { - if (GetLastError() != ERROR_NO_TOKEN) - return FALSE; - thread_token = NULL; - } - - /* - * If current thread has no access token (and uses primary - * process access token) or it does not have permission to - * adjust privileges or it does not have specified privilege - * then create a copy of the primary process access token, - * assign it for the current thread (= impersonate self) - * and then try adjusting privilege again. - */ - if (!ImpersonateSelf(SecurityImpersonation)) - { - if (thread_token) - CloseHandle(thread_token); - return FALSE; - } - } - - if (!OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, TRUE, &new_token)) - { - /* thread_token is set only when we were asked for revert method. */ - if (revert_token && revert_only_privilege) - revert_to_token(thread_token); - return FALSE; - } - - if (!set_privilege(new_token, luid_privilege, TRUE)) - { - CloseHandle(new_token); - /* thread_token is set only when we were asked for revert method. */ - if (revert_token && revert_only_privilege) - revert_to_token(thread_token); - return FALSE; - } - - /* - * Indicate that correct revert method is to change to the previous - * access token. Either to the primary process access token or to the - * previous thread access token. - */ - if (revert_token && revert_only_privilege) - { - *revert_token = thread_token; - *revert_only_privilege = FALSE; - } - return TRUE; -} - -/* - * Revert particular privilege for the current thread was previously enabled by - * enable_privilege() call. Either disable privilege in specified access token - * or revert to previous access token. - */ -static VOID -revert_privilege(LUID luid_privilege, HANDLE revert_token, BOOL revert_only_privilege) -{ - if (revert_only_privilege) - { - set_privilege(revert_token, luid_privilege, FALSE); - CloseHandle(revert_token); - } - else - { - revert_to_token(revert_token); - } -} - -/* - * Return owner of the access token used by the current thread. Buffer for - * returned owner needs to be released by LocalFree() call. - */ -static TOKEN_OWNER * -get_current_token_owner(VOID) -{ - HANDLE token; - DWORD length; - TOKEN_OWNER *owner; - - /* - * If the current thread does not have active access token then thread - * uses primary process access token for all permission checks. - */ - if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &token) && - (GetLastError() != ERROR_NO_TOKEN || - !OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token))) - return NULL; - - if (!GetTokenInformation(token, TokenOwner, NULL, 0, &length) && - GetLastError() != ERROR_INSUFFICIENT_BUFFER) - { - CloseHandle(token); - return NULL; - } - -retry: - owner = (TOKEN_OWNER *)LocalAlloc(LPTR, length); - if (!owner) - { - CloseHandle(token); - return NULL; - } - - if (!GetTokenInformation(token, TokenOwner, owner, length, &length)) - { - /* - * Length of token owner (SID) buffer between two get calls may - * changes (e.g. by another thread of process), so retry. - */ - if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) - { - LocalFree(owner); - goto retry; - } - LocalFree(owner); - CloseHandle(token); - return NULL; - } - - CloseHandle(token); - return owner; -} - -/* - * Grant particular permissions in the primary access token of the specified - * process for the owner of current thread token and set old DACL of the - * process access token for reverting permissions. Security descriptor is - * just memory buffer for old DACL. - */ -static BOOL -grant_process_token_dacl_permissions(HANDLE process, DWORD permissions, HANDLE *token, PACL *old_dacl, PSECURITY_DESCRIPTOR *security_descriptor) -{ - GetSecurityInfoProt MyGetSecurityInfo; - SetSecurityInfoProt MySetSecurityInfo; - SetEntriesInAclProt MySetEntriesInAcl; - EXPLICIT_ACCESS explicit_access; - TOKEN_OWNER *owner; - HMODULE advapi32; - PACL new_dacl; - - /* - * This source file already uses advapi32.dll library, so it is - * linked to executable and automatically loaded when starting - * current running process. - */ - advapi32 = GetModuleHandle(TEXT("advapi32.dll")); - if (!advapi32) - return FALSE; - - /* - * It does not matter if SetEntriesInAclA() or SetEntriesInAclW() is - * called as no string is passed to SetEntriesInAcl function. - */ - MyGetSecurityInfo = (GetSecurityInfoProt)(LPVOID)GetProcAddress(advapi32, "GetSecurityInfo"); - MySetSecurityInfo = (SetSecurityInfoProt)(LPVOID)GetProcAddress(advapi32, "SetSecurityInfo"); - MySetEntriesInAcl = (SetEntriesInAclProt)(LPVOID)GetProcAddress(advapi32, "SetEntriesInAclA"); - if (!MyGetSecurityInfo || !MySetSecurityInfo || !MySetEntriesInAcl) - return FALSE; - - owner = get_current_token_owner(); - if (!owner) - return FALSE; - - /* - * READ_CONTROL is required for GetSecurityInfo(DACL_SECURITY_INFORMATION) - * and WRITE_DAC is required for SetSecurityInfo(DACL_SECURITY_INFORMATION). - */ - if (!OpenProcessToken(process, READ_CONTROL | WRITE_DAC, token)) - { - LocalFree(owner); - return FALSE; - } - - if (MyGetSecurityInfo(*token, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, old_dacl, NULL, security_descriptor) != ERROR_SUCCESS) - { - LocalFree(owner); - CloseHandle(*token); - return FALSE; - } - - /* - * Set new explicit access for the owner of the current thread access - * token with non-inherited granting access to specified permissions. - */ - explicit_access.grfAccessPermissions = permissions; - explicit_access.grfAccessMode = GRANT_ACCESS; - explicit_access.grfInheritance = NO_PROPAGATE_INHERIT_ACE; - explicit_access.Trustee.pMultipleTrustee = NULL; - explicit_access.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; - explicit_access.Trustee.TrusteeForm = TRUSTEE_IS_SID; - explicit_access.Trustee.TrusteeType = TRUSTEE_IS_USER; - /* - * Unfortunately i586-mingw32msvc toolchain does not have pSid pointer - * member in Trustee union. So assign owner SID to ptstrName pointer - * member which aliases with pSid pointer member in the same union. - */ - explicit_access.Trustee.ptstrName = (PVOID)owner->Owner; - - if (MySetEntriesInAcl(1, &explicit_access, *old_dacl, &new_dacl) != ERROR_SUCCESS) - { - LocalFree(*security_descriptor); - LocalFree(owner); - CloseHandle(*token); - return FALSE; - } - - if (MySetSecurityInfo(*token, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, new_dacl, NULL) != ERROR_SUCCESS) - { - LocalFree(new_dacl); - LocalFree(*security_descriptor); - LocalFree(owner); - CloseHandle(*token); - return FALSE; - } - - LocalFree(new_dacl); - LocalFree(owner); - return TRUE; -} - -/* - * Revert particular granted permissions in specified access token done by - * grant_process_token_dacl_permissions() call. - */ -static VOID -revert_token_dacl_permissions(HANDLE token, PACL old_dacl, PSECURITY_DESCRIPTOR security_descriptor) -{ - SetSecurityInfoProt MySetSecurityInfo; - HMODULE advapi32; - - /* - * This source file already uses advapi32.dll library, so it is - * linked to executable and automatically loaded when starting - * current running process. - */ - advapi32 = GetModuleHandle(TEXT("advapi32.dll")); - if (advapi32) - { - MySetSecurityInfo = (SetSecurityInfoProt)(LPVOID)GetProcAddress(advapi32, "SetSecurityInfo"); - MySetSecurityInfo(token, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, old_dacl, NULL); - } - - LocalFree(security_descriptor); - CloseHandle(token); -} - -/* - * Change error mode of the current thread. If it is not possible then change - * error mode of the whole process. Always returns previous error mode. - */ -static UINT -change_error_mode(UINT new_mode) -{ - SetThreadErrorModeProt MySetThreadErrorMode = NULL; - HMODULE kernel32; - DWORD old_mode; - - /* - * Function SetThreadErrorMode() was introduced in Windows 7, so use - * GetProcAddress() for compatibility with older systems. - */ - kernel32 = GetModuleHandle(TEXT("kernel32.dll")); - if (kernel32) - MySetThreadErrorMode = (SetThreadErrorModeProt)(LPVOID)GetProcAddress(kernel32, "SetThreadErrorMode"); - - if (MySetThreadErrorMode && - MySetThreadErrorMode(new_mode, &old_mode)) - return old_mode; - - /* - * Fallback to function SetErrorMode() which modifies error mode of the - * whole process and returns old mode. - */ - return SetErrorMode(new_mode); -} - -/* - * Open process handle specified by the process id with the query right and - * optionally also with vm read right. - */ -static HANDLE -open_process_for_query(DWORD pid, BOOL with_vm_read) -{ - BOOL revert_only_privilege; - LUID luid_debug_privilege; - OSVERSIONINFO version; - DWORD process_right; - HANDLE revert_token; - HANDLE process; - - /* - * Some processes on Windows Vista and higher systems can be opened only - * with PROCESS_QUERY_LIMITED_INFORMATION right. This right is enough - * for accessing primary process token. But this right is not supported - * on older pre-Vista systems. When the current thread on these older - * systems does not have Debug privilege then OpenProcess() fails with - * ERROR_ACCESS_DENIED. If the current thread has Debug privilege then - * OpenProcess() success and returns handle to requested process. - * Problem is that this handle does not have PROCESS_QUERY_INFORMATION - * right and so cannot be used for accessing primary process token - * on those older systems. Moreover it has zero rights and therefore - * such handle is fully useless. So never try to use open process with - * PROCESS_QUERY_LIMITED_INFORMATION right on older systems than - * Windows Vista (NT 6.0). - */ - version.dwOSVersionInfoSize = sizeof(version); - if (GetVersionEx(&version) && - version.dwPlatformId == VER_PLATFORM_WIN32_NT && - version.dwMajorVersion >= 6) - process_right = PROCESS_QUERY_LIMITED_INFORMATION; - else - process_right = PROCESS_QUERY_INFORMATION; - - if (with_vm_read) - process_right |= PROCESS_VM_READ; - - process = OpenProcess(process_right, FALSE, pid); - if (process) - return process; - - /* - * It is possible to open only processes to which owner of the current - * thread access token has permissions. For opening other processing it - * is required to have Debug privilege enabled. By default local - * administrators have this privilege, but it is disabled. So try to - * enable it and then try to open process again. - */ - - if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid_debug_privilege)) - return NULL; - - if (!enable_privilege(luid_debug_privilege, &revert_token, &revert_only_privilege)) - return NULL; - - process = OpenProcess(process_right, FALSE, pid); - - revert_privilege(luid_debug_privilege, revert_token, revert_only_privilege); - - return process; -} - -/* - * Check if process image path name (wide string) matches exe file name - * (7-bit ASCII string). Do case-insensitive string comparison. Process - * image path name can be in any namespace format (DOS, Win32, UNC, ...). - */ -static BOOL -check_process_name(LPCWSTR path, DWORD path_length, LPCSTR exe_file) -{ - DWORD exe_file_length; - WCHAR c1; - UCHAR c2; - DWORD i; - - exe_file_length = 0; - while (exe_file[exe_file_length] != '\0') - exe_file_length++; - - /* Path must have backslash before exe file name. */ - if (exe_file_length >= path_length || - path[path_length-exe_file_length-1] != L'\\') - return FALSE; - - for (i = 0; i < exe_file_length; i++) - { - c1 = path[path_length-exe_file_length+i]; - c2 = exe_file[i]; - /* - * Input string for comparison is 7-bit ASCII and file name part - * of path must not contain backslash as it is path separator. - */ - if (c1 >= 0x80 || c2 >= 0x80 || c1 == L'\\') - return FALSE; - if (c1 >= L'a' && c1 <= L'z') - c1 -= L'a' - L'A'; - if (c2 >= 'a' && c2 <= 'z') - c2 -= 'a' - 'A'; - if (c1 != c2) - return FALSE; - } - - return TRUE; -} - -/* Open process handle with the query right specified by process exe file. */ -static HANDLE -find_and_open_process_for_query(LPCSTR exe_file) -{ - GetProcessImageFileNameWProt MyGetProcessImageFileNameW; - GetModuleFileNameExWProt MyGetModuleFileNameExW; - EnumProcessesProt MyEnumProcesses; - HMODULE kernel32, psapi; - UINT prev_error_mode; - DWORD partial_retry; - BOOL found_process; - DWORD size, length; - DWORD *processes; - HANDLE process; - LPWSTR path; - DWORD error; - DWORD count; - DWORD i; - - psapi = NULL; - kernel32 = GetModuleHandle(TEXT("kernel32.dll")); - if (!kernel32) - return NULL; - - /* - * On Windows 7 and higher systems these functions are available in - * kernel32.dll library with K32 prefix. - */ - MyGetModuleFileNameExW = NULL; - MyGetProcessImageFileNameW = (GetProcessImageFileNameWProt)(LPVOID)GetProcAddress(kernel32, "K32GetProcessImageFileNameW"); - MyEnumProcesses = (EnumProcessesProt)(LPVOID)GetProcAddress(kernel32, "K32EnumProcesses"); - if (!MyGetProcessImageFileNameW || !MyEnumProcesses) - { - /* - * On older NT-based systems these functions are available in - * psapi.dll library without K32 prefix. - */ - prev_error_mode = change_error_mode(SEM_FAILCRITICALERRORS); - psapi = LoadLibrary(TEXT("psapi.dll")); - change_error_mode(prev_error_mode); - - if (!psapi) - return NULL; - - /* - * Function GetProcessImageFileNameW() is available in - * Windows XP and higher systems. On older versions is - * available function GetModuleFileNameExW(). - */ - MyGetProcessImageFileNameW = (GetProcessImageFileNameWProt)(LPVOID)GetProcAddress(psapi, "GetProcessImageFileNameW"); - MyGetModuleFileNameExW = (GetModuleFileNameExWProt)(LPVOID)GetProcAddress(psapi, "GetModuleFileNameExW"); - MyEnumProcesses = (EnumProcessesProt)(LPVOID)GetProcAddress(psapi, "EnumProcesses"); - if ((!MyGetProcessImageFileNameW && !MyGetModuleFileNameExW) || !MyEnumProcesses) - { - FreeLibrary(psapi); - return NULL; - } - } - - /* Make initial buffer size for 1024 processes. */ - size = 1024 * sizeof(*processes); - -retry: - processes = (DWORD *)LocalAlloc(LPTR, size); - if (!processes) - { - if (psapi) - FreeLibrary(psapi); - return NULL; - } - - if (!MyEnumProcesses(processes, size, &length)) - { - LocalFree(processes); - if (psapi) - FreeLibrary(psapi); - return NULL; - } - else if (size == length) - { - /* - * There is no indication given when the buffer is too small to - * store all process identifiers. Therefore if returned length - * is same as buffer size there can be more processes. Call - * again with larger buffer. - */ - LocalFree(processes); - size *= 2; - goto retry; - } - - process = NULL; - count = length / sizeof(*processes); - - for (i = 0; i < count; i++) - { - /* Skip System Idle Process. */ - if (processes[i] == 0) - continue; - - /* - * Function GetModuleFileNameExW() requires additional - * PROCESS_VM_READ right as opposite to function - * GetProcessImageFileNameW() which does not need it. - */ - process = open_process_for_query(processes[i], MyGetProcessImageFileNameW ? FALSE : TRUE); - if (!process) - continue; - - /* - * Set initial buffer size to 256 (wide) characters. - * Final path length on the modern NT-based systems can be also larger. - */ - size = 256; - found_process = FALSE; - partial_retry = 0; - -retry_path: - path = (LPWSTR)LocalAlloc(LPTR, size * sizeof(*path)); - if (!path) - goto end_path; - - if (MyGetProcessImageFileNameW) - length = MyGetProcessImageFileNameW(process, path, size); - else - length = MyGetModuleFileNameExW(process, NULL, path, size); - - error = GetLastError(); - - /* - * GetModuleFileNameEx() returns zero and signal error ERROR_PARTIAL_COPY - * when remote process is in the middle of updating its module table. - * Sleep 10 ms and try again, max 10 attempts. - */ - if (!MyGetProcessImageFileNameW) - { - if (length == 0 && error == ERROR_PARTIAL_COPY && partial_retry++ < 10) - { - Sleep(10); - goto retry_path; - } - partial_retry = 0; - } - - /* - * When buffer is too small then function GetModuleFileNameEx() returns - * its size argument on older systems (Windows XP) or its size minus - * argument one on new systems (Windows 10) without signalling any error. - * Function GetProcessImageFileNameW() on the other hand returns zero - * value and signals error ERROR_INSUFFICIENT_BUFFER. So in all these - * cases call function again with larger buffer. - */ - - if (MyGetProcessImageFileNameW && length == 0 && error != ERROR_INSUFFICIENT_BUFFER) - goto end_path; - - if ((MyGetProcessImageFileNameW && length == 0) || - (!MyGetProcessImageFileNameW && (length == size || length == size-1))) - { - LocalFree(path); - size *= 2; - goto retry_path; - } - - if (length && check_process_name(path, length, exe_file)) - found_process = TRUE; - -end_path: - if (path) - { - LocalFree(path); - path = NULL; - } - - if (found_process) - break; - - CloseHandle(process); - process = NULL; - } - - LocalFree(processes); - - if (psapi) - FreeLibrary(psapi); - - return process; -} - -/* - * Try to open primary access token of the particular process with specified - * rights. Before opening access token try to adjust DACL permissions of the - * primary process access token, so following open does not fail on error - * related to no open permissions. Revert DACL permissions after open attempt. - * As following steps are not atomic, try to execute them more times in case - * of possible race conditions caused by other threads or processes. - */ -static HANDLE -try_grant_permissions_and_open_process_token(HANDLE process, DWORD rights) -{ - PSECURITY_DESCRIPTOR security_descriptor; - HANDLE grant_token; - PACL old_dacl; - HANDLE token; - DWORD retry; - DWORD error; - - /* - * This code is not atomic. Between grant and open calls can other - * thread or process change or revert permissions. So try to execute - * it more times. - */ - for (retry = 0; retry < 10; retry++) - { - if (!grant_process_token_dacl_permissions(process, rights, &grant_token, &old_dacl, &security_descriptor)) - return NULL; - if (!OpenProcessToken(process, rights, &token)) - { - token = NULL; - error = GetLastError(); - } - revert_token_dacl_permissions(grant_token, old_dacl, security_descriptor); - if (token) - return token; - else if (error != ERROR_ACCESS_DENIED) - return NULL; - } - - return NULL; -} - -/* - * Open primary access token of particular process handle with specified rights. - * If permissions for specified rights are missing then try to grant them. - */ -static HANDLE -open_process_token_with_rights(HANDLE process, DWORD rights) -{ - HANDLE old_token; - HANDLE token; - - /* First try to open primary access token of process handle directly. */ - if (OpenProcessToken(process, rights, &token)) - return token; - - /* - * If opening failed then it means that owner of the current thread - * access token does not have permission for it. Try it again with - * primary process access token. - */ - if (change_token_to_primary(&old_token)) - { - if (!OpenProcessToken(process, rights, &token)) - token = NULL; - revert_to_token(old_token); - if (token) - return token; - } - - /* - * If opening is still failing then try to grant specified permissions - * for the current thread and try to open it again. - */ - token = try_grant_permissions_and_open_process_token(process, rights); - if (token) - return token; - - /* - * And if it is still failing then try it again with granting - * permissions for the primary process token of the current process. - */ - if (change_token_to_primary(&old_token)) - { - token = try_grant_permissions_and_open_process_token(process, rights); - revert_to_token(old_token); - if (token) - return token; - } - - /* - * TODO: Sorry, no other option for now... - * It could be possible to use Take Ownership Name privilege to - * temporary change token owner of specified process to the owner of - * the current thread token, grant permissions for current thread in - * that process token, change ownership back to original one, open - * that process token and revert granted permissions. But this is - * not implemented yet. - */ - return NULL; -} - /* * Call supplied function Func with its Arg and if it fails with * ERROR_PRIVILEGE_NOT_HELD then try to enable Tcb privilege and @@ -1035,14 +150,14 @@ CallFuncWithTcbPrivilege(BOOL (*Func)(LPVOID), LPVOID Arg) * If the current thread has already Tcb privilege enabled then there * is some additional unhanded restriction. */ - if (have_privilege(luid_tcb_privilege)) + if (win32_have_privilege(luid_tcb_privilege)) goto err_privilege_not_held; /* Try to enable Tcb privilege and try function call again. */ - if (enable_privilege(luid_tcb_privilege, &revert_token_tcb_privilege, &revert_only_tcb_privilege)) + if (win32_enable_privilege(luid_tcb_privilege, &revert_token_tcb_privilege, &revert_only_tcb_privilege)) { ret = Func(Arg); - revert_privilege(luid_tcb_privilege, revert_token_tcb_privilege, revert_only_tcb_privilege); + win32_revert_privilege(luid_tcb_privilege, revert_token_tcb_privilege, revert_only_tcb_privilege); goto ret; } @@ -1058,7 +173,7 @@ CallFuncWithTcbPrivilege(BOOL (*Func)(LPVOID), LPVOID Arg) * So try to enable it in case it is currently disabled. */ if (LookupPrivilegeValue(NULL, SE_IMPERSONATE_NAME, &luid_impersonate_privilege) && - !have_privilege(luid_impersonate_privilege)) + !win32_have_privilege(luid_impersonate_privilege)) { /* * If current thread does not have Impersonate privilege enabled @@ -1068,11 +183,11 @@ CallFuncWithTcbPrivilege(BOOL (*Func)(LPVOID), LPVOID Arg) * affects all process threads). Both actions will be reverted * at the end of this function. */ - if (enable_privilege(luid_impersonate_privilege, &revert_token_impersonate_privilege, &revert_only_impersonate_privilege)) + if (win32_enable_privilege(luid_impersonate_privilege, &revert_token_impersonate_privilege, &revert_only_impersonate_privilege)) { impersonate_privilege_enabled = TRUE; } - else if (enable_privilege(luid_impersonate_privilege, NULL, NULL)) + else if (win32_enable_privilege(luid_impersonate_privilege, NULL, NULL)) { impersonate_privilege_enabled = TRUE; revert_token_impersonate_privilege = NULL; @@ -1089,10 +204,10 @@ CallFuncWithTcbPrivilege(BOOL (*Func)(LPVOID), LPVOID Arg) * thread requires Impersonate privilege, so enabling Tcb again * could now pass. */ - if (enable_privilege(luid_tcb_privilege, &revert_token_tcb_privilege, &revert_only_tcb_privilege)) + if (win32_enable_privilege(luid_tcb_privilege, &revert_token_tcb_privilege, &revert_only_tcb_privilege)) { ret = Func(Arg); - revert_privilege(luid_tcb_privilege, revert_token_tcb_privilege, revert_only_tcb_privilege); + win32_revert_privilege(luid_tcb_privilege, revert_token_tcb_privilege, revert_only_tcb_privilege); goto ret; } } @@ -1108,7 +223,7 @@ CallFuncWithTcbPrivilege(BOOL (*Func)(LPVOID), LPVOID Arg) * administrators (unless it was disabled by local administrators). */ - lsass_process = find_and_open_process_for_query("lsass.exe"); + lsass_process = win32_find_and_open_process_for_query("lsass.exe"); if (!lsass_process) goto err_privilege_not_held; @@ -1117,7 +232,7 @@ CallFuncWithTcbPrivilege(BOOL (*Func)(LPVOID), LPVOID Arg) * rights. Just these two rights are required for impersonating other * primary process token (impersonate right is really not required!). */ - lsass_token = open_process_token_with_rights(lsass_process, TOKEN_QUERY | TOKEN_DUPLICATE); + lsass_token = win32_open_process_token_with_rights(lsass_process, TOKEN_QUERY | TOKEN_DUPLICATE); CloseHandle(lsass_process); @@ -1128,7 +243,7 @@ CallFuncWithTcbPrivilege(BOOL (*Func)(LPVOID), LPVOID Arg) * After successful open of the primary lsass.exe process access token, * assign its copy for the current thread. */ - if (!change_token(lsass_token, &old_token)) + if (!win32_change_token(lsass_token, &old_token)) goto err_privilege_not_held; revert_to_old_token = TRUE; @@ -1144,8 +259,8 @@ CallFuncWithTcbPrivilege(BOOL (*Func)(LPVOID), LPVOID Arg) * be reverted. So there is no need to setup revert method for * enabling privilege. */ - if (have_privilege(luid_tcb_privilege) || - !enable_privilege(luid_tcb_privilege, NULL, NULL)) + if (win32_have_privilege(luid_tcb_privilege) || + !win32_enable_privilege(luid_tcb_privilege, NULL, NULL)) goto err_privilege_not_held; ret = Func(Arg); @@ -1158,10 +273,10 @@ err_privilege_not_held: ret: if (revert_to_old_token) - revert_to_token(old_token); + win32_revert_to_token(old_token); if (impersonate_privilege_enabled) - revert_privilege(luid_impersonate_privilege, revert_token_impersonate_privilege, revert_only_impersonate_privilege); + win32_revert_privilege(luid_impersonate_privilege, revert_token_impersonate_privilege, revert_only_impersonate_privilege); if (lsass_token) CloseHandle(lsass_token); @@ -1228,9 +343,9 @@ SetProcessUserModeIOPL(VOID) * It means that NT kernel does not show unwanted GUI message box to user * when LoadLibrary() function fails. */ - prev_error_mode = change_error_mode(SEM_FAILCRITICALERRORS); + prev_error_mode = win32_change_error_mode(SEM_FAILCRITICALERRORS); ntdll = LoadLibrary(TEXT("ntdll.dll")); - change_error_mode(prev_error_mode); + win32_change_error_mode(prev_error_mode); if (!ntdll) { SetLastError(ERROR_INVALID_FUNCTION); diff --git a/lib/win32-helpers.c b/lib/win32-helpers.c index dd47865..29ce5d8 100644 --- a/lib/win32-helpers.c +++ b/lib/win32-helpers.c @@ -9,10 +9,43 @@ */ #include +#include #include /* for sprintf() */ #include "win32-helpers.h" +/* Unfortunately i586-mingw32msvc toolchain does not provide this constant. */ +#ifndef PROCESS_QUERY_LIMITED_INFORMATION +#define PROCESS_QUERY_LIMITED_INFORMATION 0x1000 +#endif + +/* + * These psapi functions are available in kernel32.dll library with K32 prefix + * on Windows 7 and higher systems. On older Windows systems these functions are + * available in psapi.dll libary without K32 prefix. So resolve pointers to + * these functions dynamically at runtime from the available system library. + * Function GetProcessImageFileNameW() is not available on Windows 2000 and + * older systems. + */ +typedef BOOL (WINAPI *EnumProcessesProt)(DWORD *lpidProcess, DWORD cb, DWORD *cbNeeded); +typedef DWORD (WINAPI *GetProcessImageFileNameWProt)(HANDLE hProcess, LPWSTR lpImageFileName, DWORD nSize); +typedef DWORD (WINAPI *GetModuleFileNameExWProt)(HANDLE hProcess, HMODULE hModule, LPWSTR lpImageFileName, DWORD nSize); + +/* + * These aclapi functions are available in advapi.dll library on Windows NT 4.0 + * and higher systems. + */ +typedef DWORD (WINAPI *GetSecurityInfoProt)(HANDLE handle, SE_OBJECT_TYPE ObjectType, SECURITY_INFORMATION SecurityInfo, PSID *ppsidOwner, PSID *ppsidGroup, PACL *ppDacl, PACL *ppSacl, PSECURITY_DESCRIPTOR *ppSecurityDescriptor); +typedef DWORD (WINAPI *SetSecurityInfoProt)(HANDLE handle, SE_OBJECT_TYPE ObjectType, SECURITY_INFORMATION SecurityInfo, PSID psidOwner, PSID psidGroup, PACL pDacl, PACL pSacl); +typedef DWORD (WINAPI *SetEntriesInAclProt)(ULONG cCountOfExplicitEntries, PEXPLICIT_ACCESS pListOfExplicitEntries, PACL OldAcl, PACL *NewAcl); + +/* + * This errhandlingapi function is available in kernel32.dll library on + * Windows 7 and higher systems. + */ +typedef BOOL (WINAPI *SetThreadErrorModeProt)(DWORD dwNewMode, LPDWORD lpOldMode); + + const char * win32_strerror(DWORD win32_error_id) { @@ -95,3 +128,856 @@ win32_is_32bit_on_win8_64bit_system(void) return win32_is_32bit_on_64bit_system(); #endif } + +/* + * Change error mode of the current thread. If it is not possible then change + * error mode of the whole process. Always returns previous error mode. + */ +UINT +win32_change_error_mode(UINT new_mode) +{ + SetThreadErrorModeProt MySetThreadErrorMode = NULL; + HMODULE kernel32; + DWORD old_mode; + + /* + * Function SetThreadErrorMode() was introduced in Windows 7, so use + * GetProcAddress() for compatibility with older systems. + */ + kernel32 = GetModuleHandle(TEXT("kernel32.dll")); + if (kernel32) + MySetThreadErrorMode = (SetThreadErrorModeProt)(LPVOID)GetProcAddress(kernel32, "SetThreadErrorMode"); + + if (MySetThreadErrorMode && + MySetThreadErrorMode(new_mode, &old_mode)) + return old_mode; + + /* + * Fallback to function SetErrorMode() which modifies error mode of the + * whole process and returns old mode. + */ + return SetErrorMode(new_mode); +} + +/* + * Check if the current thread has particular privilege in current active access + * token. Case when it not possible to determinate it (e.g. current thread does + * not have permission to open its own current active access token) is evaluated + * as thread does not have that privilege. + */ +BOOL +win32_have_privilege(LUID luid_privilege) +{ + PRIVILEGE_SET priv; + HANDLE token; + BOOL ret; + + /* + * If the current thread does not have active access token then thread + * uses primary process access token for all permission checks. + */ + if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &token) && + (GetLastError() != ERROR_NO_TOKEN || + !OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token))) + return FALSE; + + priv.PrivilegeCount = 1; + priv.Control = PRIVILEGE_SET_ALL_NECESSARY; + priv.Privilege[0].Luid = luid_privilege; + priv.Privilege[0].Attributes = SE_PRIVILEGE_ENABLED; + + if (!PrivilegeCheck(token, &priv, &ret)) + return FALSE; + + return ret; +} + +/* + * Enable or disable particular privilege in specified access token. + * + * Note that it is not possible to disable privilege in access token with + * SE_PRIVILEGE_ENABLED_BY_DEFAULT attribute. This function does not check + * this case and incorrectly returns no error even when disabling failed. + * Rationale for this decision: Simplification of this function as WinAPI + * call AdjustTokenPrivileges() does not signal error in this case too. + */ +static BOOL +set_privilege(HANDLE token, LUID luid_privilege, BOOL enable) +{ + TOKEN_PRIVILEGES token_privileges; + + token_privileges.PrivilegeCount = 1; + token_privileges.Privileges[0].Luid = luid_privilege; + token_privileges.Privileges[0].Attributes = enable ? SE_PRIVILEGE_ENABLED : 0; + + /* + * WinAPI function AdjustTokenPrivileges() success also when not all + * privileges were enabled. It is always required to check for failure + * via GetLastError() call. AdjustTokenPrivileges() always sets error + * also when it success, as opposite to other WinAPI functions. + */ + if (!AdjustTokenPrivileges(token, FALSE, &token_privileges, sizeof(token_privileges), NULL, NULL) || + GetLastError() != ERROR_SUCCESS) + return FALSE; + + return TRUE; +} + +/* + * Change access token for the current thread to new specified access token. + * Previously active access token is stored in old_token variable and can be + * used for reverting to this access token. It is set to NULL if the current + * thread previously used primary process access token. + */ +BOOL +win32_change_token(HANDLE new_token, HANDLE *old_token) +{ + HANDLE token; + + if (!OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE, TRUE, &token)) + { + if (GetLastError() != ERROR_NO_TOKEN) + return FALSE; + token = NULL; + } + + if (!ImpersonateLoggedOnUser(new_token)) + { + if (token) + CloseHandle(token); + return FALSE; + } + + *old_token = token; + return TRUE; +} + +/* + * Change access token for the current thread to the primary process access + * token. This function fails also when the current thread already uses primary + * process access token. + */ +static BOOL +change_token_to_primary(HANDLE *old_token) +{ + HANDLE token; + + if (!OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE, TRUE, &token)) + return FALSE; + + RevertToSelf(); + + *old_token = token; + return TRUE; +} + +/* + * Revert to the specified access token for the current thread. When access + * token is specified as NULL then revert to the primary process access token. + * Use to revert after win32_change_token() or change_token_to_primary() call. + */ +VOID +win32_revert_to_token(HANDLE token) +{ + /* + * If SetThreadToken() call fails then there is no option to revert to + * the specified previous thread access token. So in this case revert to + * the primary process access token. + */ + if (!token || !SetThreadToken(NULL, token)) + RevertToSelf(); + if (token) + CloseHandle(token); +} + +/* + * Enable particular privilege for the current thread. And set method how to + * revert this privilege (if to revert whole token or only privilege). + */ +BOOL +win32_enable_privilege(LUID luid_privilege, HANDLE *revert_token, BOOL *revert_only_privilege) +{ + HANDLE thread_token; + HANDLE new_token; + + if (OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, TRUE, &thread_token)) + { + if (set_privilege(thread_token, luid_privilege, TRUE)) + { + /* + * Indicate that correct revert method is just to + * disable privilege in access token. + */ + if (revert_token && revert_only_privilege) + { + *revert_token = thread_token; + *revert_only_privilege = TRUE; + } + else + { + CloseHandle(thread_token); + } + return TRUE; + } + CloseHandle(thread_token); + /* + * If enabling privilege failed then try to enable it via + * primary process access token. + */ + } + + /* + * If the current thread has already active thread access token then + * open it with just impersonate right as it would be used only for + * future revert. + */ + if (revert_token && revert_only_privilege) + { + if (!OpenThreadToken(GetCurrentThread(), TOKEN_IMPERSONATE, TRUE, &thread_token)) + { + if (GetLastError() != ERROR_NO_TOKEN) + return FALSE; + thread_token = NULL; + } + + /* + * If current thread has no access token (and uses primary + * process access token) or it does not have permission to + * adjust privileges or it does not have specified privilege + * then create a copy of the primary process access token, + * assign it for the current thread (= impersonate self) + * and then try adjusting privilege again. + */ + if (!ImpersonateSelf(SecurityImpersonation)) + { + if (thread_token) + CloseHandle(thread_token); + return FALSE; + } + } + + if (!OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, TRUE, &new_token)) + { + /* thread_token is set only when we were asked for revert method. */ + if (revert_token && revert_only_privilege) + win32_revert_to_token(thread_token); + return FALSE; + } + + if (!set_privilege(new_token, luid_privilege, TRUE)) + { + CloseHandle(new_token); + /* thread_token is set only when we were asked for revert method. */ + if (revert_token && revert_only_privilege) + win32_revert_to_token(thread_token); + return FALSE; + } + + /* + * Indicate that correct revert method is to change to the previous + * access token. Either to the primary process access token or to the + * previous thread access token. + */ + if (revert_token && revert_only_privilege) + { + *revert_token = thread_token; + *revert_only_privilege = FALSE; + } + return TRUE; +} + +/* + * Revert particular privilege for the current thread was previously enabled by + * win32_enable_privilege() call. Either disable privilege in specified access token + * or revert to previous access token. + */ +VOID +win32_revert_privilege(LUID luid_privilege, HANDLE revert_token, BOOL revert_only_privilege) +{ + if (revert_only_privilege) + { + set_privilege(revert_token, luid_privilege, FALSE); + CloseHandle(revert_token); + } + else + { + win32_revert_to_token(revert_token); + } +} + +/* + * Return owner of the access token used by the current thread. Buffer for + * returned owner needs to be released by LocalFree() call. + */ +static TOKEN_OWNER * +get_current_token_owner(VOID) +{ + HANDLE token; + DWORD length; + TOKEN_OWNER *owner; + + /* + * If the current thread does not have active access token then thread + * uses primary process access token for all permission checks. + */ + if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &token) && + (GetLastError() != ERROR_NO_TOKEN || + !OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token))) + return NULL; + + if (!GetTokenInformation(token, TokenOwner, NULL, 0, &length) && + GetLastError() != ERROR_INSUFFICIENT_BUFFER) + { + CloseHandle(token); + return NULL; + } + +retry: + owner = (TOKEN_OWNER *)LocalAlloc(LPTR, length); + if (!owner) + { + CloseHandle(token); + return NULL; + } + + if (!GetTokenInformation(token, TokenOwner, owner, length, &length)) + { + /* + * Length of token owner (SID) buffer between two get calls may + * changes (e.g. by another thread of process), so retry. + */ + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) + { + LocalFree(owner); + goto retry; + } + LocalFree(owner); + CloseHandle(token); + return NULL; + } + + CloseHandle(token); + return owner; +} + +/* + * Grant particular permissions in the primary access token of the specified + * process for the owner of current thread token and set old DACL of the + * process access token for reverting permissions. Security descriptor is + * just memory buffer for old DACL. + */ +static BOOL +grant_process_token_dacl_permissions(HANDLE process, DWORD permissions, HANDLE *token, PACL *old_dacl, PSECURITY_DESCRIPTOR *security_descriptor) +{ + GetSecurityInfoProt MyGetSecurityInfo; + SetSecurityInfoProt MySetSecurityInfo; + SetEntriesInAclProt MySetEntriesInAcl; + EXPLICIT_ACCESS explicit_access; + TOKEN_OWNER *owner; + HMODULE advapi32; + PACL new_dacl; + + /* + * This source file already uses advapi32.dll library, so it is + * linked to executable and automatically loaded when starting + * current running process. + */ + advapi32 = GetModuleHandle(TEXT("advapi32.dll")); + if (!advapi32) + return FALSE; + + /* + * It does not matter if SetEntriesInAclA() or SetEntriesInAclW() is + * called as no string is passed to SetEntriesInAcl function. + */ + MyGetSecurityInfo = (GetSecurityInfoProt)(LPVOID)GetProcAddress(advapi32, "GetSecurityInfo"); + MySetSecurityInfo = (SetSecurityInfoProt)(LPVOID)GetProcAddress(advapi32, "SetSecurityInfo"); + MySetEntriesInAcl = (SetEntriesInAclProt)(LPVOID)GetProcAddress(advapi32, "SetEntriesInAclA"); + if (!MyGetSecurityInfo || !MySetSecurityInfo || !MySetEntriesInAcl) + return FALSE; + + owner = get_current_token_owner(); + if (!owner) + return FALSE; + + /* + * READ_CONTROL is required for GetSecurityInfo(DACL_SECURITY_INFORMATION) + * and WRITE_DAC is required for SetSecurityInfo(DACL_SECURITY_INFORMATION). + */ + if (!OpenProcessToken(process, READ_CONTROL | WRITE_DAC, token)) + { + LocalFree(owner); + return FALSE; + } + + if (MyGetSecurityInfo(*token, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, old_dacl, NULL, security_descriptor) != ERROR_SUCCESS) + { + LocalFree(owner); + CloseHandle(*token); + return FALSE; + } + + /* + * Set new explicit access for the owner of the current thread access + * token with non-inherited granting access to specified permissions. + */ + explicit_access.grfAccessPermissions = permissions; + explicit_access.grfAccessMode = GRANT_ACCESS; + explicit_access.grfInheritance = NO_PROPAGATE_INHERIT_ACE; + explicit_access.Trustee.pMultipleTrustee = NULL; + explicit_access.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; + explicit_access.Trustee.TrusteeForm = TRUSTEE_IS_SID; + explicit_access.Trustee.TrusteeType = TRUSTEE_IS_USER; + /* + * Unfortunately i586-mingw32msvc toolchain does not have pSid pointer + * member in Trustee union. So assign owner SID to ptstrName pointer + * member which aliases with pSid pointer member in the same union. + */ + explicit_access.Trustee.ptstrName = (PVOID)owner->Owner; + + if (MySetEntriesInAcl(1, &explicit_access, *old_dacl, &new_dacl) != ERROR_SUCCESS) + { + LocalFree(*security_descriptor); + LocalFree(owner); + CloseHandle(*token); + return FALSE; + } + + if (MySetSecurityInfo(*token, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, new_dacl, NULL) != ERROR_SUCCESS) + { + LocalFree(new_dacl); + LocalFree(*security_descriptor); + LocalFree(owner); + CloseHandle(*token); + return FALSE; + } + + LocalFree(new_dacl); + LocalFree(owner); + return TRUE; +} + +/* + * Revert particular granted permissions in specified access token done by + * grant_process_token_dacl_permissions() call. + */ +static VOID +revert_token_dacl_permissions(HANDLE token, PACL old_dacl, PSECURITY_DESCRIPTOR security_descriptor) +{ + SetSecurityInfoProt MySetSecurityInfo; + HMODULE advapi32; + + /* + * This source file already uses advapi32.dll library, so it is + * linked to executable and automatically loaded when starting + * current running process. + */ + advapi32 = GetModuleHandle(TEXT("advapi32.dll")); + if (advapi32) + { + MySetSecurityInfo = (SetSecurityInfoProt)(LPVOID)GetProcAddress(advapi32, "SetSecurityInfo"); + MySetSecurityInfo(token, SE_KERNEL_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, old_dacl, NULL); + } + + LocalFree(security_descriptor); + CloseHandle(token); +} + +/* + * Open process handle specified by the process id with the query right and + * optionally also with vm read right. + */ +static HANDLE +open_process_for_query(DWORD pid, BOOL with_vm_read) +{ + BOOL revert_only_privilege; + LUID luid_debug_privilege; + OSVERSIONINFO version; + DWORD process_right; + HANDLE revert_token; + HANDLE process; + + /* + * Some processes on Windows Vista and higher systems can be opened only + * with PROCESS_QUERY_LIMITED_INFORMATION right. This right is enough + * for accessing primary process token. But this right is not supported + * on older pre-Vista systems. When the current thread on these older + * systems does not have Debug privilege then OpenProcess() fails with + * ERROR_ACCESS_DENIED. If the current thread has Debug privilege then + * OpenProcess() success and returns handle to requested process. + * Problem is that this handle does not have PROCESS_QUERY_INFORMATION + * right and so cannot be used for accessing primary process token + * on those older systems. Moreover it has zero rights and therefore + * such handle is fully useless. So never try to use open process with + * PROCESS_QUERY_LIMITED_INFORMATION right on older systems than + * Windows Vista (NT 6.0). + */ + version.dwOSVersionInfoSize = sizeof(version); + if (GetVersionEx(&version) && + version.dwPlatformId == VER_PLATFORM_WIN32_NT && + version.dwMajorVersion >= 6) + process_right = PROCESS_QUERY_LIMITED_INFORMATION; + else + process_right = PROCESS_QUERY_INFORMATION; + + if (with_vm_read) + process_right |= PROCESS_VM_READ; + + process = OpenProcess(process_right, FALSE, pid); + if (process) + return process; + + /* + * It is possible to open only processes to which owner of the current + * thread access token has permissions. For opening other processing it + * is required to have Debug privilege enabled. By default local + * administrators have this privilege, but it is disabled. So try to + * enable it and then try to open process again. + */ + + if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid_debug_privilege)) + return NULL; + + if (!win32_enable_privilege(luid_debug_privilege, &revert_token, &revert_only_privilege)) + return NULL; + + process = OpenProcess(process_right, FALSE, pid); + + win32_revert_privilege(luid_debug_privilege, revert_token, revert_only_privilege); + + return process; +} + +/* + * Check if process image path name (wide string) matches exe file name + * (7-bit ASCII string). Do case-insensitive string comparison. Process + * image path name can be in any namespace format (DOS, Win32, UNC, ...). + */ +static BOOL +check_process_name(LPCWSTR path, DWORD path_length, LPCSTR exe_file) +{ + DWORD exe_file_length; + WCHAR c1; + UCHAR c2; + DWORD i; + + exe_file_length = 0; + while (exe_file[exe_file_length] != '\0') + exe_file_length++; + + /* Path must have backslash before exe file name. */ + if (exe_file_length >= path_length || + path[path_length-exe_file_length-1] != L'\\') + return FALSE; + + for (i = 0; i < exe_file_length; i++) + { + c1 = path[path_length-exe_file_length+i]; + c2 = exe_file[i]; + /* + * Input string for comparison is 7-bit ASCII and file name part + * of path must not contain backslash as it is path separator. + */ + if (c1 >= 0x80 || c2 >= 0x80 || c1 == L'\\') + return FALSE; + if (c1 >= L'a' && c1 <= L'z') + c1 -= L'a' - L'A'; + if (c2 >= 'a' && c2 <= 'z') + c2 -= 'a' - 'A'; + if (c1 != c2) + return FALSE; + } + + return TRUE; +} + +/* Open process handle with the query right specified by process exe file. */ +HANDLE +win32_find_and_open_process_for_query(LPCSTR exe_file) +{ + GetProcessImageFileNameWProt MyGetProcessImageFileNameW; + GetModuleFileNameExWProt MyGetModuleFileNameExW; + EnumProcessesProt MyEnumProcesses; + HMODULE kernel32, psapi; + UINT prev_error_mode; + DWORD partial_retry; + BOOL found_process; + DWORD size, length; + DWORD *processes; + HANDLE process; + LPWSTR path; + DWORD error; + DWORD count; + DWORD i; + + psapi = NULL; + kernel32 = GetModuleHandle(TEXT("kernel32.dll")); + if (!kernel32) + return NULL; + + /* + * On Windows 7 and higher systems these functions are available in + * kernel32.dll library with K32 prefix. + */ + MyGetModuleFileNameExW = NULL; + MyGetProcessImageFileNameW = (GetProcessImageFileNameWProt)(LPVOID)GetProcAddress(kernel32, "K32GetProcessImageFileNameW"); + MyEnumProcesses = (EnumProcessesProt)(LPVOID)GetProcAddress(kernel32, "K32EnumProcesses"); + if (!MyGetProcessImageFileNameW || !MyEnumProcesses) + { + /* + * On older NT-based systems these functions are available in + * psapi.dll library without K32 prefix. + */ + prev_error_mode = win32_change_error_mode(SEM_FAILCRITICALERRORS); + psapi = LoadLibrary(TEXT("psapi.dll")); + win32_change_error_mode(prev_error_mode); + + if (!psapi) + return NULL; + + /* + * Function GetProcessImageFileNameW() is available in + * Windows XP and higher systems. On older versions is + * available function GetModuleFileNameExW(). + */ + MyGetProcessImageFileNameW = (GetProcessImageFileNameWProt)(LPVOID)GetProcAddress(psapi, "GetProcessImageFileNameW"); + MyGetModuleFileNameExW = (GetModuleFileNameExWProt)(LPVOID)GetProcAddress(psapi, "GetModuleFileNameExW"); + MyEnumProcesses = (EnumProcessesProt)(LPVOID)GetProcAddress(psapi, "EnumProcesses"); + if ((!MyGetProcessImageFileNameW && !MyGetModuleFileNameExW) || !MyEnumProcesses) + { + FreeLibrary(psapi); + return NULL; + } + } + + /* Make initial buffer size for 1024 processes. */ + size = 1024 * sizeof(*processes); + +retry: + processes = (DWORD *)LocalAlloc(LPTR, size); + if (!processes) + { + if (psapi) + FreeLibrary(psapi); + return NULL; + } + + if (!MyEnumProcesses(processes, size, &length)) + { + LocalFree(processes); + if (psapi) + FreeLibrary(psapi); + return NULL; + } + else if (size == length) + { + /* + * There is no indication given when the buffer is too small to + * store all process identifiers. Therefore if returned length + * is same as buffer size there can be more processes. Call + * again with larger buffer. + */ + LocalFree(processes); + size *= 2; + goto retry; + } + + process = NULL; + count = length / sizeof(*processes); + + for (i = 0; i < count; i++) + { + /* Skip System Idle Process. */ + if (processes[i] == 0) + continue; + + /* + * Function GetModuleFileNameExW() requires additional + * PROCESS_VM_READ right as opposite to function + * GetProcessImageFileNameW() which does not need it. + */ + process = open_process_for_query(processes[i], MyGetProcessImageFileNameW ? FALSE : TRUE); + if (!process) + continue; + + /* + * Set initial buffer size to 256 (wide) characters. + * Final path length on the modern NT-based systems can be also larger. + */ + size = 256; + found_process = FALSE; + partial_retry = 0; + +retry_path: + path = (LPWSTR)LocalAlloc(LPTR, size * sizeof(*path)); + if (!path) + goto end_path; + + if (MyGetProcessImageFileNameW) + length = MyGetProcessImageFileNameW(process, path, size); + else + length = MyGetModuleFileNameExW(process, NULL, path, size); + + error = GetLastError(); + + /* + * GetModuleFileNameEx() returns zero and signal error ERROR_PARTIAL_COPY + * when remote process is in the middle of updating its module table. + * Sleep 10 ms and try again, max 10 attempts. + */ + if (!MyGetProcessImageFileNameW) + { + if (length == 0 && error == ERROR_PARTIAL_COPY && partial_retry++ < 10) + { + Sleep(10); + goto retry_path; + } + partial_retry = 0; + } + + /* + * When buffer is too small then function GetModuleFileNameEx() returns + * its size argument on older systems (Windows XP) or its size minus + * argument one on new systems (Windows 10) without signalling any error. + * Function GetProcessImageFileNameW() on the other hand returns zero + * value and signals error ERROR_INSUFFICIENT_BUFFER. So in all these + * cases call function again with larger buffer. + */ + + if (MyGetProcessImageFileNameW && length == 0 && error != ERROR_INSUFFICIENT_BUFFER) + goto end_path; + + if ((MyGetProcessImageFileNameW && length == 0) || + (!MyGetProcessImageFileNameW && (length == size || length == size-1))) + { + LocalFree(path); + size *= 2; + goto retry_path; + } + + if (length && check_process_name(path, length, exe_file)) + found_process = TRUE; + +end_path: + if (path) + { + LocalFree(path); + path = NULL; + } + + if (found_process) + break; + + CloseHandle(process); + process = NULL; + } + + LocalFree(processes); + + if (psapi) + FreeLibrary(psapi); + + return process; +} + +/* + * Try to open primary access token of the particular process with specified + * rights. Before opening access token try to adjust DACL permissions of the + * primary process access token, so following open does not fail on error + * related to no open permissions. Revert DACL permissions after open attempt. + * As following steps are not atomic, try to execute them more times in case + * of possible race conditions caused by other threads or processes. + */ +static HANDLE +try_grant_permissions_and_open_process_token(HANDLE process, DWORD rights) +{ + PSECURITY_DESCRIPTOR security_descriptor; + HANDLE grant_token; + PACL old_dacl; + HANDLE token; + DWORD retry; + DWORD error; + + /* + * This code is not atomic. Between grant and open calls can other + * thread or process change or revert permissions. So try to execute + * it more times. + */ + for (retry = 0; retry < 10; retry++) + { + if (!grant_process_token_dacl_permissions(process, rights, &grant_token, &old_dacl, &security_descriptor)) + return NULL; + if (!OpenProcessToken(process, rights, &token)) + { + token = NULL; + error = GetLastError(); + } + revert_token_dacl_permissions(grant_token, old_dacl, security_descriptor); + if (token) + return token; + else if (error != ERROR_ACCESS_DENIED) + return NULL; + } + + return NULL; +} + +/* + * Open primary access token of particular process handle with specified rights. + * If permissions for specified rights are missing then try to grant them. + */ +HANDLE +win32_open_process_token_with_rights(HANDLE process, DWORD rights) +{ + HANDLE old_token; + HANDLE token; + + /* First try to open primary access token of process handle directly. */ + if (OpenProcessToken(process, rights, &token)) + return token; + + /* + * If opening failed then it means that owner of the current thread + * access token does not have permission for it. Try it again with + * primary process access token. + */ + if (change_token_to_primary(&old_token)) + { + if (!OpenProcessToken(process, rights, &token)) + token = NULL; + win32_revert_to_token(old_token); + if (token) + return token; + } + + /* + * If opening is still failing then try to grant specified permissions + * for the current thread and try to open it again. + */ + token = try_grant_permissions_and_open_process_token(process, rights); + if (token) + return token; + + /* + * And if it is still failing then try it again with granting + * permissions for the primary process token of the current process. + */ + if (change_token_to_primary(&old_token)) + { + token = try_grant_permissions_and_open_process_token(process, rights); + win32_revert_to_token(old_token); + if (token) + return token; + } + + /* + * TODO: Sorry, no other option for now... + * It could be possible to use Take Ownership Name privilege to + * temporary change token owner of specified process to the owner of + * the current thread token, grant permissions for current thread in + * that process token, change ownership back to original one, open + * that process token and revert granted permissions. But this is + * not implemented yet. + */ + return NULL; +} diff --git a/lib/win32-helpers.h b/lib/win32-helpers.h index 18f75de..e5f4445 100644 --- a/lib/win32-helpers.h +++ b/lib/win32-helpers.h @@ -2,3 +2,11 @@ const char *win32_strerror(DWORD win32_error_id); BOOL win32_is_non_nt_system(void); BOOL win32_is_32bit_on_64bit_system(void); BOOL win32_is_32bit_on_win8_64bit_system(void); +UINT win32_change_error_mode(UINT new_mode); +BOOL win32_have_privilege(LUID luid_privilege); +BOOL win32_enable_privilege(LUID luid_privilege, HANDLE *revert_token, BOOL *revert_only_privilege); +VOID win32_revert_privilege(LUID luid_privilege, HANDLE revert_token, BOOL revert_only_privilege); +BOOL win32_change_token(HANDLE new_token, HANDLE *old_token); +VOID win32_revert_to_token(HANDLE token); +HANDLE win32_find_and_open_process_for_query(LPCSTR exe_file); +HANDLE win32_open_process_token_with_rights(HANDLE process, DWORD rights); diff --git a/lib/win32-kldbg.c b/lib/win32-kldbg.c index bb00508..33f5751 100644 --- a/lib/win32-kldbg.c +++ b/lib/win32-kldbg.c @@ -15,7 +15,6 @@ #include /* for memset() and memcpy() */ #include "internal.h" -#include "i386-io-windows.h" #include "win32-helpers.h" #ifndef ERROR_NOT_FOUND @@ -480,7 +479,7 @@ win32_kldbg_setup(struct pci_access *a) return 0; } - if (!enable_privilege(luid_debug_privilege, &revert_token, &revert_only_privilege)) + if (!win32_enable_privilege(luid_debug_privilege, &revert_token, &revert_only_privilege)) { a->debug("Process does not have right to enable Debug privilege."); CloseHandle(kldbg_dev); @@ -502,7 +501,7 @@ win32_kldbg_setup(struct pci_access *a) CloseHandle(kldbg_dev); kldbg_dev = INVALID_HANDLE_VALUE; - revert_privilege(luid_debug_privilege, revert_token, revert_only_privilege); + win32_revert_privilege(luid_debug_privilege, revert_token, revert_only_privilege); revert_token = NULL; revert_only_privilege = FALSE; return 0; @@ -538,7 +537,7 @@ win32_kldbg_cleanup(struct pci_access *a UNUSED) if (debug_privilege_enabled) { - revert_privilege(luid_debug_privilege, revert_token, revert_only_privilege); + win32_revert_privilege(luid_debug_privilege, revert_token, revert_only_privilege); revert_token = NULL; revert_only_privilege = FALSE; debug_privilege_enabled = FALSE; diff --git a/lib/win32-sysdbg.c b/lib/win32-sysdbg.c index 22ecc60..99ce607 100644 --- a/lib/win32-sysdbg.c +++ b/lib/win32-sysdbg.c @@ -11,7 +11,6 @@ #include #include "internal.h" -#include "i386-io-windows.h" #include "win32-helpers.h" #ifndef NTSTATUS @@ -126,9 +125,9 @@ win32_sysdbg_setup(struct pci_access *a) if (win32_sysdbg_initialized) return 1; - prev_error_mode = change_error_mode(SEM_FAILCRITICALERRORS); + prev_error_mode = win32_change_error_mode(SEM_FAILCRITICALERRORS); ntdll = LoadLibrary(TEXT("ntdll.dll")); - change_error_mode(prev_error_mode); + win32_change_error_mode(prev_error_mode); if (!ntdll) { a->debug("Cannot open ntdll.dll library."); @@ -180,7 +179,7 @@ win32_sysdbg_setup(struct pci_access *a) return 0; } - if (!enable_privilege(luid_debug_privilege, &revert_token, &revert_only_privilege)) + if (!win32_enable_privilege(luid_debug_privilege, &revert_token, &revert_only_privilege)) { a->debug("Cannot enable Debug privilege."); FreeLibrary(ntdll); @@ -198,7 +197,7 @@ win32_sysdbg_setup(struct pci_access *a) return 1; } - revert_privilege(luid_debug_privilege, revert_token, revert_only_privilege); + win32_revert_privilege(luid_debug_privilege, revert_token, revert_only_privilege); revert_token = NULL; revert_only_privilege = FALSE; @@ -245,7 +244,7 @@ win32_sysdbg_cleanup(struct pci_access *a UNUSED) if (debug_privilege_enabled) { - revert_privilege(luid_debug_privilege, revert_token, revert_only_privilege); + win32_revert_privilege(luid_debug_privilege, revert_token, revert_only_privilege); revert_token = NULL; revert_only_privilege = FALSE; debug_privilege_enabled = FALSE;