从 C# 调用 CreateProcessAsUser
我一直在尝试使用 Windows API 的 CreateProcessAsUser 函数在特定用户的上下文中创建一个新进程,但似乎遇到了相当严重的安全问题...
在我解释之前更进一步,这是我当前用于启动新进程的代码(控制台进程 - 具体来说是 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 构造函数中删除 message 参数发现的。不可否认,我这里的错误处理代码可能不是最好的,但这有点无关紧要不过,如果您愿意,欢迎您对此发表评论。)我真的很困惑在这种情况下出现这种模糊错误的原因。 MSDN 文档和各种论坛主题只给了我这么多建议,特别是考虑到此类错误的原因似乎多种多样,我不知道我需要修改哪一部分代码。 也许这只是我需要更改的一个参数,但据我所知,我可能进行了错误/不足的 WinAPI 调用。 让我非常困惑的是,使用普通 CreateProcess
函数(除了用户令牌参数之外的等效函数)的先前版本的代码运行得非常好。 据我了解,只需调用登录用户函数来接收适当的令牌句柄,然后复制它,以便将其传递给 CreateProcessAsUser 。
任何对代码修改的建议以及解释都将非常受欢迎。
我主要参考了 MSDN 文档(以及 C# function/strut/enum 声明的 PInvoke.net )。 特别是以下几页的“备注”部分似乎包含大量信息,其中一些可能很重要但我却无法理解:
编辑
我刚刚尝试了米奇的建议,但不幸的是旧错误刚刚被新错误取代:“系统找不到指定的文件。” (错误代码 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”)并独立传递域作为第二个参数,一切都无济于事(相同的错误)。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
啊...似乎我遇到了 WinAPI 互操作编程中最大的陷阱之一。 另外,在这种情况下,发布我的函数声明的代码是一个明智的主意。
不管怎样,我所需要做的就是向指定
CharSet = CharSet.Unicode
的函数的 DllImport 属性添加一个参数。 这对 CreateProcessWithLogonW 和 CreateProcessWithTokenW 函数都起到了作用。 我想我终于意识到函数名称的 W 后缀引用了 Unicode,并且我需要在 C# 中显式指定这一点! 以下是正确的函数声明,以防有人感兴趣:Ahh... seems liker I've been caught out by one of the biggest gotchas in WinAPI interop programming. Also, Posting the code for my function declarations would have been a wise idea in this case.
Anyway, all that I needed to do was add an argument to the DllImport attribute of the function specifying
CharSet = CharSet.Unicode
. This did the trick for both theCreateProcessWithLogonW
andCreateProcessWithTokenW
functions. I guess it finally just hit me that the W suffix of the function names referred to Unicode and that I needed to explicitly specify this in C#! Here are the correct function declarations in case anyone is interested:来自此处:
请参阅此博客文章 如何调用CreateProcessWithLogonW& .NET 中的 CreateProcessAsUser
From here:
See this blog post How to call CreateProcessWithLogonW & CreateProcessAsUser in .NET
乔纳森·佩珀斯
提供了这段很棒的代码来解决我的问题
http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/0c0ca087-5e7b-4046-93cb-c7b3e48d0dfb?ppud=4
Jonathan Peppers
provided this great piece of code that fixed my issues
http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/0c0ca087-5e7b-4046-93cb-c7b3e48d0dfb?ppud=4