From d1f3b5cf8f13370fd21583b91cf406ad789530e6 Mon Sep 17 00:00:00 2001 From: Pali Rohár Date: Wed, 24 May 2023 20:28:38 +0200 Subject: windows: Move win32_call_func_with_tcb_privilege() from i386-io-windows.h to win32-helpers.c --- lib/i386-io-windows.h | 191 +------------------------------------------------- lib/win32-helpers.c | 189 +++++++++++++++++++++++++++++++++++++++++++++++++ lib/win32-helpers.h | 1 + 3 files changed, 191 insertions(+), 190 deletions(-) diff --git a/lib/i386-io-windows.h b/lib/i386-io-windows.h index eff1901..d2da452 100644 --- a/lib/i386-io-windows.h +++ b/lib/i386-io-windows.h @@ -70,11 +70,6 @@ __readeflags(void) /* Read IOPL of the current process, IOPL is stored in eflag bits [13:12]. */ #define read_iopl() ((__readeflags() >> 12) & 0x3) -/* Unfortunately some toolchains do not provide this constant. */ -#ifndef SE_IMPERSONATE_NAME -#define SE_IMPERSONATE_NAME TEXT("SeImpersonatePrivilege") -#endif - /* * Unfortunately NtSetInformationProcess() function, ProcessUserModeIOPL * constant and all other helpers for its usage are not specified in any @@ -100,190 +95,6 @@ __readeflags(void) typedef NTSTATUS (NTAPI *NtSetInformationProcessProt)(HANDLE ProcessHandle, PROCESSINFOCLASS ProcessInformationClass, PVOID ProcessInformation, ULONG ProcessInformationLength); typedef ULONG (NTAPI *RtlNtStatusToDosErrorProt)(NTSTATUS Status); -/* - * Call supplied function Func with its Arg and if it fails with - * ERROR_PRIVILEGE_NOT_HELD then try to enable Tcb privilege and - * call Func with its Arg again. - */ -static BOOL -CallFuncWithTcbPrivilege(BOOL (*Func)(LPVOID), LPVOID Arg) -{ - LUID luid_tcb_privilege; - LUID luid_impersonate_privilege; - - HANDLE revert_token_tcb_privilege; - BOOL revert_only_tcb_privilege; - - HANDLE revert_token_impersonate_privilege; - BOOL revert_only_impersonate_privilege; - - BOOL impersonate_privilege_enabled; - - BOOL revert_to_old_token; - HANDLE old_token; - - HANDLE lsass_process; - HANDLE lsass_token; - - BOOL ret; - - impersonate_privilege_enabled = FALSE; - revert_to_old_token = FALSE; - lsass_token = NULL; - old_token = NULL; - - /* Call supplied function. */ - ret = Func(Arg); - if (ret || GetLastError() != ERROR_PRIVILEGE_NOT_HELD) - goto ret; - - /* - * If function call failed with ERROR_PRIVILEGE_NOT_HELD - * error then it means that the current thread token does not have - * Tcb privilege enabled. Try to enable it. - */ - - if (!LookupPrivilegeValue(NULL, SE_TCB_NAME, &luid_tcb_privilege)) - goto err_privilege_not_held; - - /* - * If the current thread has already Tcb privilege enabled then there - * is some additional unhanded restriction. - */ - if (win32_have_privilege(luid_tcb_privilege)) - goto err_privilege_not_held; - - /* Try to enable Tcb privilege and try function call again. */ - if (win32_enable_privilege(luid_tcb_privilege, &revert_token_tcb_privilege, &revert_only_tcb_privilege)) - { - ret = Func(Arg); - win32_revert_privilege(luid_tcb_privilege, revert_token_tcb_privilege, revert_only_tcb_privilege); - goto ret; - } - - /* - * If enabling of Tcb privilege failed then it means that current thread - * does not have this privilege. But current process may have it. So try it - * again with primary process access token. - */ - - /* - * If system supports Impersonate privilege (Windows 2000 SP4 or higher) then - * all future actions in this function require this Impersonate privilege. - * So try to enable it in case it is currently disabled. - */ - if (LookupPrivilegeValue(NULL, SE_IMPERSONATE_NAME, &luid_impersonate_privilege) && - !win32_have_privilege(luid_impersonate_privilege)) - { - /* - * If current thread does not have Impersonate privilege enabled - * then first try to enable it just for the current thread. If - * it is not possible to enable it just for the current thread - * then try it to enable globally for whole process (which - * affects all process threads). Both actions will be reverted - * at the end of this function. - */ - if (win32_enable_privilege(luid_impersonate_privilege, &revert_token_impersonate_privilege, &revert_only_impersonate_privilege)) - { - impersonate_privilege_enabled = TRUE; - } - else if (win32_enable_privilege(luid_impersonate_privilege, NULL, NULL)) - { - impersonate_privilege_enabled = TRUE; - revert_token_impersonate_privilege = NULL; - revert_only_impersonate_privilege = TRUE; - } - else - { - goto err_privilege_not_held; - } - - /* - * Now when Impersonate privilege is enabled, try to enable Tcb - * privilege again. Enabling other privileges for the current - * thread requires Impersonate privilege, so enabling Tcb again - * could now pass. - */ - if (win32_enable_privilege(luid_tcb_privilege, &revert_token_tcb_privilege, &revert_only_tcb_privilege)) - { - ret = Func(Arg); - win32_revert_privilege(luid_tcb_privilege, revert_token_tcb_privilege, revert_only_tcb_privilege); - goto ret; - } - } - - /* - * If enabling Tcb privilege failed then it means that the current - * thread access token does not have this privilege or does not - * have permission to adjust privileges. - * - * Try to use more privileged token from Local Security Authority - * Subsystem Service process (lsass.exe) which has Tcb privilege. - * Retrieving this more privileged token is possible for local - * administrators (unless it was disabled by local administrators). - */ - - lsass_process = win32_find_and_open_process_for_query("lsass.exe"); - if (!lsass_process) - goto err_privilege_not_held; - - /* - * Open primary lsass.exe process access token with query and duplicate - * rights. Just these two rights are required for impersonating other - * primary process token (impersonate right is really not required!). - */ - lsass_token = win32_open_process_token_with_rights(lsass_process, TOKEN_QUERY | TOKEN_DUPLICATE); - - CloseHandle(lsass_process); - - if (!lsass_token) - goto err_privilege_not_held; - - /* - * After successful open of the primary lsass.exe process access token, - * assign its copy for the current thread. - */ - if (!win32_change_token(lsass_token, &old_token)) - goto err_privilege_not_held; - - revert_to_old_token = TRUE; - - ret = Func(Arg); - if (ret || GetLastError() != ERROR_PRIVILEGE_NOT_HELD) - goto ret; - - /* - * Now current thread is not using primary process token anymore - * but is using custom access token. There is no need to revert - * enabled Tcb privilege as the whole custom access token would - * be reverted. So there is no need to setup revert method for - * enabling privilege. - */ - if (win32_have_privilege(luid_tcb_privilege) || - !win32_enable_privilege(luid_tcb_privilege, NULL, NULL)) - goto err_privilege_not_held; - - ret = Func(Arg); - goto ret; - -err_privilege_not_held: - SetLastError(ERROR_PRIVILEGE_NOT_HELD); - ret = FALSE; - goto ret; - -ret: - if (revert_to_old_token) - win32_revert_to_token(old_token); - - if (impersonate_privilege_enabled) - win32_revert_privilege(luid_impersonate_privilege, revert_token_impersonate_privilege, revert_only_impersonate_privilege); - - if (lsass_token) - CloseHandle(lsass_token); - - return ret; -} - /* * ProcessUserModeIOPL is syscall for NT kernel to change x86 IOPL * of the current running process to 3. @@ -365,7 +176,7 @@ SetProcessUserModeIOPL(VOID) Arg[1] = (LPVOID)GetProcAddress(ntdll, "RtlNtStatusToDosError"); /* Call ProcessUserModeIOPL with Tcb privilege. */ - ret = CallFuncWithTcbPrivilege(SetProcessUserModeIOPLFunc, (LPVOID)&Arg); + ret = win32_call_func_with_tcb_privilege(SetProcessUserModeIOPLFunc, (LPVOID)&Arg); FreeLibrary(ntdll); diff --git a/lib/win32-helpers.c b/lib/win32-helpers.c index 29ce5d8..bb3d8a0 100644 --- a/lib/win32-helpers.c +++ b/lib/win32-helpers.c @@ -19,6 +19,11 @@ #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 @@ -981,3 +986,187 @@ win32_open_process_token_with_rights(HANDLE process, DWORD rights) */ return NULL; } + +/* + * Call supplied function with its argument and if it fails with + * ERROR_PRIVILEGE_NOT_HELD then try to enable Tcb privilege and + * call function with its argument again. + */ +BOOL +win32_call_func_with_tcb_privilege(BOOL (*function)(LPVOID), LPVOID argument) +{ + LUID luid_tcb_privilege; + LUID luid_impersonate_privilege; + + HANDLE revert_token_tcb_privilege; + BOOL revert_only_tcb_privilege; + + HANDLE revert_token_impersonate_privilege; + BOOL revert_only_impersonate_privilege; + + BOOL impersonate_privilege_enabled; + + BOOL revert_to_old_token; + HANDLE old_token; + + HANDLE lsass_process; + HANDLE lsass_token; + + BOOL ret; + + impersonate_privilege_enabled = FALSE; + revert_to_old_token = FALSE; + lsass_token = NULL; + old_token = NULL; + + /* Call supplied function. */ + ret = function(argument); + if (ret || GetLastError() != ERROR_PRIVILEGE_NOT_HELD) + goto ret; + + /* + * If function call failed with ERROR_PRIVILEGE_NOT_HELD + * error then it means that the current thread token does not have + * Tcb privilege enabled. Try to enable it. + */ + + if (!LookupPrivilegeValue(NULL, SE_TCB_NAME, &luid_tcb_privilege)) + goto err_privilege_not_held; + + /* + * If the current thread has already Tcb privilege enabled then there + * is some additional unhanded restriction. + */ + if (win32_have_privilege(luid_tcb_privilege)) + goto err_privilege_not_held; + + /* Try to enable Tcb privilege and try function call again. */ + if (win32_enable_privilege(luid_tcb_privilege, &revert_token_tcb_privilege, &revert_only_tcb_privilege)) + { + ret = function(argument); + win32_revert_privilege(luid_tcb_privilege, revert_token_tcb_privilege, revert_only_tcb_privilege); + goto ret; + } + + /* + * If enabling of Tcb privilege failed then it means that current thread + * does not have this privilege. But current process may have it. So try it + * again with primary process access token. + */ + + /* + * If system supports Impersonate privilege (Windows 2000 SP4 or higher) then + * all future actions in this function require this Impersonate privilege. + * So try to enable it in case it is currently disabled. + */ + if (LookupPrivilegeValue(NULL, SE_IMPERSONATE_NAME, &luid_impersonate_privilege) && + !win32_have_privilege(luid_impersonate_privilege)) + { + /* + * If current thread does not have Impersonate privilege enabled + * then first try to enable it just for the current thread. If + * it is not possible to enable it just for the current thread + * then try it to enable globally for whole process (which + * affects all process threads). Both actions will be reverted + * at the end of this function. + */ + if (win32_enable_privilege(luid_impersonate_privilege, &revert_token_impersonate_privilege, &revert_only_impersonate_privilege)) + { + impersonate_privilege_enabled = TRUE; + } + else if (win32_enable_privilege(luid_impersonate_privilege, NULL, NULL)) + { + impersonate_privilege_enabled = TRUE; + revert_token_impersonate_privilege = NULL; + revert_only_impersonate_privilege = TRUE; + } + else + { + goto err_privilege_not_held; + } + + /* + * Now when Impersonate privilege is enabled, try to enable Tcb + * privilege again. Enabling other privileges for the current + * thread requires Impersonate privilege, so enabling Tcb again + * could now pass. + */ + if (win32_enable_privilege(luid_tcb_privilege, &revert_token_tcb_privilege, &revert_only_tcb_privilege)) + { + ret = function(argument); + win32_revert_privilege(luid_tcb_privilege, revert_token_tcb_privilege, revert_only_tcb_privilege); + goto ret; + } + } + + /* + * If enabling Tcb privilege failed then it means that the current + * thread access token does not have this privilege or does not + * have permission to adjust privileges. + * + * Try to use more privileged token from Local Security Authority + * Subsystem Service process (lsass.exe) which has Tcb privilege. + * Retrieving this more privileged token is possible for local + * administrators (unless it was disabled by local administrators). + */ + + lsass_process = win32_find_and_open_process_for_query("lsass.exe"); + if (!lsass_process) + goto err_privilege_not_held; + + /* + * Open primary lsass.exe process access token with query and duplicate + * rights. Just these two rights are required for impersonating other + * primary process token (impersonate right is really not required!). + */ + lsass_token = win32_open_process_token_with_rights(lsass_process, TOKEN_QUERY | TOKEN_DUPLICATE); + + CloseHandle(lsass_process); + + if (!lsass_token) + goto err_privilege_not_held; + + /* + * After successful open of the primary lsass.exe process access token, + * assign its copy for the current thread. + */ + if (!win32_change_token(lsass_token, &old_token)) + goto err_privilege_not_held; + + revert_to_old_token = TRUE; + + ret = function(argument); + if (ret || GetLastError() != ERROR_PRIVILEGE_NOT_HELD) + goto ret; + + /* + * Now current thread is not using primary process token anymore + * but is using custom access token. There is no need to revert + * enabled Tcb privilege as the whole custom access token would + * be reverted. So there is no need to setup revert method for + * enabling privilege. + */ + if (win32_have_privilege(luid_tcb_privilege) || + !win32_enable_privilege(luid_tcb_privilege, NULL, NULL)) + goto err_privilege_not_held; + + ret = function(argument); + goto ret; + +err_privilege_not_held: + SetLastError(ERROR_PRIVILEGE_NOT_HELD); + ret = FALSE; + goto ret; + +ret: + if (revert_to_old_token) + win32_revert_to_token(old_token); + + if (impersonate_privilege_enabled) + win32_revert_privilege(luid_impersonate_privilege, revert_token_impersonate_privilege, revert_only_impersonate_privilege); + + if (lsass_token) + CloseHandle(lsass_token); + + return ret; +} diff --git a/lib/win32-helpers.h b/lib/win32-helpers.h index e5f4445..c415439 100644 --- a/lib/win32-helpers.h +++ b/lib/win32-helpers.h @@ -10,3 +10,4 @@ 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); +BOOL win32_call_func_with_tcb_privilege(BOOL (*function)(LPVOID), LPVOID argument); -- cgit 1.2.3-korg