简体   繁体   中英

Runas /NETONLY from C#

I am trying to write a C# program that does the same thing as runas /netonly. Doing the same thing as runas is easy, and I found many examples. However, doing a /netonly, exactly like runas, does not seem to be easy.

I found from various answers the bulk of the code but it requires some privileges that the standard user doesn't have. My question is, how can runas command line works without any privilege, not even admin rights, and my program cannot? What API is runas using that makes it work without any privilege? What should I change in this code, to make it work without privilege?

To explain a bit more the approach: I use LogonUser API to create a NetOnly token with "NEW_CREDENTIALS" param. Then, I Use CreateProcessAsUser to run any external exe using the previous token.

Other approch I tried failed like: Using process start with an impersonation token from LogonUser does not work, because process start does not inherit the imersonated token, but rather use the original token from the parent process.

Here is the code:

using Microsoft.Win32.SafeHandles;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace runas_manager
{
    internal class ImpersonatedProcess
    {

        //private const string CommandLine = @"C:\createjobobject.exe";
        private NativeMethods.ProcessInformation _processInfo;
        private readonly ManualResetEvent _exited = new ManualResetEvent(false);


        public IntPtr Handle { get; private set; }
        public event EventHandler? Exited;
        public TextReader StandardOutput { get; private set; }
        public TextReader StandardError { get; private set; }
        public TextWriter StandardInput { get; private set; }

        public void WaitForExit()
        {
            WaitForExit(-1);
        }

        public bool WaitForExit(int milliseconds)
        {
            return _exited.WaitOne(milliseconds);
        }

        public bool Start(string username, string password, string domain, string executablePath)
        {
            _processInfo = new NativeMethods.ProcessInformation();
            var startInfo = new NativeMethods.StartupInfo();
            bool success;

            SafeFileHandle hToken, hReadOut, hWriteOut, hReadErr, hWriteErr, hReadIn, hWriteIn;

            var securityAttributes = new NativeMethods.SecurityAttributes();
            securityAttributes.bInheritHandle = true;

            success = NativeMethods.CreatePipe(out hReadOut, out hWriteOut, securityAttributes, 0);
            if (!success)
                throw new Win32Exception(Marshal.GetLastWin32Error());

            success = NativeMethods.CreatePipe(out hReadErr, out hWriteErr, securityAttributes, 0);
            if (!success)
                throw new Win32Exception(Marshal.GetLastWin32Error());

            success = NativeMethods.CreatePipe(out hReadIn, out hWriteIn, securityAttributes, 0);
            if (!success)
                throw new Win32Exception(Marshal.GetLastWin32Error());

            success = NativeMethods.SetHandleInformation(hReadOut, NativeMethods.Constants.HANDLE_FLAG_INHERIT, 0);
            if (!success)
                throw new Win32Exception(Marshal.GetLastWin32Error());

            // Logon user
            success = NativeMethods.LogonUser(
                username,
                domain,
                password,
                NativeMethods.LogonType.LOGON32_LOGON_NEW_CREDENTIALS,
                NativeMethods.LogonProvider.LOGON32_PROVIDER_DEFAULT,
                out hToken
            );
            if (!success)
                throw new Win32Exception(Marshal.GetLastWin32Error());

            if (!NativeMethods.CreateEnvironmentBlock(out IntPtr unmanagedEnv, hToken.DangerousGetHandle(), false))
            {
                int lastError = Marshal.GetLastWin32Error();
                throw new Win32Exception(lastError, "Error calling CreateEnvironmentBlock: " + lastError);
            }

            // Create process
            startInfo.cb = Marshal.SizeOf(startInfo);
            startInfo.dwFlags = NativeMethods.Constants.STARTF_USESTDHANDLES;
            startInfo.hStdOutput = hWriteOut;
            startInfo.hStdError = hWriteErr;
            startInfo.hStdInput = hReadIn;

            success = NativeMethods.CreateProcessAsUser(
                hToken,
                null,
                executablePath,
                IntPtr.Zero,
                IntPtr.Zero,
                true,
                NativeMethods.CreateProcessFlags.CREATE_UNICODE_ENVIRONMENT,
                unmanagedEnv,
                null,
                ref startInfo,
                out _processInfo
            );

            if (!success)
                throw new Win32Exception(Marshal.GetLastWin32Error());

            Handle = _processInfo.hProcess;

            startInfo.hStdOutput.Close();
            startInfo.hStdError.Close();
            startInfo.hStdInput.Close();
            StandardOutput = new StreamReader(new FileStream(hReadOut, FileAccess.Read), Console.OutputEncoding);
            StandardError = new StreamReader(new FileStream(hReadErr, FileAccess.Read), Console.OutputEncoding);
            StandardInput = new StreamWriter(new FileStream(hWriteIn, FileAccess.Write), Console.InputEncoding);

            WaitForExitAsync();

            return success;
        }

        private void WaitForExitAsync()
        {
            var thr = new Thread(() =>
            {
                _ = NativeMethods.WaitForSingleObject(_processInfo.hProcess, NativeMethods.Constants.INFINITE);
                Exited?.Invoke(this, EventArgs.Empty);
                _exited.Set();
            });
            thr.Start();
        }
    }
}

and nativemethods declarations

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Win32.SafeHandles;

namespace runas_manager
{
    class NativeMethods
    {
        [StructLayout(LayoutKind.Sequential)]
        public struct ProcessInformation
        {
            public IntPtr hProcess;
            public IntPtr hThread;
            public Int32 dwProcessId;
            public Int32 dwThreadId;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct StartupInfo
        {
            public Int32 cb;
            public String lpReserved;
            public String lpDesktop;

            public String lpTitle;
            public Int32 dwX;
            public Int32 dwY;
            public Int32 dwXSize;
            public Int32 dwYSize;
            public Int32 dwXCountChars;
            public Int32 dwYCountChars;
            public Int32 dwFillAttribute;
            public uint dwFlags;
            public Int16 wShowWindow;
            public Int16 cbReserved2;
            public IntPtr lpReserved2;
            public SafeFileHandle hStdInput;
            public SafeFileHandle hStdOutput;
            public SafeFileHandle hStdError;
        }

        [StructLayout(LayoutKind.Sequential)]
        public class SecurityAttributes
        {
            public Int32 Length;
            public IntPtr lpSecurityDescriptor;
            public bool bInheritHandle;

            public SecurityAttributes()
            {
                this.Length = Marshal.SizeOf(this);
            }
        }

        [Flags]
        public enum LogonType
        {
            LOGON32_LOGON_INTERACTIVE = 2,
            LOGON32_LOGON_NETWORK = 3,
            LOGON32_LOGON_BATCH = 4,
            LOGON32_LOGON_SERVICE = 5,
            LOGON32_LOGON_UNLOCK = 7,
            LOGON32_LOGON_NETWORK_CLEARTEXT = 8,
            LOGON32_LOGON_NEW_CREDENTIALS = 9
        }

        [Flags]
        public enum LogonProvider
        {
            LOGON32_PROVIDER_DEFAULT = 0,
            LOGON32_PROVIDER_WINNT35,
            LOGON32_PROVIDER_WINNT40,
            LOGON32_PROVIDER_WINNT50
        }

        [Flags]
        public enum CreateProcessFlags
        {
            CREATE_BREAKAWAY_FROM_JOB = 0x01000000,
            CREATE_DEFAULT_ERROR_MODE = 0x04000000,
            CREATE_NEW_CONSOLE = 0x00000010,
            CREATE_NEW_PROCESS_GROUP = 0x00000200,
            CREATE_NO_WINDOW = 0x08000000,
            CREATE_PROTECTED_PROCESS = 0x00040000,
            CREATE_PRESERVE_CODE_AUTHZ_LEVEL = 0x02000000,
            CREATE_SEPARATE_WOW_VDM = 0x00000800,
            CREATE_SHARED_WOW_VDM = 0x00001000,
            CREATE_SUSPENDED = 0x00000004,
            CREATE_UNICODE_ENVIRONMENT = 0x00000400,
            DEBUG_ONLY_THIS_PROCESS = 0x00000002,
            DEBUG_PROCESS = 0x00000001,
            DETACHED_PROCESS = 0x00000008,
            EXTENDED_STARTUPINFO_PRESENT = 0x00080000,
            INHERIT_PARENT_AFFINITY = 0x00010000
        }


        public class Constants
        {
            public const int HANDLE_FLAG_INHERIT = 1;
            public static uint STARTF_USESTDHANDLES = 0x00000100;
            public const UInt32 INFINITE = 0xFFFFFFFF;

        }

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool CreatePipe(out SafeFileHandle phReadPipe, out SafeFileHandle phWritePipe,
            SecurityAttributes lpPipeAttributes, uint nSize);

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern bool SetHandleInformation(SafeFileHandle hObject, int dwMask, uint dwFlags);

        [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        public static extern Boolean LogonUser(
            String lpszUserName,
            String lpszDomain,
            String lpszPassword,
            LogonType dwLogonType,
            LogonProvider dwLogonProvider,
            out SafeFileHandle phToken);

        [DllImport("userenv.dll", SetLastError = true)]
        public static extern bool CreateEnvironmentBlock(out IntPtr lpEnvironment, IntPtr hToken, bool bInherit);

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern Boolean CreateProcessAsUser
        (
            SafeFileHandle hToken,
            String? lpApplicationName,
            String lpCommandLine,
            IntPtr lpProcessAttributes,
            IntPtr lpThreadAttributes,
            Boolean bInheritHandles,
            CreateProcessFlags dwCreationFlags,
            IntPtr lpEnvironment,
            String? lpCurrentDirectory,
            ref StartupInfo lpStartupInfo,
            out ProcessInformation lpProcessInformation
        );

        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);


    }
}

I found a way to make it work. Instead of using LogonUser and CreateProcessAsUser, in the code above, I used only CreateProcessWithLogonW. This requires to know the password, which you usually know when you use runas /netonly, but the good thing is that you don't need any privilege apart from standard user!

Replace LogonUser, Environment creation and CreateProcessAsUser with the following code.

success = NativeMethods.CreateProcessWithLogonW(
                username,
                domain,
                password,
                NativeMethods.LogonFlags.LOGON_NETCREDENTIALS_ONLY,
                null,
                executablePath,
                NativeMethods.CreateProcessFlags.CREATE_UNICODE_ENVIRONMENT,
                IntPtr.Zero,//unmanagedEnv,
                null,
                ref startInfo,
                out _processInfo
                );

Add to the dllimport definition

        [Flags]
        public enum LogonFlags
        {
            LOGON_WITH_PROFILE = 1,
            LOGON_NETCREDENTIALS_ONLY = 2
        }


        [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        public static extern bool CreateProcessWithLogonW(
            String userName,
            String domain,
            String password,
            LogonFlags logonFlags,
            String? applicationName,
            String commandLine,
            CreateProcessFlags creationFlags,
            IntPtr environment,
            String? currentDirectory,
            ref StartupInfo startupInfo,
            out ProcessInformation processInformation);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM