Search code examples
c#powershellwindows-servicessession-0-isolation

launch process from Session 0 Isolation


On Windows 8.1 I have a service that starts PowerShell scripts. The service runs as “nt authority\system” in Session 0 Isolation. Any process that I spawn from PowerShell runs as “nt authority\system” in Session 0 Isolation.

I need to run a script that is under a user account out of session 0 and not the system account. I have tried this

Start-Process "$PsHome\PowerShell.exe" -Credential $pp -ArgumentList $script  -wait 

and PsExec specifying which session I want with "-I 1" argument.

& PsExec.exe "Install.bat" -i 1 -accepteula -u "domain\user" -p "awesomePassword" -w "startdir" -h

I have tried setting "Allow service to interact with desktop".

I keep getting Access is denied errors when I try and start the process either from PowerShell or from the c# service.

Here is an example exception when I try to escape using c# on the service.

System.ComponentModel.Win32Exception (0x80004005): Access is denied
   at System.Diagnostics.Process.StartWithCreateProcess(ProcessStartInfo startInfo)

How do I escape from session 0?

I can re-write the c# code to start a process under a different user. or I can re-write the called PowerShell script to start another process as a user. No matter what I try, I can't seem to break out of session 0.


Solution

  • Using the example I found at code project I got a partial solution. The example in the link above will launch a process as the user who is running the "winlogon" process. In order to launch a process as the user who is logged in I just changed the process to look for "explorer" instead.

    Here is a snippet of the original code

    // obtain the process id of the winlogon process that is 
    // running within the currently active session
    Process[] processes = Process.GetProcessesByName("winlogon");
    

    I just change the process to look for explorer.

    Process[] processes = Process.GetProcessesByName("explorer");
    

    Now the process launches as domain/me in Session 3 as a user not admin.

    There has to be issues with this approach, such as Remote Desktop, but for what I want this will ultimately do.

    Here is the final code for completeness in case the original link evaporates.

    Here is how to launch it

    // the name of the application to launch
    String applicationName = "cmd.exe";
    
    // launch the application
    ApplicationLoader.PROCESS_INFORMATION procInfo;
    ApplicationLoader.StartProcessAndBypassUAC(applicationName, out procInfo);
    

    Here is the code

    using System;
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    using System.Security;
    
    namespace SuperAwesomeNameSpaceOfJustice
    {
        /// <summary>
        /// Class that allows running applications with full admin rights. In
        /// addition the application launched will bypass the Vista UAC prompt.
        /// </summary>
        public class ApplicationLoader
        {
            #region Structures
    
            [StructLayout(LayoutKind.Sequential)]
            public struct SECURITY_ATTRIBUTES
            {
                public int Length;
                public IntPtr lpSecurityDescriptor;
                public bool bInheritHandle;
            }
    
            [StructLayout(LayoutKind.Sequential)]
            public struct STARTUPINFO
            {
                public int 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;
            }
    
            [StructLayout(LayoutKind.Sequential)]
            public struct PROCESS_INFORMATION
            {
                public IntPtr hProcess;
                public IntPtr hThread;
                public uint dwProcessId;
                public uint dwThreadId;
            }
    
            #endregion
    
            #region Enumerations
    
            enum TOKEN_TYPE : int
            {
                TokenPrimary = 1,
                TokenImpersonation = 2
            }
    
            enum SECURITY_IMPERSONATION_LEVEL : int
            {
                SecurityAnonymous = 0,
                SecurityIdentification = 1,
                SecurityImpersonation = 2,
                SecurityDelegation = 3,
            }
    
            #endregion
    
            #region Constants
    
            public const int TOKEN_DUPLICATE = 0x0002;
            public const uint MAXIMUM_ALLOWED = 0x2000000;
            public const int CREATE_NEW_CONSOLE = 0x00000010;
    
            public const int IDLE_PRIORITY_CLASS = 0x40;
            public const int NORMAL_PRIORITY_CLASS = 0x20;
            public const int HIGH_PRIORITY_CLASS = 0x80;
            public const int REALTIME_PRIORITY_CLASS = 0x100;
    
            #endregion
    
            #region Win32 API Imports
    
            [DllImport("kernel32.dll", SetLastError = true)]
            private static extern bool CloseHandle(IntPtr hSnapshot);
    
            [DllImport("kernel32.dll")]
            static extern uint WTSGetActiveConsoleSessionId();
    
            [DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
            public extern static bool CreateProcessAsUser(IntPtr hToken, String lpApplicationName, String lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes,
                ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandle, int dwCreationFlags, IntPtr lpEnvironment,
                String lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);
    
            [DllImport("kernel32.dll")]
            static extern bool ProcessIdToSessionId(uint dwProcessId, ref uint pSessionId);
    
            // Fixed invalid declaration from Code Projects code
            [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")]
            public extern static bool DuplicateTokenEx(IntPtr ExistingTokenHandle, uint dwDesiredAccess,
                ref SECURITY_ATTRIBUTES lpThreadAttributes, int ImpersonationLevel,
                int TokenType, ref IntPtr DuplicateTokenHandle);
    
            [DllImport("kernel32.dll")]
            static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);
    
            [DllImport("advapi32", SetLastError = true), SuppressUnmanagedCodeSecurity]
            static extern bool OpenProcessToken(IntPtr ProcessHandle, int DesiredAccess, ref IntPtr TokenHandle);
    
            #endregion
    
            /// <summary>
            /// Launches the given application with full admin rights, and in addition bypasses the Vista UAC prompt
            /// </summary>
            /// <param name="applicationName">The name of the application to launch</param>
            /// <param name="procInfo">Process information regarding the launched application that gets returned to the caller</param>
            /// <returns></returns>
            public static bool StartProcessAndBypassUAC(String applicationName, string startingDir, out PROCESS_INFORMATION procInfo)
            {
                uint winlogonPid = 0;
                IntPtr hUserTokenDup = IntPtr.Zero, hPToken = IntPtr.Zero, hProcess = IntPtr.Zero;
                procInfo = new PROCESS_INFORMATION();
    
                // obtain the currently active session id; every logged on user in the system has a unique session id
                uint dwSessionId = WTSGetActiveConsoleSessionId();
    
                // obtain the process id of the winlogon process that is running within the currently active session
                // -- chaged by ty 
                // Process[] processes = Process.GetProcessesByName("winlogon");
                Process[] processes = Process.GetProcessesByName("explorer");
                foreach (Process p in processes)
                {
                    if ((uint)p.SessionId == dwSessionId)
                    {
                        winlogonPid = (uint)p.Id;
                    }
                }
    
                // obtain a handle to the winlogon process
                hProcess = OpenProcess(MAXIMUM_ALLOWED, false, winlogonPid);
    
                // obtain a handle to the access token of the winlogon process
                if (!OpenProcessToken(hProcess, TOKEN_DUPLICATE, ref hPToken))
                {
                    CloseHandle(hProcess);
                    return false;
                }
    
                // Security attibute structure used in DuplicateTokenEx and CreateProcessAsUser
                // I would prefer to not have to use a security attribute variable and to just 
                // simply pass null and inherit (by default) the security attributes
                // of the existing token. However, in C# structures are value types and therefore
                // cannot be assigned the null value.
                SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
                sa.Length = Marshal.SizeOf(sa);
    
                // copy the access token of the winlogon process; the newly created token will be a primary token
                if (!DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, ref sa, (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, (int)TOKEN_TYPE.TokenPrimary, ref hUserTokenDup))
                {
                    CloseHandle(hProcess);
                    CloseHandle(hPToken);
                    return false;
                }
    
                // By default CreateProcessAsUser creates a process on a non-interactive window station, meaning
                // the window station has a desktop that is invisible and the process is incapable of receiving
                // user input. To remedy this we set the lpDesktop parameter to indicate we want to enable user 
                // interaction with the new process.
                STARTUPINFO si = new STARTUPINFO();
                si.cb = (int)Marshal.SizeOf(si);
                si.lpDesktop = @"winsta0\default"; // interactive window station parameter; basically this indicates that the process created can display a GUI on the desktop
    
                // flags that specify the priority and creation method of the process
                int dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE;
    
                // create a new process in the current user's logon session
                bool result = CreateProcessAsUser(hUserTokenDup,        // client's access token
                                                null,                   // file to execute
                                                applicationName,        // command line
                                                ref sa,                 // pointer to process SECURITY_ATTRIBUTES
                                                ref sa,                 // pointer to thread SECURITY_ATTRIBUTES
                                                false,                  // handles are not inheritable
                                                dwCreationFlags,        // creation flags
                                                IntPtr.Zero,            // pointer to new environment block 
                                                startingDir,                   // name of current directory 
                                                ref si,                 // pointer to STARTUPINFO structure
                                                out procInfo            // receives information about new process
                                                );
    
                // invalidate the handles
                CloseHandle(hProcess);
                CloseHandle(hPToken);
                CloseHandle(hUserTokenDup);
    
                return result; // return the result
            }
    
        }
    }