主机安全技术剖析- Windows本地提权(应用篇)

主机安全技术剖析- Windows本地提权(应用篇)

Windows的提权是在攻防对抗中绕不开的话题, 目前长亭牧云(CloudWalker)主机安全管理平台已支持通用性的windows提权检测 ,更多交流或者投递简历,欢迎联系邮箱: jingyuan.chen@chaitin.com

长亭科技「主机安全技术专栏」已开辟2期内容分享Windows提权。本期是应用篇,从权限等级角度切入,介绍两类Win提权姿势:从常规用户到管理员用户、从服务账户到SYSTEM,分享近年国内外最为火热和范围最广的提权方式。 应用篇没有前后阅读顺序,读者可根据自身需求选择阅读内容。

了解Windows操作系统的本地提权手段原理以及方式,指路 基础概念篇


从常规用户到管理员账户(BypassUAC)


这一方面的实现已经是经久不衰的话题,这里笔者要讨论的可能也猜到了,就是 BypassUAC ,当然在这里可能不只有BypassUAC实现了从常规用户到管理员用户,但目前的主流手段仍然是BypassUAC。

UAC是微软MicrosoftWindowsVista以后版本引入的一种安全机制。其原理是通知用户是否对应用程序使用硬盘驱动器和系统文件授权,以达到帮助阻止恶意程序(有时也称为“恶意软件”)损坏系统的效果。

通过UAC,应用程序和任务可始终在非管理员账户的安全上下文中运行,除非管理员特别授予管理员级别的系统访问权限。UAC可以阻止未经授权的应用程序自动进行安装,并防止无意中更改系统设置。

下图清晰描述了如何根据是否启用UAC以及应用程序是否具有UAC清单来运行应用。

在开启了UAC之后,如果用户是标准用户, Windows会给用户分配一个标准Access Token而如果用户以管理员权限登录,会生成两份访问令牌,一份是完整的管理员访问令牌(Full Access Token),一份是标准用户令牌,令牌相关知识已经在前文提到,这里就不再过多赘述。

在绝大多数BypassUAC的实现中,笔者认为利用方式主要可以分为两大类:

  1. 各类UAC白名单程序的DLL劫持 (Dll Hijack)
  2. 各类提升权限的COM接口利用 (Elevated COM interface)

这里首先以第二种方式为例,结合实际BypassUAC组件来分析如何从常规用户到管理员账户进行提权。

UAC的逆向分析

首先我们需要知道的是在任务管理器中使用管理员身份启动的进程的父进程是explorer,但是在explorer中KERNELBASE!CreateProcessW位置下断点,使用管理员权限运行程序,并不会断下,而正常启动程序则可以正常断下来,这说明elevated的程序很有可能并非是由explorer拉起的。

而explorer本身也并非一个SYSTEM权限的进程,如果提权过程是由explorer过程发起,因此也不可能达到提权的目的。为此我们先在KERNELBASE!CreateProcessW下入断点随后正常启动一个程序,查看调用栈分析:

由文件名可知该函数在windows_storage.dll中,进行反编译查看该函数的实现过程:

发现CInvokeCreateProcessVerb::CallCreateProcess实际上会去调用AicLaunchAdminProcess。

我们跟进这个函数:

可以看到这里实际上进行了RPC的通信,我们根据绑定句柄的UUID值可以找到相应的接口,这里使用Rpcview进行查看:

其实我们在服务中也可以看到appinfo的服务,对应的描述和UAC的实现非常类似。

这里得到了对应接口的DLL为appinfo.dll,我们拖入到IDA进行分析该DLL:

我们发现在该dll中的导出函数多以使用RPCRT4较多:

  • RPC Functions(Remote Procedure Call)使得一个程序可以调用另一计算机的子程序
  • 本地过程调用(LPC,Local Procedure Call)则是在本机进程间进行通讯。

而在之前的了解中已经知道在UAC验证中是使用AiLaunchAdminProcess这个API实现的,根据接口地址找到一个对应函数RAiLaunchAdminProcess,在该函数的实现中找到了AiLaunchAdminProcess:

其中封装了CreateProcessAsUser函数来进行提权操作,因此其实在整个UAC提升权限的过程中是appinfo服务完成了提升权限的处理。

定位可利用的BypassUAC

到这里当然还没有结束,我们知道在UAC过程中会弹出认证框,因此我们还需要对整个弹窗流程进行分析:

弹窗的整体流程是:
RAiLaunchAdminProcess -> AiCheckLUA -> AiLaunchConsentUI

其中根据名称很容易想到AiLaunchConsentUI应该就是实现弹窗的过程,因此我们再继续跟进该函数进行分析:

swprintf_s(Buffer, 0x3Dui64, L"consent.exe %u %u %p", CurrentProcessId, phkResult, a2);

然后调用AiLaunchProcess来启动consent.exe,也就是真正绘制uac窗口的程序:

由此我们得知consent.exe程序是用来绘制uac窗口的程序,因此将该程序进行逆向分析,通过分析解决如下问题:

  • 在consent绘制的uac窗口上,我们可以看到要进行权限提升的程序的路径,命令行等等相关信息,consent是如何获取这些信息的?

consent的命令行中传入了父进程的pid(appinfo服务的进程pid),一个结构体长度以及一个指向结构体的指针,随后consent调用NtReadVirtualMemory从父进程的内存中读取结构体的内容,这个结构体中就包含了需要特权提升的进程信息。

  • consent程序如何将用户的操作反馈回给appinfo服务进程?

同样通过调用NtWriteVirtualMemory写内存的方式从而传递到appinfo服务的内存当中。

跟进发现里面对COM组件进行了判断,判断依据主要是COM组件的Elevation属性:

继续返回到appinfo.dll中查看逻辑,我们已经知道BypassUAC最主要也就是两种方式:

  1. 各类UAC白名单程序的DLL劫持(Dll Hijack)
  2. 各类提升权限的COM接口利用(Elevated COM interface)

找到了对应判断第一种的函数AiIsEXESafeToAutoApprove:

继续跟进该函数:

此时得到了官方支持的具有autoElevate的可执行文件名单,也就是UAC白名单。

UAC是如何进行权限提升的

谈到这里,可能还是得回到consent程序中来看,其实整个UAC的过程就是appinfo和consent横跳的过程,接CuiCheckElevationAutoApprovalMedium判断完成之后通过LABLE_78调用NtQueryInformationToken来获取一个权限提升的令牌。

不过在此之前,判断是通过CuiGetTokenForApp拿到已经是高权限的TokenHandle,由于逆向水平不高,就没有继续跟进该函数进一步底层分析。

之后通过NtDuplicateObject和NtWriteVirtualMemory将提升权限后的令牌写回至appinfo服务进程中。

至此,UAC的整个过程应该也就差不多完成,最后来一个整体的总结。

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

ICMLuaUtil方式提权

COM提升名称(COM Elevation Moniker)技术允许运行在用户账户控制(UAC)下的应用程序用提升权限的方法来激活COM类,以此提升COM接口权限。

我们可以从UACME项目来查看对应的利用方式,该组件对应项目中实现的具体函数名称为:ucmCMLuaUtilShellExecMethod

  • UACME项目到目前为止总结了60多种绕过UAC的方式,并且列出具备auto-elevate能力的UAC白名单程序或接口。

该函数的源码如下:

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);
        // Potential fix check.
        if (supIsConsentApprovedInterface(T_CLSID_CMSTPLUA, &bApprove)) {
            if (bApprove == FALSE) {
                MethodResult = STATUS_NOINTERFACE;
                break;
        r = ucmAllocateElevatedObject(
            T_CLSID_CMSTPLUA,
            &IID_ICMLuaUtil,
            CLSCTX_LOCAL_SERVER,
            (void**)&CMLuaUtil);
        if (r != S_OK)
            break;
        if (CMLuaUtil == NULL) {
            r = E_OUTOFMEMORY;
            break;
        r = CMLuaUtil->lpVtbl->ShellExec(CMLuaUtil,
            lpszExecutable,
            NULL,
            NULL,
            SEE_MASK_DEFAULT,
            SW_SHOW);
        if (SUCCEEDED(r))
            MethodResult = STATUS_SUCCESS;
    } while (FALSE);
    if (CMLuaUtil != NULL) {
        CMLuaUtil->lpVtbl->Release(CMLuaUtil);
    if (hr_init == S_OK)
        CoUninitialize();
    return MethodResult;
}

结合源码可知这里利用的是CMSTPLUA组件中ICMLuaUtil接口,来搜索对应的CLSID,对应{3E5FC7F9-9A51-4367-9063-A120244FBEC7}。

在前文我们已经了解UAC的整个过程,知道在consent程序会调用CuiIsCOMClassAutoApprovable函数判断是否存在自动权限提升的COM组件,其中判断的标志就是搜寻elevation属性中是否为auto Approval。

而该组件是具有该属性的,因此如果想要利用COM组件提权,必须满足的条件之一就是 Elevation属性中的Enabled跟Auto Approval为True。

这里我们使用的是ICMLuaUtil接口,因此继续分析cmlua.dll。

我们主要来看该DLL的虚函数表是否有能够利用的函数。

函数原型如下:

__int64 __fastcall CCMLuaUtil::ShellExec(
        CCMLuaUtil *this,
        const unsigned __int16 *a2,
        const unsigned __int16 *a3,
        const unsigned __int16 *a4,
        unsigned int a5,
        unsigned int a6)
    pExecInfo.lpFile = a2;
    pExecInfo.lpParameters = a3;
    pExecInfo.lpDirectory = a4;
    pExecInfo.fMask = a5;
    pExecInfo.nShow = a6;

因此当我们创建并实例化该接口时,调用接口的ShellExec方法创建指定进程从而BypassUAC。

这里我们通过C#写了一个利用该组件提权的程序,其核心代码如下:

当编译完成运行时却发现程序依然还是会弹出UAC框进行验证,这是因为如果执行COM提升名称(COM Elevation Moniker)代码的程序身份是不可信的,则会触发UAC弹窗,这里我们的程序并没有经过验证。

默认能够绕过UAC的文件或者程序需要满足如下三个条件:

  • 程序配置为自动提升权限,以管理员权限执行
  • 程序包含签名
  • 从受信任的目录(c:\windows\system32)执行

因此我们要利用系统的可信进程去进行调用,可以选择的有rundll32.exe、explorer.exe等,只需要把创建COM组件的代码以及执行你想执行的命令的代码,放到可信任进程里面去执行,这样就可以BypassUAC。

对应的第一种方式就是生成DLL文件,将实现都放在DLLmain中,调用rundll32.exe或者cmstp.exe来执行该DLL文件,不过考虑到会有文件落地,并且rundll32.exe本身已经被很多AV监控,因此该方法不是首选方法。

第二种方法引出另一个技术,叫MasqueradePEB。

进程的信息,包括命令行参数、图像位置、加载的模块等存储在进程控制块中,并且该结构是可以在用户空间访问和修改的,通过修改PEB进程控制块来欺骗系统认为该进程是一个合法进程。

  • 注意在为自己的进程修改进程控制块过程中不需要SeDebugPrivilege权限

而UAC在判断系统进程是否可信,判断依据是PEB结构,因此在使用COM组件提权之前我们将进程信息伪装成可信程序,例如c:\windows\explorer.exe等就能够BypassUAC。

当调用PEBMasq.MasqueradePEB伪装成explorer.exe后。

我们执行该程序发现现在并不会弹出UAC框并且成功获得管理员权限的cmd进程:

利用白名单程序实现BypassUAC

利用白名单程序的本质实际上是劫持注册表,这种方法主要是通过寻找autoElevated属性为true的程序,修改其注册表\shell\open\command的值,改成我们想要执行的paylaod,在该值中指明的字段会在这类程序运行时自动执行,类似于默认程序打开,当你以后运行该程序时,这个command命令都会自动执行。

这里我们以Windows 10中Fodhelper.exe为例,该程序是一个autoelevate程序。

使用ProcessMonitor监控打开该进程后该进程的相关操作:

启动fodhelper.exe时,会在注册表中执行以下检查:

HKCU:\Software\Classes\ms-settings\shell\open\commandHKCU:\Software\Classes\ms-settings\shell\open\command\DelegateExecuteHKCU:\Software\Classes\ms-settings\shell\open\command\(default)

由于这些注册表项不存在,用户可以在注册表中创建此结构,以便操纵fodhelper以绕过用户账户控制 (UAC) 执行具有更高权限的命令。

当再次运行fodhelper后将执行命令并打开提升的PowerShell会话:

但是在利用过程中值得注意的是需要确保DelegateExecute项是存在的,否则也无法成功BypassUAC。

如何寻找具有autoPriv的白名单

其实这一类程序都满足一定的条件:

  1. 可执行文件必须经过Windows Publisher的数字签名,Windows Publisher是用于对Windows附带的所有代码进行签名的证书(仅由 Microsoft进行签名是不够的,因此Windows未附带的Microsoft软件不包括在内);
  2. 可执行文件必须位于其中一个为数不多的“安全”目录中。安全目录是指标准用户无法修改的目录,并且它们包括 %SystemRoot%\System32(例如,\Windows\System32)及其大多数子目录、%SystemRoot%\Ehome,以及 %ProgramFiles%下的少许目录(其中包括Windows Defender和Windows notepad)。

这里可以直接使用命令:

strings.exe -s *.exe | findstr /i "autoElevate"

来获取manifest文件中声明autoElevate的程序,也可以通过UACME项目中uacinfo.exe来获得相关程序信息。

从服务账户到SYSTEM

之所以需要单独划分为从服务账户到SYSTEM权限,主要是因为在服务账户中通常具有SeImpersonatePrivilege权限,即使是IIS、Sqlserver用户等也同样具有该权限,在渗透测试中从web端或者数据库来打点也是极为常见的,因此如何进行存在SeImpersonatePrivilege权限的提权也是一直研究的热点。

以下用户拥有SeImpersonatePrivilege权限:

  • 本地管理员账户(不包括管理员组普通账户)和本地服务帐户
  • 由SCM启动的服务

服务账户在Windows权限模型中本身就拥有很高的权限,所以微软不认为这是一个漏洞。

讨论到这里,首先需要介绍的就是目前使用最广泛的Potato系列,其实所有的Potato系列本质上都是relay,如果是远程提权则是relay,用于本地也可以称为是reflection。

HotPotato

2016年1月中旬,来自FoxGlove Security安全团队的breenmachine在博客中介绍了一种被称为 Hot Potato 的漏洞利用技术。在默认配置下,Hot Potato能够利用Windows操作系统的已知缺陷来获取本地计算机的最高控制权限,受影响的操作系统包括Windows 7/8/10 和 Windows Server 2008/2012。

有关详细的原理讲述可以参考原作者的文章,在这里的核心思想是通过NTLM中继(基于HTTP->SMB中继)和NBNS欺骗。

在此之前我们需要知道windows解析域名的顺序是:

HostsDNS (cache / server) LLMNRNBNS

LLMNR

LLMNR 是一种基于协议域名系统(DNS)数据包的格式,使得两者的IPv4和IPv6的主机进行名称解析为同一本地链路上的主机,因此也称作 多播DNS 。监听的端口为UDP/5355,支持IPv4和IPv6 ,并且在Linux上也实现了此协议。其解析名称的特点为端到端,IPv4 的广播地址为224.0.0.252,IPv6的广播地址为FF02:0:0:0:0:0:1:3或FF02::1:3。

当局域网中的DNS服务器不可用时,DNS客户端会使用LLMNR本地链路多播名称解析来解析本地网段上的主机的名称,直到网络连接恢复正常为止。

LLMNR进行名称解析的过程为:

  1. 检查本地 NetBIOS 缓存
  2. 如果缓存中没有则会向当前子网域发送广播
  3. 当前子网域的其他主机收到并检查广播包,如果没有主机响应则请求失败

也就是说LLMNR并不需要一个服务器,而是采用广播包的形式,去询问DNS,跟ARP很像,因此也存在类似arp投毒等问题的出现。

举个简单的例子:当我们通过net use尝试去建立一个不存在IPC链接或者是尝试和攻击机建立IPC链接的时候:

当本机在Hosts文件里面没有找到,通过DNS解析失败。就会通过LLMNR协议进行广播,前文已经说过LLMNR的广播地址对应的就是224.0.0.252。

NBNS

NBNS全称是NetBIOS Name Service,NetBIOS 协议进行名称解析的过程如下:

  1. 检查本地 NetBIOS 缓存;
  2. 如果缓存中没有请求的名称且已配置了 WINS 服务器,接下来则会向 WINS 服务器发出请求;
  3. 如果没有配置 WINS 服务器或 WINS 服务器无响应则会向当前子网域发送广播;
  4. 如果发送广播后无任何主机响应则会读取本地的 lmhosts 文件。

需要注意的是这里和ARP类似,会在本地所有主机进行广播,因此当攻击者相应这条广播消息时,发送方便会认为自己查找的目标就是这个响应者。

NBNS包有1个2字节的TXID字段,必须进行请求\响应的匹配,在这里我们假定没有权限进行流量的监听,也就不知道是在哪一个端口进行通信,但由于是通过UDP进行传输,因此可以通过1-65535之间进行泛洪猜测。

前文提到如果网络中有DNS记录,此时就不会用到NBNS协议,作者使用称为UDP端口耗尽的技术来强制系统上的所有DNS查找失败,当请求已经没有可用的UDP资源时,DNS查找失败便会通过NBNS进行广播查询。

设置假的WPAD服务器

wpad全称是Web Proxy Auto-Discovery Protocol,通过让浏览器自动发现代理服务器,定位代理配置文件PAC(在下文也叫做PAC文件或者wpad.dat),下载编译并运行,最终自动使用代理访问网络。

它在本地网络上搜索名为wpad的计算机以找到该文件。然后执行以下步骤:

  1. 如果配置了DHCP服务器,则客户端从DHCP服务器中检索wpad.dat文件(如果成功,则执行步骤4)。
  2. wpad.corpdomain.com 查询被发送到DNS服务器以查找分发Wpad配置的设备。(如果成功,则执行第4步)。
  3. 发送WPAD的LLMNR或NBNS查询(如果成功,请转到第4步,否则无法使用代理)。
  4. 下载wpad.dat并使用它。

在下面的流量捕获中,机器以广播方式发送NBNS数据包,请求wpad.dat:

在 Windows 中,系统默认会通过访问URL wpad/wpad.dat 自动尝试检测网络代理设置配置,并且适用于某些Windows服务,例如Windows更新。

当然是 wpad/wpad.dat 不会存在于所有网络上,因为主机名wpad不一定存在于DNS名称服务器中。然而,正如我们在上面看到的,我们可以使用NBNS欺骗来 伪造主机名。

凭借欺骗NBNS响应的能力,我们可以将 NBNS 欺骗器定位在127.0.0.1。我们用主机WPAD或WPAD.DOMAIN.TLD的NBNS响应数据包淹没目标机器(我们自己的机器),我们说WPAD主机的IP地址为127.0.0.1。

同时,我们在127.0.0.1本地运行一个HTTP服务器。当它收到对 wpad/wpad.dat 的请求时,它会响应如下:

使得所有流量都通过127.0.0.1上运行的服务器重定向。

NTLM中继

NTLM中继是一种众所周知但经常被误解的针对 Windows NTLM 身份验证的攻击。NTLM 协议容易受到中间人攻击。如果攻击者可以欺骗用户尝试使用 NTLM 对其机器进行身份验证,他可以将该身份验证尝试中继到另一台机器。

这种攻击的旧版本让受害者尝试使用带有NTLM身份验证的SMB协议向攻击者进行身份验证。然后攻击者会将这些凭据转发回受害者的计算机,并使用类似“psexec”的技术获得远程访问权限。

微软通过使用已经在进行中的挑战来禁止相同协议的NTLM身份验证来修补这个问题。这意味着从一台主机到其自身的SMB->SMB NTLM中继将不再起作用。然而,跨协议攻击,如HTTP->SMB仍然可以中继成功。

在Potato漏洞利用中,所有HTTP请求都通过302重定向重定向到 http://localhost/GETHASHESxxxxx,其中xxxxx对应一个唯一标识符。请求http://localhost/GETHASHESxxxxx响应NTLM身份验证的 401 请求。

这样我们就可以将所有NTLM凭据中继到本地SMB侦听器以创建运行用户定义命令的新系统服务。

当有问题的HTTP请求来自高权限账户时,例如,当它是来自Windows更新服务的请求时,那么将以“NT AUTHORITY\SYSTEM”权限运行。

最原始的Potato其本质就是一个NTLM Relay,最后附上一张整体的流程图。

局限性

Microsoft通过使用已经在进行中的质询来禁止相同协议的NTLM身份验证来修补此问题 (MS16-075)。这意味着从一台主机到其自身的SMB->SMB NTLM中继将不再起作用。MS16-077 WPAD名称解析将不使用NetBIOS 并且在请求 PAC 文件时不发送凭据,因此WAPD Attack已修补。

Rotten Potato

相比于Hotpotato利用NTLM Relay中继到SMB,最后调用svcctl.CreateServiceW创建指定的服务,Rotten Potato主要利用远程过程调用(RPC)通过CoGetInstanceFromIStorage进行NTLM中继,在本地协商得到NT AUTHORITY\SYSTEM账户的安全令牌,在模拟安全令牌后调用CreateProcessWithToken等API来使用指定的令牌来创建进程。

public static void BootstrapComMarshal()
IStorage stg = ComUtils.CreateStorage();
// Use a known local system service COM server, in this cast BITSv1
Guid clsid = new Guid("4991d34b-80a1-4291-83b6-3328366b9097");
TestClass c = new TestClass(stg, String.Format("{0}[{1}]", "127.0.0.1", 6666)); // ip and port
MULTI_QI[] qis = new MULTI_QI[1];
qis[0].pIID = ComUtils.IID_IUnknownPtr;
qis[0].pItf = null;
qis[0].hr = 0;
CoGetInstanceFromIStorage(null, ref clsid, null, CLSCTX.CLSCTX_LOCAL_SERVER, c, 1,qis);