Вызов CreateProcessAsUser из C # - PullRequest
       35

Вызов CreateProcessAsUser из C #

9 голосов
/ 21 марта 2009

Я пытался создать новый процесс в контексте определенного пользователя, используя функцию CreateProcessAsUser API Windows, но, похоже, столкнулся с довольно неприятной проблемой безопасности ...

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

    private void StartProcess()
    {
        bool retValue;

        // Create startup info for new console process.
        var startupInfo = new STARTUPINFO();
        startupInfo.cb = Marshal.SizeOf(startupInfo);
        startupInfo.dwFlags = StartFlags.STARTF_USESHOWWINDOW;
        startupInfo.wShowWindow = _consoleVisible ? WindowShowStyle.Show : WindowShowStyle.Hide;
        startupInfo.lpTitle = this.ConsoleTitle ?? "Console";

        var procAttrs = new SECURITY_ATTRIBUTES();
        var threadAttrs = new SECURITY_ATTRIBUTES();
        procAttrs.nLength = Marshal.SizeOf(procAttrs);
        threadAttrs.nLength = Marshal.SizeOf(threadAttrs);

        // Log on user temporarily in order to start console process in its security context.
        var hUserToken = IntPtr.Zero;
        var hUserTokenDuplicate = IntPtr.Zero;
        var pEnvironmentBlock = IntPtr.Zero;
        var pNewEnvironmentBlock = IntPtr.Zero;

        if (!WinApi.LogonUser("UserName", null, "Password",
            LogonType.Interactive, LogonProvider.Default, out hUserToken))
            throw new Win32Exception(Marshal.GetLastWin32Error(), "Error logging on user.");

        var duplicateTokenAttrs = new SECURITY_ATTRIBUTES();
        duplicateTokenAttrs.nLength = Marshal.SizeOf(duplicateTokenAttrs);
        if (!WinApi.DuplicateTokenEx(hUserToken, 0, ref duplicateTokenAttrs,
            SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenPrimary,
            out hUserTokenDuplicate))
            throw new Win32Exception(Marshal.GetLastWin32Error(), "Error duplicating user token.");

        try
        {
            // Get block of environment vars for logged on user.
            if (!WinApi.CreateEnvironmentBlock(out pEnvironmentBlock, hUserToken, false))
                throw new Win32Exception(Marshal.GetLastWin32Error(),
                    "Error getting block of environment variables for user.");

            // Read block as array of strings, one per variable.
            var envVars = ReadEnvironmentVariables(pEnvironmentBlock);

            // Append custom environment variables to list.
            foreach (var var in this.EnvironmentVariables)
                envVars.Add(var.Key + "=" + var.Value);

            // Recreate environment block from array of variables.
            var newEnvironmentBlock = string.Join("\0", envVars.ToArray()) + "\0";
            pNewEnvironmentBlock = Marshal.StringToHGlobalUni(newEnvironmentBlock);

            // Start new console process.
            retValue = WinApi.CreateProcessAsUser(hUserTokenDuplicate, null, this.CommandLine,
                ref procAttrs, ref threadAttrs, false, CreationFlags.CREATE_NEW_CONSOLE |
                CreationFlags.CREATE_SUSPENDED | CreationFlags.CREATE_UNICODE_ENVIRONMENT,
                pNewEnvironmentBlock, null, ref startupInfo, out _processInfo);
            if (!retValue) throw new Win32Exception(Marshal.GetLastWin32Error(),
                "Unable to create new console process.");
        }
        catch
        {
            // Catch any exception thrown here so as to prevent any malicious program operating
            // within the security context of the logged in user.

            // Clean up.
            if (hUserToken != IntPtr.Zero)
            {
                WinApi.CloseHandle(hUserToken);
                hUserToken = IntPtr.Zero;
            }

            if (hUserTokenDuplicate != IntPtr.Zero)
            {
                WinApi.CloseHandle(hUserTokenDuplicate);
                hUserTokenDuplicate = IntPtr.Zero;
            }

            if (pEnvironmentBlock != IntPtr.Zero)
            {
                WinApi.DestroyEnvironmentBlock(pEnvironmentBlock);
                pEnvironmentBlock = IntPtr.Zero;
            }

            if (pNewEnvironmentBlock != IntPtr.Zero)
            {
                Marshal.FreeHGlobal(pNewEnvironmentBlock);
                pNewEnvironmentBlock = IntPtr.Zero;
            }

            throw;
        }
        finally
        {
            // Clean up.
            if (hUserToken != IntPtr.Zero)
                WinApi.CloseHandle(hUserToken);

            if (hUserTokenDuplicate != IntPtr.Zero)
                WinApi.CloseHandle(hUserTokenDuplicate);

            if (pEnvironmentBlock != IntPtr.Zero)
                WinApi.DestroyEnvironmentBlock(pEnvironmentBlock);

            if (pNewEnvironmentBlock != IntPtr.Zero)
                Marshal.FreeHGlobal(pNewEnvironmentBlock);
        }

        _process = Process.GetProcessById(_processInfo.dwProcessId);
    }

Ради этой проблемы проигнорируйте код, относящийся к переменным среды (я протестировал этот раздел независимо, и, похоже, он работает).

Теперь я получаю следующую ошибку (в строке после вызова CreateProcessAsUSer):

«Требуемая привилегия не удерживается клиентом» (код ошибки 1314)

(Сообщение об ошибке было обнаружено путем удаления параметра сообщения из конструктора Win32Exception. Конечно, мой код обработки ошибок здесь может быть не самым лучшим, но это несколько неуместно. Вы можете прокомментировать его, если хотите , однако.) Я действительно очень запутался относительно причины этой неопределенной ошибки в этой ситуации. Документация MSDN и различные ветки форума давали мне очень много советов, и особенно учитывая, что причины таких ошибок, как представляется, сильно различаются, я понятия не имею, какой раздел кода мне нужно изменить. Возможно, это просто один параметр, который мне нужно изменить, но я мог делать неправильные / недостаточные вызовы WinAPI для всего, что я знаю. Что меня сильно смущает, так это то, что предыдущая версия кода, использующая простую функцию CreateProcess (эквивалентную, кроме параметра токена пользователя), работала отлично. Как я понимаю, необходимо только вызвать пользовательскую функцию входа в систему, чтобы получить соответствующий маркер токена, а затем продублировать его, чтобы он мог быть передан CreateProcessAsUser.

Буду очень рад любым предложениям по модификации кода, а также объяснениям.

Примечания

Я в основном ссылался на документы MSDN (а также PInvoke.net для объявлений функции C # / strut / enum). Следующие страницы, в частности, содержат много информации в разделах «Замечания», некоторые из которых могут быть важными и ускользать от меня:

Редактировать

Я только что опробовал предложение Митча, но, к сожалению, старая ошибка была заменена новой: «Система не может найти указанный файл». (код ошибки 2)

Предыдущий вызов CreateProcessAsUser был заменен следующим:

retValue = WinApi.CreateProcessWithTokenW(hUserToken, LogonFlags.WithProfile, null,
    this.CommandLine, CreationFlags.CREATE_NEW_CONSOLE |
    CreationFlags.CREATE_SUSPENDED | CreationFlags.CREATE_UNICODE_ENVIRONMENT,
    pNewEnvironmentBlock, null, ref startupInfo, out _processInfo);

Обратите внимание, что в этом коде больше не используется дублирующий токен, а скорее оригинал, как это видно из документов MSDN.

А вот еще одна попытка использования CreateProcessWithLogonW. На этот раз ошибка «Ошибка входа в систему: неизвестное имя пользователя или неверный пароль» (код ошибки 1326)

retValue = WinApi.CreateProcessWithLogonW("Alex", null, "password",
    LogonFlags.WithProfile, null, this.CommandLine,
    CreationFlags.CREATE_NEW_CONSOLE | CreationFlags.CREATE_SUSPENDED |
    CreationFlags.CREATE_UNICODE_ENVIRONMENT, pNewEnvironmentBlock,
    null, ref startupInfo, out _processInfo);

Я также попытался указать имя пользователя в формате UPN («Alex @ Alex-PC») и передать домен независимо в качестве второго аргумента, но все безрезультатно (идентичная ошибка).

Ответы [ 3 ]

7 голосов
/ 22 марта 2009

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

В любом случае, все, что мне нужно было сделать, это добавить аргумент в атрибут DllImport функции, определяющей CharSet = CharSet.Unicode. Это помогло обеим функциям CreateProcessWithLogonW и CreateProcessWithTokenW. Полагаю, меня, наконец, поразило, что W-суффикс имен функций ссылается на Unicode и мне нужно было явно указать это в C #! Вот правильные объявления функций на случай, если кому-то будет интересно:

[DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool CreateProcessWithLogonW(string principal, string authority,
    string password, LogonFlags logonFlags, string appName, string cmdLine,
    CreationFlags creationFlags, IntPtr environmentBlock, string currentDirectory,
    ref STARTUPINFO startupInfo, out PROCESS_INFORMATION processInfo);

[DllImport("advapi32", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool CreateProcessWithTokenW(IntPtr hToken, LogonFlags dwLogonFlags,
    string lpApplicationName, string lpCommandLine, CreationFlags dwCreationFlags,
    IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFO lpStartupInfo,
    out PROCESS_INFORMATION lpProcessInformation);
6 голосов
/ 21 марта 2009

С здесь :

Как правило, процесс, который вызывает Функция CreateProcessAsUser должна иметь SE_ASSIGNPRIMARYTOKEN_NAME и Права доступа SE_INCREASE_QUOTA_NAME. Если эта функция не работает с ERROR_PRIVILEGE_NOT_HELD (1314), используйте функция CreateProcessWithLogonW вместо. CreateProcessWithLogonW не требует особых привилегий, но указанная учетная запись пользователя должна быть разрешено входить в систему в интерактивном режиме. Как правило, лучше всего использовать CreateProcessWithLogonW, чтобы создать процесс с альтернативными учетными данными.

См. Этот блог Как вызвать CreateProcessWithLogonW & CreateProcessAsUser в .NET

4 голосов
/ 03 декабря 2009

Джонатан Пепперс предоставил этот отличный кусок кода, который исправил мои проблемы

http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/0c0ca087-5e7b-4046-93cb-c7b3e48d0dfb?ppud=4

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...