【UAC】Windows UAC 原理浅析

UAC理解

简而言之
用户账户控制 (User Account Control) 是Windows Vista (及更高版本操作系统)通过弹框进一步让用户确认是否授权当前可执行文件来达到阻止恶意程序的目的。

在这里插入图片描述
从图中看可以理解为:
想要获得管理员权限:

  1. 进程已经拥有管理员权限。
  2. 进程被用户允许通过管理员权限允许。

UAC 的实现:

ACL: windows 中的所有资源都有ACL。给各个程序划定了权限。

在开启了UAC之后,如果用户是标准用户,Windows 会给用户分配一个标准Access Token

如果用户以管理员权限登录,会生成两份访问令牌。一份是完整的管理员访问令牌。(full access token),一份是标准用户令牌。

一般情况下会以标准用户权限启动Explore.exe 进程。如果用户同意,则赋予完整管理员权限访问令牌进行操作。

总结如下:

当一个管理员登录到计算机时,Windows Vista的处理方式却与先前版本的Windows有所不同。虽然系统创建了一个新的登录会话,但却为该登录会话创建了两个不同的访问令牌,而不是先前版本中的一个。第一个访问令牌提供了管理员所有的许可和权限,而第二个就是所谓的“受限访问令牌”,有时候也叫做“过滤访问令牌(filtered token)",该令牌提供了少得多的许可和权限。实际上,受限访问令牌所提供的访问权限和标准用户的令牌没什么区别。然后系统将使用该受限访间令牌创建 shell应用程序。这也就意味着即使用户是以管理员身份登录的,其默认的运行程序许可和权限仍为标准用户。

在这里插入图片描述

可以使用whoami /priv 查看当前权限。
(左边是普通权限,右边是管理员权限)

在这里插入图片描述

UAC架构

如图:
在这里插入图片描述

UAC虚拟化

UAC虚拟化也被称为重定向。在权限判定的过程中,如果用户的权限没有达到程序所需的权限,UAC就会重定向到该文件夹。

例如: 如果程序试图写入到C:\Program Files\Contoso\Settings.ini 目录下,但用户没有写入权限。这个写操作就会被重定向为:
C:\Users\Username\AppData\Local\VirtualStore\Program Files\contoso\settings.ini

虚拟化被分为文件虚拟化和注册表虚拟化。
在这里插入图片描述
需要注意的是,在以下条件下,虚拟化不可用

64 位程序
非交互式程序
模拟令牌的进程 (Processes that impersonate)
内核模式调用方
带有 requestedExecutionLevel(请求执行等级)的可执行程序

文件虚拟化

在这里插入图片描述

注册表虚拟化:

在这里插入图片描述

UAC原理初探

使用procexp 查看进程,然后用管理员权限运行一个文件会弹出UAC 提示框,会发现多出一个进程consent.exe
在这里插入图片描述
进程中有一个dll appinfo。 而UAC的主要实现文件就在于appinfo.dll 这个文件
对该appinfo.dll 逆向分析:
使用很多RPC Functions ,使得一个程序可以调用另一计算机的字程序。
本地过程调用(LPC) 则是在本机进程间进行通讯。

AppInfo是一个本地RPC服务,其接口ID为201ef99a-7fa0-444c-9399-19ba84f12a1a。该RPC服务中的RAiLaunchAdminProcess函数用于在权限不一致需要向上提权时进行UAC路由分发,具有以高权限启动进程的功能。当被启动的程序属于系统目录中的白名单进程时可避免弹窗以管理员权限启动。

查看其导出函数,会发现其使用的RPCRT4 Library较多。
这个dll就是通过注册LPC接口进行调用
在这里插入图片描述
RPC Functions (Remote Procedure Call),使得一个程序可以调用另一个计算机子程序。
LPC本地过程调用, 则是在本机进行进程间进行通讯。
这个dll就是通过注册LPC接口进行调用。

本地过程调用通常也被称为轻量过程调用或者本地进程间通讯,通过这一方式,同一计算机上的进程可以进行通信。在多任务操作系统中,它能使同时允许的任务能相互回话,这些任务共享内存空间。

以RpcServerRegisterIfEx 为例。这个函数是用来注册LPC/RPC的接口的。 到底注册了什么接口呢?
先了解一下Windows如何创建进程。(下一节探讨)

而我们知道AicLaunchAdminProcess 是可以创建管理员进程的。

在这里插入图片描述

UAC进程启动原理:

我们知道,我们在cmd中或者shell中双击一个程序,运行程序,都是通过ShellExecute来执行的。那么如果执行一个需要管理权限的程序,就会弹出UAC安全框
,其中的过程是怎样的呢

可以跟踪执行堆栈:

# ChildEBP RetAddr  Args to Child              
00 001febb8 76ff6a04 752069dc 00000002 001fec0c ntdll!KiFastSystemCallRet
01 001febbc 752069dc 00000002 001fec0c 00000001 ntdll!NtWaitForMultipleObjects+0xc
02 001fec58 7641bc8e 001fec0c 001fec80 00000000 KERNELBASE!WaitForMultipleObjectsEx+0x100
03 001feca0 76ef62f9 00000002 7ffdd000 00000000 kernel32!WaitForMultipleObjectsExImplementation+0xe0
04 001fecf4 76ef37f7 00000024 001fedd0 ffffffff user32!RealMsgWaitForMultipleObjectsEx+0x13c
05 001fed10 7574eacc 00000001 001fedd0 00000000 user32!MsgWaitForMultipleObjects+0x1f
06 001fed64 7574ea55 001fedac ffffffff 00000000 shell32!AicpMsgWaitForCompletion+0x3c
07 001fed84 7574e927 001fedac 00000001 ffffffff shell32!AicpAsyncFinishCall+0x33
08 001ff0b4 7574e60f 004285a4 003d4c38 00000000 shell32!AicLaunchAdminProcess+0x39c
09 001ff1a8 75692bda 000701be 00000000 004285a4 shell32!_SHCreateProcess+0x331               *****************√
0a 001ff1fc 756853c5 00000001 00426818 00000001 shell32!CExecuteApplication::_CreateProcess+0xfc
0b 001ff20c 75685379 00426818 00426820 00000001 shell32!CExecuteApplication::_TryCreateProcess+0x2e
0c 001ff21c 756847d1 00426820 00403170 001ff24c shell32!CExecuteApplication::_DoApplication+0x48
0d 001ff22c 7569f6b1 00426820 00403170 00426818 shell32!CExecuteApplication::Execute+0x33
0e 001ff24c 75684a1c 00426820 00401bfc 00403178 shell32!CExecuteAssociation::_DoCommand+0x88
0f 001ff270 7569f733 00000000 003e11a8 762d3472 shell32!CExecuteAssociation::_TryApplication+0x41
10 001ff290 75684b63 00403178 003d6248 003d6248 shell32!CExecuteAssociation::Execute+0x5f
11 001ff2bc 75692260 003e11a8 003d6248 001ff398 shell32!CShellExecute::_ExecuteAssoc+0x8c
12 001ff2d8 75686772 00000000 00008140 003d6248 shell32!CShellExecute::_DoExecute+0x89
13 001ff2ec 75691efa 001ff398 00008140 001ff398 shell32!CShellExecute::ExecuteNormal+0x8b
14 001ff300 75691e88 001ff398 0000003c 4a135260 shell32!ShellExecuteNormal+0x33
15 001ff318 4a12580f 001ff398 2aeaa614 003cc870 shell32!ShellExecuteExW+0x62

通过跟踪,会发现最终请求到了shell32!_SHCreateProcess。查看这个请求的反汇编代码。 在这里可以看到调用了CreateProcessAsUserW API。可以简单理解为,修改被UAC进程启动程序的父进程为原程序。

当用户允许一次UAC提权时,AIS服务(AppInfo Service)调用的CreateProcessAsUser() 函数创建进程并且赋予恰当的管理员权限,在理论上说AIS服务(所在的进程)是提权后辣个进程的父进程。

然而,当我们用一些进程查看管理工具 进行查看时,会发现已经被提权的进程,它的父进程是创建它的进程,而不是AIS服务(所在的进程)。

这是因为AIS利用了CreateProcessAsUser() API中的一个新的功能,这里的新功能就是将要被提权的进程的父进程设置成创建该进程的进程,如果我们利用一下该API,就可以把一个进程的父进程设置为任意进程。

关于CreateProcessAsUser

如果是CreateProcessAsUser 的dwCreationFlags 的参数被设置为EXTENDED_STARTUPINFO_PRESENT, 这就是有扩展启动信息的结构体,
这里的IpStartupInfo参数需要填好STARTUPEX 结构
这个结构由STARTUOINFO结构和PROC_THREAD_ATTRIBUTE_LIST 指针构成

typedef struct _STARTUPINFOEX {
  STARTUPINFO                 StartupInfo;
  PPROC_THREAD_ATTRIBUTE_LIST lpAttributeList;
} STARTUPINFOEX, *LPSTARTUPINFOEX;

也就是说,调用CreateProcessAsUser 且设置dwCreationFlags参数的值为EXTENDED_STARTUPINFO_PRESENT,此时再将结构体中的PPROC_THREAD_ATTRIBUTE_LIST lpAttributeList 值设置目标父进程的属性。 具体例子在文章最后。

继续看反编译代码

在这里插入图片描述
如果发现” 请求的操作需要提升“ 即用户确定,则使用AicLaunchAdminProcess提权启动。

由此我们知道,只有在explorer 建立管理员进程时,AicLaunchAdminProcess 才会被调用,否则建立普通进程调用调用的是KERNELBASE!CreateProcessInternalW

在以管理员身份运行程序时CInvokeCreateProcessVerb::CallCreateProcess会去调用AicLaunchAdminProcess,而AicLaunchAdminProcess本身并不拉起进程,而是做了rpc通信

在这里插入图片描述
整个过程为:

  1. 使用CreateProcess尝试启动进程。
  2. 如果启动失败,并且发现需要提权。调用AicLaunchAdminProcess,利用UAC启动进程。

AicLaunchAdminProcess 会构造RPC请求,并等待RPC返回结果:

在这里插入图片描述
启动过程就是RPC远程调用的的过程。
在这里插入图片描述
使用如下两种方式等待返回:
在这里插入图片描述

SVCHOST 服务

RPC 远程过程调用的响应进程为C:\Windows\system32\svchost.exe -k netsvcs ,首先启动一个UAC对话框进程。
在这里插入图片描述
如上图 启动consent.exe进程

该进程启动之后,将会在桌面弹框:
在这里插入图片描述
consent.exe 堆栈如下:
在这里插入图片描述
等待consent.exe 验证完成之后,启动真实进程。如下:

在这里插入图片描述

再次总结:
通过以上描述,再进行一次梳理。
我们会发现尽管在explorer中使用管理员身份启动的进程是explorer,但如果在explorer进程中对KERNELBASE!CreateProcessW下断,使用管理员权限运行,也并不会断下。而正常的启动程序可以断下来。

通过调试分析:
CInvokeCreateProcessVerb::CallCreateProcess会去调用AicLaunchAdminProcess,而AicLaunchAdminProcess本身并不拉起进程,而是做了rpc通信,真正拉起权限提升进程的程序并非是explorer
在这里插入图片描述
根据上图中的uuid信息,可以在rpcview找到对应的接口,是一个svchost启的服务。
在这里插入图片描述
从启动命令可以发现为 appinfo, 并且根据rpcview中显示的procedure 地址,可以找到对应的dll,也就是appinfo.dll
在这里插入图片描述
然后根据接口地址找到对应函数RAiLaunchAdminProcess,可以看到最终调用函数AiLaunchProcess,而该函数是对CreateProcessAsUserW的封装。而权限提升的进程最终是由appinfo服务进程拉起来的。

在这里插入图片描述

UAC弹框流程

找到UAC弹框的函数:
以管理员权限打开一个程序,弹出uac窗口之后,断下来,切换到appinfo服务所在的进程,打印所有线程栈。

1: kd> !process 48c 6
........
        THREAD ffffbb86af9b7080  Cid 048c.18b0  Teb: 000000eb9578f000 Win32Thread: ffffbb86b02046c0 WAIT: (UserRequest) UserMode Non-Alertable
            ffffbb86aa520080  ProcessObject
        Not impersonating
        DeviceMap                 ffffa90e43a35600
        Owning Process            ffffbb86add680c0       Image:         svchost.exe
        Attached Process          N/A            Image:         N/A
        Wait Start TickCount      21551          Ticks: 509 (0:00:00:07.953)
        Context Switch Count      3579           IdealProcessor: 1             
        UserTime                  00:00:00.265
        KernelTime                00:00:00.578
        Win32 Start Address ntdll!TppWorkerThread (0x00007ffc51aa20e0)
        Stack Init ffffb90acbaa7c90 Current ffffb90acbaa76a0
        Base ffffb90acbaa8000 Limit ffffb90acbaa2000 Call 0000000000000000
        Priority 9 BasePriority 8 PriorityDecrement 0 IoPriority 2 PagePriority 5
        Child-SP          RetAddr               : Args to Child                                                           : Call Site
        ffffb90a`cbaa76e0 fffff803`1b0e4e60     : ffffbb86`00000008 00000000`ffffffff ffffb90a`00000000 ffffbb86`ae3d2158 : nt!KiSwapContext+0x76
        ffffb90a`cbaa7820 fffff803`1b0e438f     : 00000000`00000009 00000000`00000000 ffffb90a`cbaa79e0 ffffffff`fffffffe : nt!KiSwapThread+0x500
        ffffb90a`cbaa78d0 fffff803`1b0e3c33     : ffff5817`00000000 fffff803`00000000 00000000`00000000 ffffbb86`af9b71c0 : nt!KiCommitThreadWait+0x14f
        ffffb90a`cbaa7970 fffff803`1b4f6531     : ffffbb86`aa520080 fffff803`00000006 ffffb90a`cbaa7b01 ffffb90a`cbaa7b00 : nt!KeWaitForSingleObject+0x233
        ffffb90a`cbaa7a60 fffff803`1b4f65da     : ffffbb86`af9b7080 00000000`00000000 00000000`00000000 00000000`00000000 : nt!ObWaitForSingleObject+0x91
        ffffb90a`cbaa7ac0 fffff803`1b20bbb5     : ffffbb86`af9b0000 00000000`00001000 00000000`00000000 00000000`00000000 : nt!NtWaitForSingleObject+0x6a
        ffffb90a`cbaa7b00 00007ffc`51b2be24     : 00007ffc`4f5926ee 00000000`00000022 00000023`00000004 00000004`00000000 : nt!KiSystemServiceCopyEnd+0x25 (TrapFrame @ ffffb90a`cbaa7b00)
        000000eb`99f7e1f8 00007ffc`4f5926ee     : 00000000`00000022 00000023`00000004 00000004`00000000 00000000`00000024 : ntdll!NtWaitForSingleObject+0x14
        000000eb`99f7e200 00007ffc`38537bf9     : 00000000`00000000 00000000`00000001 000000eb`00000000 00000000`00001c88 : KERNELBASE!WaitForSingleObjectEx+0x8e
        000000eb`99f7e2a0 00007ffc`38537503     : 00000000`00000000 00000220`790095e0 000000eb`00000002 00000000`00000004 : appinfo!AiLaunchConsentUI+0x559
        000000eb`99f7e4c0 00007ffc`38536ba2     : 00000000`00000021 00000000`00000021 00000000`00000000 00000220`7c39e7f8 : appinfo!AiCheckLUA+0x343
        000000eb`99f7e6a0 00007ffc`50772153     : 00000220`7b1f3e00 00000220`7b245df0 00000220`7c39e7f8 00000220`7c39e860 : appinfo!RAiLaunchAdminProcess+0xbe2
        000000eb`99f7ecb0 00007ffc`507da5ea     : 00000220`7b1f3e00 00000220`7b23fae0 00000220`772b1ae0 00007ffc`00000000 : RPCRT4!Invoke+0x73

可以发现, UAC弹框流程为:
RAiLaunchAdminProcess -> AiCheckLUA -> AiLaunchConsentUI

查看函数AiLaunchConsentUI, 发现构造命令行之后会调用AiLaunchProcess 来启动consent.exe 它是真正绘制窗口的程序。
在这里插入图片描述
通过对consent.exe进行逆向
在consent绘制的窗口上,可以看到要进行权限提升的程序的路径,命令行等相关信息。consent是如何获取这些信息 的?
在这里插入图片描述
答案是consent的命令行中传入了父进程的pid(appinfo服务的进程pid),包含一个结构体长度及一个指向结构体的指针,随后consent调用NtReadVirtualMemory从父进程内存中读取结构体内容。这个结构体当中就包含了需要特权提升的进程信息。
在这里插入图片描述
还有一个问题是特权提升的进程最终是由appinfo服务进程拉起的,但是UAC窗口则是consent绘制的,consent如何将用户的操作反馈给appinfo服务进程。
在这里插入图片描述
同意是通过写appinfo进程的内存实现。
通过逆向找到决定是否弹框的关键函数:

在这里插入图片描述
关键代码:
在这里插入图片描述

可以知道consent是否弹框主要由父进程传入结构体确定。
继续回看appinfo
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看到有一些白名单校验,属性校验等。满足这些条件之后,便不会弹框。

令牌权限提升过程

权限提升的过程位于consent中,consent从appinfo服务进程中获取未授权提升的令牌后,调用NtQueryInformationToken 获取一个权限提升的令牌(undocument的用法),随后将这个token写回到appinfo服务进程中,然后再利用这个提升后的令牌创建进程。

在这里插入图片描述
通过NtQueryInformationToken获取权限提升令牌
在这里插入图片描述
如图 将令牌写回到appinfo中

触发UAC方式:

* 配置Windows Update
* 增加或删除用户账户
* 改变用户的账户类型
* 改变UAC设置
* 安装ActiveX
* 安装或移除程序
* 安装设备驱动程序
* 设置家长控制
* 将文件移动或复制到Program Files或Windows目录
* 查看其他用户文件夹

触发流程:

在触发UAC时,系统会创建一个consent.exe进程。该进程用以确定是否创建管理员进程。(根据白名单和用户选择判断)

然后createProcess 请求进程

将要请求的进程cmdline 和进程路径 通过 LPC接口传递给appinfo 的 RAiLuanchAdminProcess函数

该函数首先验证路径是否在白名单中,并将结果传递给consent.exe 进程。

consent.exe进程验证被请求的进程签名和发起者权限是否符合要求,然后弹出UAC弹框

UAC弹框会创建新的安全桌面,屏蔽之前的界面。

这个UAC进程是SYSTEM权限进程,且其他普通进程无法与之进行通信交互。

用户确认之后,调用CreateProcessAsUser 函数以管理员权限启动请求的进程。

例子:

利用 CreateProcessAsUser 伪造父进程。

//一般情况下,创建子进程的进程 就是子进程的父进程。而这部分代码功能: 去指定任何一个有相应权限的进程为新创建进程的父进程


#include <iostream>
#include <Windows.h>
#include <Tlhelp32.h> //因为Tlhelp32.h 中一些宏定义是在windows.h中定义的,所以Tlhelp32.h 要定义在windows.h的后面
#pragma comment(lib, "Advapi32.lib")


int main()
{
/
    LPVOID pAlloc1;
    LPVOID pAlloc2;
    HANDLE hfile;
    PIMAGE_NT_HEADERS pPeHeader;
    PIMAGE_SECTION_HEADER pSectionHeader;
    int lastError, ReadInfo = 0;
    DWORD BytesRead = 0;
    CONTEXT Context = { 0 };
    Context.ContextFlags = CONTEXT_ALL;


    PROCESSENTRY32 pe;
    // explorer.exe的进程ID
    DWORD  pid = 0;


    HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    pe.dwSize = sizeof(PROCESSENTRY32);
    if (!Process32First(hSnapshot, &pe))
        return 0;


    do
    {
        pe.dwSize = sizeof(PROCESSENTRY32);
        if (Process32Next(hSnapshot, &pe) == FALSE)
            break;
        if (wcscmp(pe.szExeFile, L"cmd.exe") == 0)
        {
            pid = pe.th32ProcessID;
            break;
        }


    } while (1);


    CloseHandle(hSnapshot);


    /* 以全部权限打开explorer.exe 进程 */
    HANDLE handle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);


    /* 创建启动信息结构体 */
    STARTUPINFOEXA si;


    /* 初始化结构体 */
    ZeroMemory(&si, sizeof(si));


    /* 设置结构体成员 */
    si.StartupInfo.cb = sizeof(si);


    SIZE_T lpsize = 0;


    /* 用微软规定的特定的函数初始化结构体 */
    InitializeProcThreadAttributeList(NULL, 1, 0, &lpsize);//首先要获取到需要初始化的大小


    char * temp = new char[lpsize];


    /* 转换指针到正确类型 */
    LPPROC_THREAD_ATTRIBUTE_LIST AttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)temp;


    /* 真正为结构体初始化属性参数 */
    InitializeProcThreadAttributeList(AttributeList, 1, 0, &lpsize);//设置AttributeList结构体属性个数以及初始化它的大小


    /* 用已构造的属性结构体更新属性表 */
    if (!UpdateProcThreadAttribute(AttributeList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &handle, sizeof(HANDLE), NULL, NULL))
    {
        //更新AttrubuteList 属性,添加PROC_THREAD_ATTRIBUTE_PARENT_PROCESS属性
        printf("UpdateProcThreadAttribute failed ! (%d).\n", GetLastError());
    }


    /* 移交指针,这里已更换了父进程的属性表是 explorer.exe */
    si.lpAttributeList = AttributeList;


    PROCESS_INFORMATION pi;


    ZeroMemory(&pi, sizeof(pi));
    //当调用下面的api 且createFlags的参数是EXTENDED_STARTUPINFO_PRESENT时,lpStartupInfo就需要有扩展的信息,也就是这条属性:PPROC_THREAD_ATTRIBUTE_LIST lpAttributeList;
    if (CreateProcessAsUserA(NULL, 0, "C:\\Users\\Administrator\\Desktop\\Play.exe", 0, 0, 0, EXTENDED_STARTUPINFO_PRESENT, 0, 0, (LPSTARTUPINFOA)&si, &pi))
    {
        printf("CreateProcessAsUserA success !  \n");
    }
    else
    {
        printf("CreateProcessAsUserA failed ! (%d). \n ", GetLastError());
    }


    /* 处理后事 */
    DeleteProcThreadAttributeList(AttributeList);


    delete temp;


    printf("exit");
    getchar();


    return 0;
}

欢迎各位大佬一起来免费知识星球学习: 一起探究安全原理,探索更多攻击方法。
https://t.zsxq.com/2rjA6yn

REFERENCE

https://citrusice.github.io/posts/uac-reversing/
https://www.i4k.xyz/article/xiangbaohui/103704153
https://www.cnblogs.com/chesky/p/uac_bypass.html

  • 3
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值