Использование Process.Start() для запуска процесса как другого пользователя из службы Windows

Я хочу периодически запускать произвольную .NET exe под указанной учетной записью пользователя из службы Windows.

Пока у меня работает служба Windows с логикой, чтобы решить, что такое целевой процесс, и когда его запускать. Целевой процесс запускается следующим образом:

  • Служба Windows запускается с использованием учетных данных "administrator".
  • Когда придет время, выполняется промежуточный процесс .NET с аргументами, подробно описывающими, какой процесс должен быть запущен (имя файла, имя пользователя, домен, пароль).
  • Этот процесс создает новый System.Diagnostics.Process, связывает объект ProcessStartInfo, заполненный переданными ему аргументами, а затем вызывает объект Start() для объекта процесса.

первый раз это происходит, целевой процесс выполняется нормально, а затем обычно закрывается. Однако каждый последующий момент, как только начинается целевой процесс, он выдает ошибку "Приложение не удалось инициализировать правильно (0xc0000142)". Перезапуск службы Windows позволит процессу снова успешно выполнить (для первого выполнения).

Естественно, цель состоит в успешном выполнении целевого процесса каждый раз.

Относительно шага 2 выше: для запуска процесса как другого пользователя .NET вызывает функцию win32 CreateProcessWithLogonW. Эта функция требует, чтобы дескриптор окна регистрировал указанного пользователя. Поскольку служба Windows не работает в интерактивном режиме, у нее нет дескриптора окна. Этот промежуточный процесс решает проблему, поскольку он имеет дескриптор окна, который может быть передан целевому процессу.

Пожалуйста, никаких рекомендаций по использованию psexec или планировщика задач Windows. Я принял свою судьбу в жизни, и это включает решение проблемы в соответствии с изложенным выше способом.

Ответ 1

Кажется, у меня есть рабочая реализация (Works On My Machine (TM)) для следующих сценариев:

Пакетный файл, сборка .NET консоли, приложение .NET Windows Forms.

Вот как:

У меня есть служба Windows, работающая как пользователь Administrator. Я добавляю следующие политики пользователю-администратору:

  • Войдите в систему как услугу
  • Действовать как часть операционной системы
  • Настройка квот памяти для процесса
  • Заменить токен уровня процесса

Эти политики можно добавить, открыв Панель управления/Администрирование/Локальная политика безопасности/Назначение прав пользователей. После их установки политики не вступают в силу до следующего входа в систему. Вы можете использовать другого пользователя вместо Администратора, который может сделать вещи более безопасными:)

Теперь моя служба Windows имеет необходимые разрешения для запуска заданий в качестве других пользователей. Когда нужно запустить задание, служба выполняет отдельную сборку (сборка консоли "Стартер".NET), которая инициирует этот процесс для меня.

Следующий код, расположенный в службе Windows, выполняет мою консоль консоли "Стартер":

Process proc = null;
System.Diagnostics.ProcessStartInfo info;
string domain = string.IsNullOrEmpty(row.Domain) ? "." : row.Domain;
info = new ProcessStartInfo("Starter.exe");
info.Arguments = cmd + " " + domain + " " + username + " " + password + " " + args;
info.WorkingDirectory = Path.GetDirectoryName(cmd);
info.UseShellExecute = false;
info.RedirectStandardError = true;
info.RedirectStandardOutput = true;
proc = System.Diagnostics.Process.Start(info);

Затем консольная сборка запускает целевой процесс через вызовы interop:

class Program
{
    #region Interop

    [StructLayout(LayoutKind.Sequential)]
    public struct LUID
    {
        public UInt32 LowPart;
        public Int32 HighPart;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct LUID_AND_ATTRIBUTES
    {
        public LUID Luid;
        public UInt32 Attributes;
    }

    public struct TOKEN_PRIVILEGES
    {
        public UInt32 PrivilegeCount;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
        public LUID_AND_ATTRIBUTES[] Privileges;
    }

    enum TOKEN_INFORMATION_CLASS
    {
        TokenUser = 1,
        TokenGroups,
        TokenPrivileges,
        TokenOwner,
        TokenPrimaryGroup,
        TokenDefaultDacl,
        TokenSource,
        TokenType,
        TokenImpersonationLevel,
        TokenStatistics,
        TokenRestrictedSids,
        TokenSessionId,
        TokenGroupsAndPrivileges,
        TokenSessionReference,
        TokenSandBoxInert,
        TokenAuditPolicy,
        TokenOrigin,
        TokenElevationType,
        TokenLinkedToken,
        TokenElevation,
        TokenHasRestrictions,
        TokenAccessInformation,
        TokenVirtualizationAllowed,
        TokenVirtualizationEnabled,
        TokenIntegrityLevel,
        TokenUIAccess,
        TokenMandatoryPolicy,
        TokenLogonSid,
        MaxTokenInfoClass
    }

    [Flags]
    enum CreationFlags : uint
    {
        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 = 0x00001000,
        CREATE_SUSPENDED = 0x00000004,
        CREATE_UNICODE_ENVIRONMENT = 0x00000400,
        DEBUG_ONLY_THIS_PROCESS = 0x00000002,
        DEBUG_PROCESS = 0x00000001,
        DETACHED_PROCESS = 0x00000008,
        EXTENDED_STARTUPINFO_PRESENT = 0x00080000
    }

    public enum TOKEN_TYPE
    {
        TokenPrimary = 1,
        TokenImpersonation
    }

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

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

    enum LOGON_TYPE
    {
        LOGON32_LOGON_INTERACTIVE = 2,
        LOGON32_LOGON_NETWORK,
        LOGON32_LOGON_BATCH,
        LOGON32_LOGON_SERVICE,
        LOGON32_LOGON_UNLOCK = 7,
        LOGON32_LOGON_NETWORK_CLEARTEXT,
        LOGON32_LOGON_NEW_CREDENTIALS
    }

    enum LOGON_PROVIDER
    {
        LOGON32_PROVIDER_DEFAULT,
        LOGON32_PROVIDER_WINNT35,
        LOGON32_PROVIDER_WINNT40,
        LOGON32_PROVIDER_WINNT50
    }

    #region _SECURITY_ATTRIBUTES
    //typedef struct _SECURITY_ATTRIBUTES {  
    //    DWORD nLength;  
    //    LPVOID lpSecurityDescriptor;  
    //    BOOL bInheritHandle;
    //} SECURITY_ATTRIBUTES,  *PSECURITY_ATTRIBUTES,  *LPSECURITY_ATTRIBUTES;
    #endregion
    struct SECURITY_ATTRIBUTES
    {
        public uint Length;
        public IntPtr SecurityDescriptor;
        public bool InheritHandle;
    }

    [Flags] enum SECURITY_INFORMATION : uint
    {
        OWNER_SECURITY_INFORMATION        = 0x00000001,
        GROUP_SECURITY_INFORMATION        = 0x00000002,
        DACL_SECURITY_INFORMATION         = 0x00000004,
        SACL_SECURITY_INFORMATION         = 0x00000008,
        UNPROTECTED_SACL_SECURITY_INFORMATION = 0x10000000,
        UNPROTECTED_DACL_SECURITY_INFORMATION = 0x20000000,
        PROTECTED_SACL_SECURITY_INFORMATION   = 0x40000000,
        PROTECTED_DACL_SECURITY_INFORMATION   = 0x80000000
    }

    #region _SECURITY_DESCRIPTOR
    //typedef struct _SECURITY_DESCRIPTOR {
    //  UCHAR  Revision;
    //  UCHAR  Sbz1;
    //  SECURITY_DESCRIPTOR_CONTROL  Control;
    //  PSID  Owner;
    //  PSID  Group;
    //  PACL  Sacl;
    //  PACL  Dacl;
    //} SECURITY_DESCRIPTOR, *PISECURITY_DESCRIPTOR;
    #endregion
    [StructLayoutAttribute(LayoutKind.Sequential)]
    struct SECURITY_DESCRIPTOR
    {
        public byte revision;
        public byte size;
        public short control; // public SECURITY_DESCRIPTOR_CONTROL control;
        public IntPtr owner;
        public IntPtr group;
        public IntPtr sacl;
        public IntPtr dacl;
    }

    #region _STARTUPINFO
    //typedef struct _STARTUPINFO {  
    //    DWORD cb;  
    //    LPTSTR lpReserved;  
    //    LPTSTR lpDesktop;  
    //    LPTSTR lpTitle;  
    //    DWORD dwX;  
    //    DWORD dwY;  
    //    DWORD dwXSize;  
    //    DWORD dwYSize;  
    //    DWORD dwXCountChars;  
    //    DWORD dwYCountChars;  
    //    DWORD dwFillAttribute;  
    //    DWORD dwFlags;  
    //    WORD wShowWindow;  
    //    WORD cbReserved2;  
    //    LPBYTE lpReserved2;  
    //    HANDLE hStdInput;  
    //    HANDLE hStdOutput;  
    //    HANDLE hStdError; 
    //} STARTUPINFO,  *LPSTARTUPINFO;
    #endregion
    struct STARTUPINFO
    {
        public uint cb;
        [MarshalAs(UnmanagedType.LPTStr)]
        public string Reserved;
        [MarshalAs(UnmanagedType.LPTStr)]
        public string Desktop;
        [MarshalAs(UnmanagedType.LPTStr)]
        public string Title;
        public uint X;
        public uint Y;
        public uint XSize;
        public uint YSize;
        public uint XCountChars;
        public uint YCountChars;
        public uint FillAttribute;
        public uint Flags;
        public ushort ShowWindow;
        public ushort Reserverd2;
        public byte bReserverd2;
        public IntPtr StdInput;
        public IntPtr StdOutput;
        public IntPtr StdError;
    }

    #region _PROCESS_INFORMATION
    //typedef struct _PROCESS_INFORMATION {  
    //  HANDLE hProcess;  
    //  HANDLE hThread;  
    //  DWORD dwProcessId;  
    //  DWORD dwThreadId; } 
    //  PROCESS_INFORMATION,  *LPPROCESS_INFORMATION;
    #endregion
    [StructLayout(LayoutKind.Sequential)]
    struct PROCESS_INFORMATION
    {
        public IntPtr Process;
        public IntPtr Thread;
        public uint ProcessId;
        public uint ThreadId;
    }

    [DllImport("advapi32.dll", SetLastError = true)]
    static extern bool InitializeSecurityDescriptor(IntPtr pSecurityDescriptor, uint dwRevision);
    const uint SECURITY_DESCRIPTOR_REVISION = 1;

    [DllImport("advapi32.dll", SetLastError = true)]
    static extern bool SetSecurityDescriptorDacl(ref SECURITY_DESCRIPTOR sd, bool daclPresent, IntPtr dacl, bool daclDefaulted);

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    extern static bool DuplicateTokenEx(
        IntPtr hExistingToken,
        uint dwDesiredAccess,
        ref SECURITY_ATTRIBUTES lpTokenAttributes,
        SECURITY_IMPERSONATION_LEVEL ImpersonationLevel,
        TOKEN_TYPE TokenType,
        out IntPtr phNewToken);

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool LogonUser(
        string lpszUsername,
        string lpszDomain,
        string lpszPassword,
        int dwLogonType,
        int dwLogonProvider,
        out IntPtr phToken
        );

    #region GetTokenInformation
    //BOOL WINAPI GetTokenInformation(
    //  __in       HANDLE TokenHandle,
    //  __in       TOKEN_INFORMATION_CLASS TokenInformationClass,
    //  __out_opt  LPVOID TokenInformation,
    //  __in       DWORD TokenInformationLength,
    //  __out      PDWORD ReturnLength
    //);
    #endregion
    [DllImport("advapi32.dll", SetLastError = true)]
    static extern bool GetTokenInformation(
        IntPtr TokenHandle,
        TOKEN_INFORMATION_CLASS TokenInformationClass,
        IntPtr TokenInformation,
        int TokenInformationLength,
        out int ReturnLength
        );


    #region CreateProcessAsUser
    //        BOOL WINAPI CreateProcessAsUser(
    //  __in_opt     HANDLE hToken,
    //  __in_opt     LPCTSTR lpApplicationName,
    //  __inout_opt  LPTSTR lpCommandLine,
    //  __in_opt     LPSECURITY_ATTRIBUTES lpProcessAttributes,
    //  __in_opt     LPSECURITY_ATTRIBUTES lpThreadAttributes,
    //  __in         BOOL bInheritHandles,
    //  __in         DWORD dwCreationFlags,
    //  __in_opt     LPVOID lpEnvironment,
    //  __in_opt     LPCTSTR lpCurrentDirectory,
    //  __in         LPSTARTUPINFO lpStartupInfo,
    //  __out        LPPROCESS_INFORMATION lpProcessInformation);
    #endregion
    [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    static extern bool CreateProcessAsUser(
        IntPtr Token, 
        [MarshalAs(UnmanagedType.LPTStr)] string ApplicationName,
        [MarshalAs(UnmanagedType.LPTStr)] string CommandLine,
        ref SECURITY_ATTRIBUTES ProcessAttributes, 
        ref SECURITY_ATTRIBUTES ThreadAttributes, 
        bool InheritHandles,
        uint CreationFlags, 
        IntPtr Environment, 
        [MarshalAs(UnmanagedType.LPTStr)] string CurrentDirectory, 
        ref STARTUPINFO StartupInfo, 
        out PROCESS_INFORMATION ProcessInformation);

    #region CloseHandle
    //BOOL WINAPI CloseHandle(
    //      __in          HANDLE hObject
    //        );
    #endregion
    [DllImport("Kernel32.dll")]
    extern static int CloseHandle(IntPtr handle);

    [DllImport("advapi32.dll", ExactSpelling = true, SetLastError = true)]
    internal static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall, ref TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen);

    [DllImport("advapi32.dll", SetLastError = true)]
    internal static extern bool LookupPrivilegeValue(string host, string name, ref long pluid);

    [StructLayout(LayoutKind.Sequential, Pack = 1)]
    internal struct TokPriv1Luid
    {
        public int Count;
        public long Luid;
        public int Attr;
    }

    //static internal const int TOKEN_QUERY = 0x00000008;
    internal const int SE_PRIVILEGE_ENABLED = 0x00000002;
    //static internal const int TOKEN_ADJUST_PRIVILEGES = 0x00000020;

    internal const int TOKEN_QUERY = 0x00000008;
    internal const int TOKEN_DUPLICATE = 0x0002;
    internal const int TOKEN_ASSIGN_PRIMARY = 0x0001;

    #endregion

    [STAThread]
    static void Main(string[] args)
    {
        string username, domain, password, applicationName;
        username = args[2];
        domain = args[1];
        password = args[3];
        applicationName = @args[0];

        IntPtr token = IntPtr.Zero;
        IntPtr primaryToken = IntPtr.Zero;
        try
        {
            bool result = false;

            result = LogonUser(username, domain, password, (int)LOGON_TYPE.LOGON32_LOGON_NETWORK, (int)LOGON_PROVIDER.LOGON32_PROVIDER_DEFAULT, out token);
            if (!result)
            {
                int winError = Marshal.GetLastWin32Error();
            }

            string commandLine = null;

            #region security attributes
            SECURITY_ATTRIBUTES processAttributes = new SECURITY_ATTRIBUTES();

            SECURITY_DESCRIPTOR sd = new SECURITY_DESCRIPTOR();
            IntPtr ptr = Marshal.AllocCoTaskMem(Marshal.SizeOf(sd));
            Marshal.StructureToPtr(sd, ptr, false);
            InitializeSecurityDescriptor(ptr, SECURITY_DESCRIPTOR_REVISION);
            sd = (SECURITY_DESCRIPTOR)Marshal.PtrToStructure(ptr, typeof(SECURITY_DESCRIPTOR));

            result = SetSecurityDescriptorDacl(ref sd, true, IntPtr.Zero, false);
            if (!result)
            {
                int winError = Marshal.GetLastWin32Error();
            }

            primaryToken = new IntPtr();
            result = DuplicateTokenEx(token, 0, ref processAttributes, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenPrimary, out primaryToken);
            if (!result)
            {
                int winError = Marshal.GetLastWin32Error();
            }
            processAttributes.SecurityDescriptor = ptr;
            processAttributes.Length = (uint)Marshal.SizeOf(sd);
            processAttributes.InheritHandle = true;
            #endregion

            SECURITY_ATTRIBUTES threadAttributes = new SECURITY_ATTRIBUTES();
            threadAttributes.SecurityDescriptor = IntPtr.Zero;
            threadAttributes.Length = 0;
            threadAttributes.InheritHandle = false;

            bool inheritHandles = true;
            //CreationFlags creationFlags = CreationFlags.CREATE_DEFAULT_ERROR_MODE;
            IntPtr environment = IntPtr.Zero;
            string currentDirectory = currdir;

            STARTUPINFO startupInfo = new STARTUPINFO();
            startupInfo.Desktop = "";

            PROCESS_INFORMATION processInformation;

            result = CreateProcessAsUser(primaryToken, applicationName, commandLine, ref processAttributes, ref threadAttributes, inheritHandles, 16, environment, currentDirectory, ref startupInfo, out processInformation);
            if (!result)
            {
                int winError = Marshal.GetLastWin32Error();
                File.AppendAllText(logfile, DateTime.Now.ToLongTimeString() + " " + winError + Environment.NewLine);
            }
        }
        catch
        {
            int winError = Marshal.GetLastWin32Error();
            File.AppendAllText(logfile, DateTime.Now.ToLongTimeString() + " " + winError + Environment.NewLine);
        }
        finally
        {
            if (token != IntPtr.Zero)
            {
                int x = CloseHandle(token);
                if (x == 0)
                    throw new Win32Exception(Marshal.GetLastWin32Error());
                x = CloseHandle(primaryToken);
                if (x == 0)
                    throw new Win32Exception(Marshal.GetLastWin32Error());
            }
        }
    }

Основная процедура:

  • Зарегистрировать пользователя
  • преобразовать данный токен в первичный токен
  • Используя этот токен, выполните процесс
  • Закройте дескриптор, когда закончите.

Это код разработки, свежий от моей машины и не приближенный к готовому для использования в производственных средах. Код здесь по-прежнему неисправен. Для начала: я не уверен, закрыты ли дескрипторы в правой точке, и есть несколько функций взаимодействия, определенных выше, которые не требуются. Отдельный стартовый процесс также меня очень раздражает. В идеале я бы хотел, чтобы весь этот материал Job, завернутый в сборку для использования из нашего API, а также этой службы. Если у кого-то есть предложения, они будут оценены.

Ответ 2

Я не буду предлагать ни psexec, ни планировщик задач. Но посмотрели ли вы на Sudowin?

Это почти то, что вы хотите, за исключением того, что он запрашивает пароль перед выполнением процесса.

Кроме того, будучи открытым исходным кодом и всеми, вы можете увидеть, как он выполняет процессы из связанного времени службы и снова.

Ответ 3

Причина, по которой вызов завершается с ошибкой после первого раза, очень вероятно, потому что он использует дескриптор безопасности по умолчанию (независимо от того, что это такое).

from msdn:

lpProcessAttributes [in, optional]

Указатель на SECURITY_ATTRIBUTES структура, определяющая безопасность дескриптор для нового объекта процесса и определяет, будут ли дочерние процессы может наследовать возвращенный дескриптор обработать. Если lpProcessAttributes NULL или lpSecurityDescriptor имеет значение NULL, процесс получает защиту по умолчанию дескриптор и дескриптор не могут быть по наследству. Безопасность по умолчанию дескриптор - это имя пользователя ссылка на параметр hToken. Этот дескриптор безопасности может не разрешать доступ для вызывающего абонента, , и в этом случае процесс не может быть снова открыт после его запуска. Обработчик процесса является действительным и будет по-прежнему иметь полные права доступа.

Я думаю, что CreateProcessWithLogonW создает этот дескриптор безопасности по умолчанию (в любом случае я не указываю его).

Время начала Interopping...

Ответ 4

Просто угадайте - вы используете LoadUserProfile = true с информацией о запуске? CreateProcessWithLogonW по умолчанию не загружает куст реестра пользователей, если вы не сообщите об этом.

Ответ 5

Вам не нужен дескриптор окна для использования CreateProcessWithLogonW, я не уверен, откуда появилась ваша информация.

Приложению не удалось инициализировать ошибку, есть много причин, но она почти всегда связана с безопасностью или исчерпанными ресурсами пользователя. Это очень сложно диагностировать без дополнительной информации о том, что вы используете, и о контексте, в котором вы работаете. Но все, что нужно изучить: предоставляет ли пользователь правильные разрешения для доступа к каталогу исполняемого файла, делает пользователь имеет разрешение на доступ к оконной станции и настольному компьютеру, на котором он запускается, имеет ли он правильные разрешения для любых DLL файлов, которые необходимо загрузить при инициализации, и т.д.

Ответ 6

Я только что прочитал этот комментарий в msdn (http://msdn.microsoft.com/en-us/library/ms682431(VS.85).aspx):

Не вызывайте пользовательские приложения с этим функция! КристианВиммер | Редактировать | Показать историю Пожалуйста, подождите Если вы перейдете в режим пользователя приложения, предлагающие документ редактирование и аналогичные материалы (например, Word), все несохраненные данные будут потеряны. Это потому что обычная последовательность выключения не распространяется на запущенные процессы с CreateProcessWithLogonW. В этом как запущенные приложения не получить WM_QUERYENDSESSION, WM_ENDSESSION и самый важный WM_QUIT сообщение. Поэтому они не просят сэкономить данные или очистить их материал. Oни выйдет без предупреждения. Эта функция не является удобной для пользователя и следует использовать с осторожностью.

Это просто "плохой пользовательский опыт". Никто не ожидает этого.

Это может объяснить, что я наблюдал: работает в первый раз. Сбой происходит каждый последующий раз. Это подтверждает мою веру в то, что что-то не очищается должным образом внутри страны.

Ответ 7

Вы говорите, что "Служба Windows запущена с использованием учетных данных администратора"

Вы имеете в виду фактическую учетную запись "Администратор" или пользователь в группе "Администраторы"? Запуск службы как администратора решил для меня.

Ответ 8

У меня были подобные проблемы, когда я попытался запустить двоичный файл PhantomJS с помощью "runas" -verb в службе Windows. Теперь я решил проблему, выполнив следующую процедуру:

  • Олицетворять пользователя
  • Процесс запуска (без интерфейса)
  • Олицетворение назад

Вы можете использовать Impersonator-Class для олицетворения. Также важно установить следующие свойства в ProcessStartInfo, поэтому приложение не пытается получить доступ к пользовательскому интерфейсу Windows:

var processStartInfo = new ProcessStartInfo()
{
    FileName = [email protected]"{assemblyFolder}\PhantomJS\phantomjs.exe",
    Arguments = $"--webdriver={port}",
    RedirectStandardOutput = true,
    RedirectStandardError = true,
    RedirectStandardInput = true,
    UseShellExecute = false,
    CreateNoWindow = true,
    ErrorDialog = false,
    WindowStyle = ProcessWindowStyle.Hidden
};