launch process from Session 0 Isolation
Asked Answered
S

1

9

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.

Siana answered 8/9, 2014 at 14:7 Comment(3)
"Allow service to interact with desktop" hasn't worked since Vista.Hydnocarpate
This might get useful responses at serverfault.comHydnocarpate
I think I am getting closer, I can get a console to show up by using a Win32 API CreateProcessAsUser. Found this example at code project but the new processes are still running under nt authority\system.Siana
S
8

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
        }

    }
}
Siana answered 9/9, 2014 at 2:5 Comment(12)
Thanos, I also have the same project purpose, I am using windows 8, will it be helpful?Angi
Hello Thanos, I tested this in my scenario, but it is returning either "true" or "false". How to launch Process? I want to display WPF when Web Service is called?Angi
@NiteshKothari It should work for windows 8, but I have not tested it as I only have Win8.1. We added logging code before ' // invalidate the handles' 'if (!result) logger.ErrorFormat("Failed to create process as user. Last error: {0}", Marshal.GetLastWin32Error()); else if (GetExitCodeProcess(procInfo.hProcess, out processExitCode)) logger.InfoFormat("CreateProcessAsUser - process exit code: {0}", processExitCode);'Siana
Can you help me about my issue?? @SianaAngi
I am launching WPF Process thorugh web service call means I have added code of Process launching in WebMethod. Its working in visual develoent server but when trying to launch fron local IIS, its running in background, session 0? @SianaAngi
@NiteshKothari This commenting system is hard to use. I apologize b/c i keep hitting the enter key. I will help you as well as i can. Try creating a local window service that runs when windows starts up and set it to run under the system account. Then come up with a way to trigger that service to start the process. Think of it as an intermediate step. then later on set it to the same user as IIS and try launching the process again.Siana
Let us continue this discussion in chat.Siana
@NiteshKothari Is IIS running on a box that no one is on? This code looks for whoever is running explorer Process[] processes = Process.GetProcessesByName("explorer"); and then launches the app under that user. it works for you b/c you are using explorer but the IIS box probably doesn't have anyone on it.Siana
lets discuss in chat @SianaAngi
can u help me out with iis service ?Octameter
Testing on Windows 10, but trying to go the other way (elevated process in user session), however it does work if you take the process token from winlogon.exe - this will launch the process as nt authority\system. Otherwise, the process will be launched un-elevated as the user of the session.Nealson
@Nealson can you please provide updated code, i am facing issue with windows 10Entasis

© 2022 - 2024 — McMap. All rights reserved.