Вызов CreateProcessAsUser из C#
Я пытался создать новый процесс в контексте конкретного пользователя, используя CreateProcessAsUser
функция Windows API, но, похоже, сталкивается с довольно неприятной проблемой безопасности...
прежде чем я объясню дальше, вот код, который я в настоящее время использую для запуска нового процесса (консольный процесс - 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("", envVars.ToArray()) + "";
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)
(сообщение об ошибке было обнаружено путем удаления параметра message из конструктора Win32Exception. По общему признанию, мой код обработки ошибок здесь может быть не лучшим, но это несколько неуместное дело. Однако вы можете прокомментировать это, если хотите.) Я действительно очень смущен причиной этой неопределенной ошибки в этой ситуации. Документация MSDN и различные темы форума дали мне так много советов, и особенно учитывая, что причины таких ошибок, по-видимому, широко варьируются, я понятия не имею, какой раздел кода мне нужно изменить. Возможно, это просто один параметр, который мне нужно изменить, но я могу ошибаться/недостаточно WinAPI для всех знать. Что меня сильно смущает, так это то, что в предыдущей версии кода используется plain CreateProcess
функция (эквивалентная, за исключением параметра токена пользователя) работала отлично. Как я понимаю, необходимо только вызвать функцию пользователя входа в систему, чтобы получить соответствующий маркер, а затем дублировать его, чтобы его можно было передать CreateProcessAsUser
.
любые предложения по изменениям кода, а также объяснения были бы очень добро пожаловать.
Примечания
Я в основном ссылаюсь на документы MSDN (а также PInvoke.net для объявления функции/стойки/перечисления C#). Следующие страницы, в частности, кажется, имеют много информации в разделах замечаний, некоторые из которых могут быть важными и ускользающими от меня:
редактировать
Я только что попробовал предложение Митча, но, к сожалению, старая ошибка только что была заменена новой: "система не может найти указанный файл."(код ошибки 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 ответов
Аааа... кажется, liker я был пойман одним из самых больших gotchas в программировании взаимодействия 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);
с здесь:
Как правило, процесс, который вызывает Функция CreateProcessAsUser должна иметь в SE_ASSIGNPRIMARYTOKEN_NAME и Привилегии SE_INCREASE_QUOTA_NAME. Если эта функция завершается с ERROR_PRIVILEGE_NOT_HELD (1314), использовать функция CreateProcessWithLogonW вместо. CreateProcessWithLogonW не требует особых привилегий, но указанная учетная запись пользователя должна быть разрешено входить в систему в интерактивном режиме. Как правило, лучше использовать CreateProcessWithLogonW для создания процесс с альтернативными учетными данными.
см. это сообщение в блоге как вызвать CreateProcessWithLogonW & CreateProcessAsUser в .NET