Выдача себя за пользователя в службе Windows

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

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

        ProcessStartInfo startInfo = new ProcessStartInfo();
        startInfo.FileName = CommandDetails.Command;
        startInfo.WorkingDirectory = Settings.RoboCopyWorkingDirectory;
        startInfo.Arguments = commandLine;

        startInfo.UseShellExecute = false;
        startInfo.CreateNoWindow = true;
        startInfo.RedirectStandardError = true;
        startInfo.RedirectStandardOutput = true;

        // Credentials
        startInfo.Domain = ImperDomain;
        startInfo.UserName = ImperUsername;
        startInfo.Password = ImperPasswordSecure;

        process = Process.Start(startInfo);

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

Когда я использую локальную систему, я получаю Доступ запрещен

Есть идеи, как это сделать?

StackTace

Access is denied

   at System.Diagnostics.Process.StartWithCreateProcess(ProcessStartInfo startInfo)
   at System.Diagnostics.Process.Start()
   at System.Diagnostics.Process.Start(ProcessStartInfo startInfo)
   at Ace.WindowsService.ProcessCmd.ProcessCommand.StartProcess(ProcessStartInfo startInfo) in 

Я безуспешно пытался обернуть код приведенным ниже кодом Impersonate.

Выдача себя за код

public class Impersonation2 : IDisposable
{
    private WindowsImpersonationContext _impersonatedUserContext;

    // Declare signatures for Win32 LogonUser and CloseHandle APIs
    [DllImport("advapi32.dll", SetLastError = true)]
    static extern bool LogonUser(
      string principal,
      string authority,
      string password,
      LogonSessionType logonType,
      LogonProvider logonProvider,
      out IntPtr token);

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

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    static extern int DuplicateToken(IntPtr hToken,
        int impersonationLevel,
        ref IntPtr hNewToken);

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    static extern bool RevertToSelf();

    // ReSharper disable UnusedMember.Local
    enum LogonSessionType : uint
    {
        Interactive = 2,
        Network,
        Batch,
        Service,
        NetworkCleartext = 8,
        NewCredentials
    }
    // ReSharper disable InconsistentNaming
    enum LogonProvider : uint
    {
        Default = 0, // default for platform (use this!)
        WinNT35,     // sends smoke signals to authority
        WinNT40,     // uses NTLM
        WinNT50      // negotiates Kerb or NTLM
    }
    // ReSharper restore InconsistentNaming
    // ReSharper restore UnusedMember.Local

    /// <summary>
    /// Class to allow running a segment of code under a given user login context
    /// </summary>
    /// <param name="user">domain\user</param>
    /// <param name="password">user's domain password</param>
    public Impersonation2(string domain, string username, string password)
    {
        var token = ValidateParametersAndGetFirstLoginToken(username, domain, password);

        var duplicateToken = IntPtr.Zero;
        try
        {
            if (DuplicateToken(token, 2, ref duplicateToken) == 0)
            {
                throw new Exception("DuplicateToken call to reset permissions for this token failed");
            }

            var identityForLoggedOnUser = new WindowsIdentity(duplicateToken);
            _impersonatedUserContext = identityForLoggedOnUser.Impersonate();
            if (_impersonatedUserContext == null)
            {
                throw new Exception("WindowsIdentity.Impersonate() failed");
            }
        }
        finally
        {
            if (token != IntPtr.Zero)
                CloseHandle(token);
            if (duplicateToken != IntPtr.Zero)
                CloseHandle(duplicateToken);
        }
    }

    private static IntPtr ValidateParametersAndGetFirstLoginToken(string domain, string username, string password)
    {


        if (!RevertToSelf())
        {
            throw new Exception("RevertToSelf call to remove any prior impersonations failed");
            ErrorLogger.LogEvent("RevertToSelf call to remove any prior impersonations failed", System.Diagnostics.EventLogEntryType.Error, "");

        }

        IntPtr token;

        var result = LogonUser(domain, username,
                               password,
                               LogonSessionType.Interactive,
                               LogonProvider.Default,
                               out token);
        if (!result)
        {
            var errorCode = Marshal.GetLastWin32Error();
            ErrorLogger.LogEvent(string.Format("Could not impersonate the elevated user.  LogonUser: {2}\\{1} returned error code: {0}.", errorCode, username, domain), System.Diagnostics.EventLogEntryType.Error, "");
            throw new Exception("Logon for user " + username + " failed.");
        }
        return token;
    }

    public void Dispose()
    {
        // Stop impersonation and revert to the process identity
        if (_impersonatedUserContext != null)
        {
            _impersonatedUserContext.Undo();
            _impersonatedUserContext = null;
        }
    }

Обновить

Это отлично работает, если я просто запускаю, если я просто выполняю его. Но когда он работает как служба, он не будет работать.

Обновление 2

Я не получаю отказ в доступе из Process.Start, когда я изменяю имя для входа в систему на LogonSessionType.NewCredentials и удаляю кредитные данные из процесса. Но теперь я вижу ошибку при запуске команды robocopy. Когда у меня есть учетные данные для процесса, он не создает файл журнала из команды robocopy

Ошибка

2016/07/16 09:19:12 ERROR 5 (0x00000005) 
Accessing Source Directory \\[server]\[path]\
Access is denied.

Изменить

var result = LogonUser(domain, username,
   password,
   LogonSessionType.NewCredentials,
   LogonProvider.Default,
   out token);

Обновление 3

Функции копирования и перемещения работают. Но создание подпроцесса - нет. Я играл с CreateProcessAsUser, как предложила Хэри Джонстон.


person H20rider    schedule 08.07.2016    source источник
comment
Вы говорите, что ваша служба работает как Local System, а ваша служба запускает другой процесс, используя учетную запись пользователя домена?   -  person wablab    schedule 09.07.2016
comment
Если да, то этот вопрос, возможно, является дубликатом stackoverflow.com / questions / 559719 /   -  person wablab    schedule 09.07.2016
comment
Какая строка кода вызывает ошибку отказа в доступе?   -  person Harry Johnston    schedule 09.07.2016
comment
process = Process.Start (startInfo);   -  person H20rider    schedule 09.07.2016
comment
@wablab, может ты и прав. Но этот вопрос пытался выдать себя за другое лицо без имени пользователя и пароля. Кроме того, новый процесс, похоже, вызывает некоторые проблемы. на основе ответа Франси Пенов, мне не хватает OpenProcessToken,   -  person H20rider    schedule 09.07.2016
comment
Погодите, вы пытаетесь выдать себя за пользователя домена или пытаетесь запустить процесс как пользователь домена? Это разные вещи.   -  person Harry Johnston    schedule 16.07.2016
comment
@ Гарри Джонстон. Конечная цель - заставить локальную систему запускать службу и все, что нужно олицетворять. Т.е. копировать файлы, запускать процесс и т. Д.   -  person H20rider    schedule 16.07.2016
comment
Запуск дочернего процесса (с разными учетными данными) сложен для службы. К тому же это очень неэффективный способ решения большинства задач. Есть несколько вещей, которые вы не можете сделать с олицетворением, но не многие, поэтому, если у вас нет конкретной причины, по которой вам нужен дочерний процесс, я настоятельно рекомендую вам просто использовать олицетворение.   -  person Harry Johnston    schedule 17.07.2016
comment
Если вы действительно хотите запустить дочерний процесс, у Microsoft есть образец кода в MSDN, см. Запуск интерактивного клиентского процесса на C ++. В моем ответе здесь обсуждается это, а также предоставляется образец кода, демонстрирующий пользователю образец кода Microsoft в службе. Вам нужно будет перенести код Microsoft на C #, если вы не можете найти где-нибудь эквивалентный сторонний код.   -  person Harry Johnston    schedule 17.07.2016
comment
... о, и вам, вероятно, также следует поместить дочерний процесс в задание, настроенное так, чтобы он умирал, когда умирает родительский. В противном случае служба может быть остановлена, но дочерний процесс все еще работает, вызывая путаницу и, возможно, нарушая работу, если служба затем перезапускается.   -  person Harry Johnston    schedule 17.07.2016
comment
Дополнение: очевидно, если вы используете CreateProcessAsUser, вам действительно не нужно возиться с разрешениями оконной станции. Я не тестировал это, но если предположить, что это правда, это должно упростить задачу.   -  person Harry Johnston    schedule 18.07.2016
comment
@HarryJohnston Спасибо. Я смотрел в этом направлении, но у меня это не сработало   -  person H20rider    schedule 18.07.2016
comment
AFAIK, нет возможности выдать себя за пользователя, не имея его учетных данных. В большинстве случаев настраивается учетная запись с правильными разрешениями и без истечения срока действия пароля для запущенных служб, которые в этом нуждаются.   -  person Terry Carmen    schedule 20.07.2016
comment
Я не об этом спрашивал. У меня есть полномочия.   -  person H20rider    schedule 20.07.2016
comment
@ H20rider, настоятельно рекомендую прочитать Запуск интерактивного процесса из службы Windows в Windows Vista и более поздних версиях. В этой статье рассказывается о многих проблемах.   -  person Artavazd Balayan    schedule 20.07.2016
comment
@ H20rider, не могли бы вы обновить свой код? Что ты сейчас делаешь?   -  person Artavazd Balayan    schedule 20.07.2016


Ответы (1)


Я смог заставить его работать.

Для нормального олицетворения я использовал следующий код

public class Impersonation : IDisposable
{
    private WindowsImpersonationContext _impersonatedUserContext;

    #region FUNCTIONS (P/INVOKE)

    // Declare signatures for Win32 LogonUser and CloseHandle APIs
    [DllImport("advapi32.dll", SetLastError = true)]
    static extern bool LogonUser(
      string principal,
      string authority,
      string password,
      LogonSessionType logonType,
      LogonProvider logonProvider,
      out IntPtr token);

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

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    static extern int DuplicateToken(IntPtr hToken,
        int impersonationLevel,
        ref IntPtr hNewToken);

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    static extern bool RevertToSelf();

    #endregion

    #region ENUMS

    enum LogonSessionType : uint
    {
        Interactive = 2,
        Network,
        Batch,
        Service,
        NetworkCleartext = 8,
        NewCredentials
    }

    enum LogonProvider : uint
    {
        Default = 0, // default for platform (use this!)
        WinNT35,     // sends smoke signals to authority
        WinNT40,     // uses NTLM
        WinNT50      // negotiates Kerb or NTLM
    }

    #endregion


    /// <summary>
    /// Class to allow running a segment of code under a given user login context
    /// </summary>
    /// <param name="user">domain\user</param>
    /// <param name="password">user's domain password</param>
    public Impersonation(string domain, string username, string password)
    {
        var token = ValidateParametersAndGetFirstLoginToken(username, domain, password);

        var duplicateToken = IntPtr.Zero;
        try
        {
            if (DuplicateToken(token, 2, ref duplicateToken) == 0)
            {


                throw new InvalidOperationException("DuplicateToken call to reset permissions for this token failed");
            }

            var identityForLoggedOnUser = new WindowsIdentity(duplicateToken);
            _impersonatedUserContext = identityForLoggedOnUser.Impersonate();
            if (_impersonatedUserContext == null)
            {
                throw new InvalidOperationException("WindowsIdentity.Impersonate() failed");
            }
        }
        finally
        {
            if (token != IntPtr.Zero)
                CloseHandle(token);
            if (duplicateToken != IntPtr.Zero)
                CloseHandle(duplicateToken);
        }
    }

    private static IntPtr ValidateParametersAndGetFirstLoginToken(string domain, string username, string password)
    {


        if (!RevertToSelf())
        {
            ErrorLogger.LogEvent("RevertToSelf call to remove any prior impersonations failed", System.Diagnostics.EventLogEntryType.Error, "");

            throw new InvalidOperationException("RevertToSelf call to remove any prior impersonations failed");

        }

        IntPtr token;

        var result = LogonUser(domain, username,
                               password,
                               LogonSessionType.NewCredentials,
                               LogonProvider.Default,
                               out token);
        if (!result)
        {
            var errorCode = Marshal.GetLastWin32Error();
            ErrorLogger.LogEvent(string.Format("Could not impersonate the elevated user.  LogonUser: {2}\\{1} returned error code: {0}.", errorCode, username, domain), System.Diagnostics.EventLogEntryType.Error, "");
            throw new InvalidOperationException("Logon for user " + username + " failed.");
        }
        return token;
    }

    public void Dispose()
    {
        // Stop impersonation and revert to the process identity
        if (_impersonatedUserContext != null)
        {
            _impersonatedUserContext.Undo();
            _impersonatedUserContext = null;
        }
    }
}

Для его запуска делаю следующее:

            FileInfo fi = new FileInfo(logfile);
            using (var imp = new Impersonation(Settings.ImpersonateUser.AccountDomain, Settings.ImpersonateUser.AccountName, Settings.ImpersonateUser.AccountPassword))
            {
                if (File.Exists(filename))
                    File.Delete(filename);
                fi.MoveTo(filename);
            }

Для запуска консольных команд я использовал следующий код.

public class CreateProcess
{

    #region Constants

    const UInt32 INFINITE = 0xFFFFFFFF;
    const UInt32 WAIT_FAILED = 0xFFFFFFFF;

    #endregion


    #region ENUMS

    [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
    }

    #endregion


    #region Structs

    [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 Int32 dwFlags;
        public Int16 wShowWindow;
        public Int16 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 Int32 dwProcessId;
        public Int32 dwThreadId;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct SECURITY_ATTRIBUTES
    {
        public int nLength;
        public unsafe byte* lpSecurityDescriptor;
        public int bInheritHandle;
    }

    public enum TOKEN_TYPE
    {
        TokenPrimary = 1,
        TokenImpersonation
    }

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

    #endregion


    #region FUNCTIONS (P/INVOKE)

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    static extern bool RevertToSelf();

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    static extern int DuplicateToken(IntPtr hToken,
        int impersonationLevel,
        ref IntPtr hNewToken);


    [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern Boolean LogonUser
    (
        String UserName,
        String Domain,
        String Password,
        LogonType dwLogonType,
        LogonProvider dwLogonProvider,
        out IntPtr phToken
    );


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





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

    [DllImport("kernel32", SetLastError = true)]
    public static extern Boolean CloseHandle(IntPtr handle);

    #endregion

    #region Functions

    public static int LaunchCommand(string command, string domain, string account, string password)
    {
        int ProcessId = -1;
        PROCESS_INFORMATION processInfo = new PROCESS_INFORMATION();
        STARTUPINFO startInfo = new STARTUPINFO();
        Boolean bResult = false;

        UInt32 uiResultWait = WAIT_FAILED;

        var token = ValidateParametersAndGetFirstLoginToken(domain, account, password);

        var duplicateToken = IntPtr.Zero;
        try
        {

            startInfo.cb = Marshal.SizeOf(startInfo);
            //  startInfo.lpDesktop = "winsta0\\default";

            bResult = CreateProcessAsUser(
                token,
                null,
                command,
                IntPtr.Zero,
                IntPtr.Zero,
                false,
                0,
                IntPtr.Zero,
                null,
                ref startInfo,
                out processInfo
            );

            if (!bResult) { throw new Exception("CreateProcessAsUser error #" + Marshal.GetLastWin32Error()); }

            // Wait for process to end
            uiResultWait = WaitForSingleObject(processInfo.hProcess, INFINITE);

            ProcessId = processInfo.dwProcessId;

            if (uiResultWait == WAIT_FAILED) { throw new Exception("WaitForSingleObject error #" + Marshal.GetLastWin32Error()); }

        }
        finally
        {
            if (token != IntPtr.Zero)
                CloseHandle(token);
            if (duplicateToken != IntPtr.Zero)
                CloseHandle(duplicateToken);
            CloseHandle(processInfo.hProcess);
            CloseHandle(processInfo.hThread);
        }

        return ProcessId;
    }


    private static IntPtr ValidateParametersAndGetFirstLoginToken(string domain, string username, string password)
    {


        if (!RevertToSelf())
        {
            ErrorLogger.LogEvent("RevertToSelf call to remove any prior impersonations failed", System.Diagnostics.EventLogEntryType.Error, "");
            throw new Exception("RevertToSelf call to remove any prior impersonations failed");
        }

        IntPtr token;

        var result = LogonUser(username,
                               domain,
                               password,
                               LogonType.LOGON32_LOGON_INTERACTIVE,
                               LogonProvider.LOGON32_PROVIDER_DEFAULT,
                               out token);
        if (!result)
        {
            var errorCode = Marshal.GetLastWin32Error();
            ErrorLogger.LogEvent(string.Format("Could not impersonate the elevated user.  LogonUser: {2}\\{1} returned error code: {0}.", errorCode, username, domain), System.Diagnostics.EventLogEntryType.Error, "");
            throw new Exception("Logon for user " + username + " failed.");
        }
        return token;
    }

    #endregion

}

и выполнив его, выполнив следующие

string commandLine = "Robocopy " + args;

ProcessId = CreateProcess.LaunchCommand(commandLine, ImperDomain, ImperUsername, ImperPassword);

Мне также пришлось внести некоторые изменения в локальную политику, так как я хочу копировать разрешения в robocopy.

Спасибо за все комментарии и помощь.

person H20rider    schedule 20.07.2016
comment
Вы в Windows 10? Я использовал его и продолжаю получать доступ запрещен с вашим кодом. - person freedeveloper; 02.03.2017
comment
Я не использую Windows 10. Какой тип команды вы используете. И пробовали ли вы запустить эту команду на машине с Windows 10 с тем пользователем, которого вы выдаете за себя? - person H20rider; 03.03.2017
comment
Ваш класс олицетворения отлично работал в моей службе Windows, спасибо! - person Sal; 30.04.2018