CreateProcessAsUser return 成功但进程退出并出现错误 3228369022
CreateProcessAsUser return success but process exit with error 3228369022
我有 3 个应用程序 运行:
应用程序 A:
本地服务器 运行 作为本地系统下的后台服务,具有与桌面交互的权限(至少是 Advanced Installer 所说的)
应用程序 B:
一款桌面截屏软件
应用程序 C:
作为登录用户的托盘图标 运行。
我想要实现的是能够捕获桌面 UAC 提示和登录屏幕,所以我是这样实现的:
1. 应用程序 C 调用 WTSGetActiveConsoleSessionId()(其中 return 2),
抓取桌面指针:
OpenInputDesktop(0, false, (uint)(ACCESS_MASK.DESKTOP_CREATEMENU | ACCESS_MASK.DESKTOP_CREATEWINDOW | ACCESS_MASK.DESKTOP_ENUMERATE | ACCESS_MASK.DESKTOP_HOOKCONTROL | ACCESS_MASK.DESKTOP_WRITEOBJECTS | ACCESS_MASK.DESKTOP_READOBJECTS | ACCESS_MASK.DESKTOP_SWITCHDESKTOP))
并将两者发送到应用程序 A(本地服务器)
2. 服务器运行以下代码:
static void CreateApplicationBProcess()
{
IntPtr hToken = IntPtr.Zero;
IntPtr P = IntPtr.Zero;
/*if (!OpenProcessToken(OpenProcess(Process.GetProcessesByName("winlogon").First(), ProcessAccessFlags.All), 0x2000000, out hToken))
{
throw new Exception("OpenProcessToken error #" + Marshal.GetLastWin32Error());
}*/
if (!WTSQueryUserToken(WTSGetActiveConsoleSessionId(), out hToken))
{
throw new Exception("WTSQueryUserToken error #" + Marshal.GetLastWin32Error());
}
IntPtr duplicatedTokenHandle = IntPtr.Zero;
if(!DuplicateTokenEx(hToken, 0, IntPtr.Zero, (int)SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, (int)TOKEN_TYPE.TokenPrimary, ref duplicatedTokenHandle))
{
throw new Exception("DuplicateTokenEx error #" + Marshal.GetLastWin32Error());
}
uint sessionId = SessionId;
if(!SetTokenInformation(duplicatedTokenHandle, TOKEN_INFORMATION_CLASS.TokenSessionId, ref sessionId, (uint)IntPtr.Size))
{
throw new Exception("SetTokenInformation error #" + Marshal.GetLastWin32Error());
}
if (CreateEnvironmentBlock(out P, duplicatedTokenHandle, false))
{
Win32.PROCESS_INFORMATION processInfo = new Win32.PROCESS_INFORMATION();
Win32.STARTUPINFO startInfo = new Win32.STARTUPINFO();
bool bResult = false;
uint uiResultWait = Win32.WAIT_FAILED;
try
{
// Create process
startInfo.cb = Marshal.SizeOf(startInfo);
startInfo.lpDesktop = "winsta0\default";
startInfo.dwFlags = 0x00000001;
startInfo.wShowWindow = (short)SW.SW_HIDE;
SetCurrentDirectory(GetDirectory());
Environment.CurrentDirectory = GetDirectory();
bResult = Win32.CreateProcessAsUser(duplicatedTokenHandle, Path.Combine(GetDirectory(), "ApplicationB.exe"), string.Format("{0}", (uint)DesktopPtr), IntPtr.Zero, IntPtr.Zero, false,
CREATE_UNICODE_ENVIRONMENT | CREATE_NO_WINDOW, P, GetDirectory(), ref startInfo, out processInfo);
if (!bResult)
{
throw new Exception("CreateProcessAsUser error #" + Marshal.GetLastWin32Error());
}
else
{
//IntPtr pOpenThread = OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)processInfo.dwThreadId);
ResumeThread(processInfo.hThread);
}
// Wait for process to end
uiResultWait = Win32.WaitForSingleObject(processInfo.hProcess, Win32.INFINITE);
if (uiResultWait == Win32.WAIT_FAILED)
{
throw new Exception("WaitForSingleObject error #" + Marshal.GetLastWin32Error());
}
GetExitCodeProcess(processInfo.hProcess, out uint ExitCode);
Console.WriteLine("Exit code: " + ExitCode);
}
finally
{
// Close all handles
Win32.CloseHandle(hToken);
Win32.CloseHandle(processInfo.hProcess);
Win32.CloseHandle(processInfo.hThread);
DestroyEnvironmentBlock(P);
}
}
else
{
throw new Exception("CreateEnvironmentBlock error #" + Marshal.GetLastWin32Error());
}
}
private static string GetDirectory()
{
return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
}
3. 现在 CreateProcessAsUser return 成功,但进程正在退出,错误代码为 3228369022 (C06D007E)。
我用 ProcMon 进行了监控,加载图像时没有任何错误,但是并不是我的所有 dll 都已加载。
错误来源是
startInfo.lpDesktop = "winsta0\default";
行,因为使用了CreateProcessAsUserW(从问题代码中看不到)
所以必须
startInfo.lpDesktop = L"winsta0\default";
代码中还包含其他几个错误:
我们不需要打开 winlogon.exe(或另一个硬编码的 exe 名称令牌),如果我们想使用系统令牌,我们可以打开自己的进程令牌并在这里使用它.
不需要 DuplicateTokenEx
+设置 TokenSessionId
用于 WTSQueryUserToken
返回的令牌 - 因为 WTSGetActiveConsoleSessionId()
已经 在这个token(我们通过这个session id获取)
ResumeThread
此处毫无意义的调用 - 因为当且仅当 CREATE_SUSPENDED
标志在 CreateProcess
中使用时线程创建暂停
我有 3 个应用程序 运行:
应用程序 A: 本地服务器 运行 作为本地系统下的后台服务,具有与桌面交互的权限(至少是 Advanced Installer 所说的)
应用程序 B: 一款桌面截屏软件
应用程序 C: 作为登录用户的托盘图标 运行。
我想要实现的是能够捕获桌面 UAC 提示和登录屏幕,所以我是这样实现的:
1. 应用程序 C 调用 WTSGetActiveConsoleSessionId()(其中 return 2), 抓取桌面指针:
OpenInputDesktop(0, false, (uint)(ACCESS_MASK.DESKTOP_CREATEMENU | ACCESS_MASK.DESKTOP_CREATEWINDOW | ACCESS_MASK.DESKTOP_ENUMERATE | ACCESS_MASK.DESKTOP_HOOKCONTROL | ACCESS_MASK.DESKTOP_WRITEOBJECTS | ACCESS_MASK.DESKTOP_READOBJECTS | ACCESS_MASK.DESKTOP_SWITCHDESKTOP))
并将两者发送到应用程序 A(本地服务器)
2. 服务器运行以下代码:
static void CreateApplicationBProcess()
{
IntPtr hToken = IntPtr.Zero;
IntPtr P = IntPtr.Zero;
/*if (!OpenProcessToken(OpenProcess(Process.GetProcessesByName("winlogon").First(), ProcessAccessFlags.All), 0x2000000, out hToken))
{
throw new Exception("OpenProcessToken error #" + Marshal.GetLastWin32Error());
}*/
if (!WTSQueryUserToken(WTSGetActiveConsoleSessionId(), out hToken))
{
throw new Exception("WTSQueryUserToken error #" + Marshal.GetLastWin32Error());
}
IntPtr duplicatedTokenHandle = IntPtr.Zero;
if(!DuplicateTokenEx(hToken, 0, IntPtr.Zero, (int)SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, (int)TOKEN_TYPE.TokenPrimary, ref duplicatedTokenHandle))
{
throw new Exception("DuplicateTokenEx error #" + Marshal.GetLastWin32Error());
}
uint sessionId = SessionId;
if(!SetTokenInformation(duplicatedTokenHandle, TOKEN_INFORMATION_CLASS.TokenSessionId, ref sessionId, (uint)IntPtr.Size))
{
throw new Exception("SetTokenInformation error #" + Marshal.GetLastWin32Error());
}
if (CreateEnvironmentBlock(out P, duplicatedTokenHandle, false))
{
Win32.PROCESS_INFORMATION processInfo = new Win32.PROCESS_INFORMATION();
Win32.STARTUPINFO startInfo = new Win32.STARTUPINFO();
bool bResult = false;
uint uiResultWait = Win32.WAIT_FAILED;
try
{
// Create process
startInfo.cb = Marshal.SizeOf(startInfo);
startInfo.lpDesktop = "winsta0\default";
startInfo.dwFlags = 0x00000001;
startInfo.wShowWindow = (short)SW.SW_HIDE;
SetCurrentDirectory(GetDirectory());
Environment.CurrentDirectory = GetDirectory();
bResult = Win32.CreateProcessAsUser(duplicatedTokenHandle, Path.Combine(GetDirectory(), "ApplicationB.exe"), string.Format("{0}", (uint)DesktopPtr), IntPtr.Zero, IntPtr.Zero, false,
CREATE_UNICODE_ENVIRONMENT | CREATE_NO_WINDOW, P, GetDirectory(), ref startInfo, out processInfo);
if (!bResult)
{
throw new Exception("CreateProcessAsUser error #" + Marshal.GetLastWin32Error());
}
else
{
//IntPtr pOpenThread = OpenThread(ThreadAccess.SUSPEND_RESUME, false, (uint)processInfo.dwThreadId);
ResumeThread(processInfo.hThread);
}
// Wait for process to end
uiResultWait = Win32.WaitForSingleObject(processInfo.hProcess, Win32.INFINITE);
if (uiResultWait == Win32.WAIT_FAILED)
{
throw new Exception("WaitForSingleObject error #" + Marshal.GetLastWin32Error());
}
GetExitCodeProcess(processInfo.hProcess, out uint ExitCode);
Console.WriteLine("Exit code: " + ExitCode);
}
finally
{
// Close all handles
Win32.CloseHandle(hToken);
Win32.CloseHandle(processInfo.hProcess);
Win32.CloseHandle(processInfo.hThread);
DestroyEnvironmentBlock(P);
}
}
else
{
throw new Exception("CreateEnvironmentBlock error #" + Marshal.GetLastWin32Error());
}
}
private static string GetDirectory()
{
return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
}
3. 现在 CreateProcessAsUser return 成功,但进程正在退出,错误代码为 3228369022 (C06D007E)。
我用 ProcMon 进行了监控,加载图像时没有任何错误,但是并不是我的所有 dll 都已加载。
错误来源是
startInfo.lpDesktop = "winsta0\default";
行,因为使用了CreateProcessAsUserW(从问题代码中看不到)
所以必须
startInfo.lpDesktop = L"winsta0\default";
代码中还包含其他几个错误:
我们不需要打开 winlogon.exe(或另一个硬编码的 exe 名称令牌),如果我们想使用系统令牌,我们可以打开自己的进程令牌并在这里使用它.
不需要 DuplicateTokenEx
+设置 TokenSessionId
用于 WTSQueryUserToken
返回的令牌 - 因为 WTSGetActiveConsoleSessionId()
已经 在这个token(我们通过这个session id获取)
ResumeThread
此处毫无意义的调用 - 因为当且仅当 CREATE_SUSPENDED
标志在 CreateProcess