You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

CreateProcessAsUser()调用成功但无进程实际启动的问题排查

问题根因
  • 核心错误是目标会话选择错误:从日志可见所有SessionId取值均为0,这是Windows Vista之后引入的Session 0隔离机制专属的系统服务会话,该会话不支持任何交互UI,没有登录的普通用户。你调用WTSGetActiveConsoleSessionId()直接拿到0值说明当前物理控制台没有活动登录用户,或是API调用时机不对,基于Session 0的winlogon进程复制token创建的进程,只会运行在不可见的系统会话中,要么因为桌面访问权限不足直接崩溃,要么用户完全看不到进程窗口。
  • Token复制参数错误:调用DuplicateTokenEx时使用了SecurityIdentification模拟级别,该级别仅允许查询token的身份信息,不具备模拟用户执行操作、访问用户安全资源的权限,完全不满足CreateProcessAsUser的权限要求。
  • 缺少Token会话绑定步骤:复制得到的主token没有显式绑定到目标交互会话ID,即使拿到了正确会话的winlogon token,也无法保证进程启动在目标用户会话内。
  • 资源权限缺失:没有给启动进程使用的token授予交互窗口站WinSta0\Default的访问权限,进程初始化阶段无法获取桌面句柄会直接触发退出,根本不会执行到程序自身的逻辑,这也是你挂起创建进程后附加调试器仍直接消失的原因——崩溃发生在进程加载器、系统初始化阶段,早于程序入口点执行。
  • 启动参数不完整:CreateProcessAsUser调用时传入的工作目录为null,也没有生成目标用户的环境块,新进程会沿用服务会话的环境变量,工作目录错误会导致程序找不到依赖DLL、配置文件,触发启动即崩溃。
  • 权限请求方式错误:调用OpenProcess、OpenProcessToken时使用MAXIMUM_ALLOWED权限,该取值会根据系统安全策略动态计算权限,在开启UAC、高安全等级配置的系统上会直接触发访问拒绝,导致拿到的句柄权限不足。
修复方案

按以下步骤修改代码即可解决问题:

  • 正确枚举活动交互会话:不要直接依赖WTSGetActiveConsoleSessionId的返回值,使用WTSEnumerateSessions遍历所有系统会话,筛选状态为WTSActive的会话获取其ID,若返回的会话ID为0直接终止逻辑,不要继续在系统会话创建进程。
  • 修正Token复制参数:将DuplicateTokenEx的模拟级别从SecurityIdentification改为SecurityImpersonation,同时明确指定需要的token权限,不要用MAXIMUM_ALLOWED。
  • 绑定Token到目标会话:复制得到主token后,调用SetTokenInformation传入TokenSessionId信息类,将token的关联会话ID设置为前面找到的活动交互会话ID。
  • 配置桌面访问权限:启动进程前修改WinSta0\Default桌面的DACL,给启动进程使用的token对应用户授予桌面的通用访问权限,避免进程初始化时因权限不足崩溃。
  • 补全启动参数:调用CreateEnvironmentBlock生成目标用户会话的环境块,创建进程时传入CREATE_UNICODE_ENVIRONMENT标志,同时传入应用程序所在目录作为工作目录,不要传null;进程启动完成后调用DestroyEnvironmentBlock释放环境块内存。
  • 明确指定所需权限:调用OpenProcess时请求PROCESS_QUERY_INFORMATION | PROCESS_DUP_HANDLE权限,调用OpenProcessToken时请求TOKEN_DUPLICATE | TOKEN_QUERY | TOKEN_ASSIGN_PRIMARY | TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID权限,这些是完成操作所需的最小权限,不会触发不必要的安全拦截。
关键修正代码片段
// 1. 枚举查找活动交互会话
IntPtr pSessionList = IntPtr.Zero;
int sessionCount = 0;
int targetSessionId = 0;
WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, ref pSessionList, ref sessionCount);
int sessionInfoSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));
long sessionPtr = pSessionList.ToInt64();
for (int i = 0; i < sessionCount; i++)
{
    WTS_SESSION_INFO sessionInfo = Marshal.PtrToStructure<WTS_SESSION_INFO>((IntPtr)sessionPtr);
    if (sessionInfo.State == WTS_CONNECTSTATE_CLASS.WTSActive)
    {
        targetSessionId = sessionInfo.SessionId;
        break;
    }
    sessionPtr += sessionInfoSize;
}
WTSFreeMemory(pSessionList);
// 无活动交互会话直接返回
if (targetSessionId == 0) return false;

// 2. 复制Token时使用正确的模拟级别
if (!DuplicateTokenEx(hPToken,
    TOKEN_DUPLICATE | TOKEN_QUERY | TOKEN_ASSIGN_PRIMARY | TOKEN_ADJUST_DEFAULT | TOKEN_ADJUST_SESSIONID,
    ref sa,
    SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation,
    TOKEN_TYPE.TokenPrimary,
    out hUserTokenDup))
{
    // 错误处理逻辑
}

// 3. 将Token绑定到目标交互会话
uint sessionIdVal = (uint)targetSessionId;
SetTokenInformation(hUserTokenDup, 
    TOKEN_INFORMATION_CLASS.TokenSessionId, 
    ref sessionIdVal, 
    (uint)Marshal.SizeOf<uint>());

// 4. 生成用户环境块
IntPtr envBlock = IntPtr.Zero;
CreateEnvironmentBlock(out envBlock, hUserTokenDup, false);

// 5. 补全启动参数
STARTUPINFO si = new STARTUPINFO();
si.cb = (int)Marshal.SizeOf(si);
si.lpDesktop = @"WinSta0\Default";
uint createFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT;
string exeDir = Path.GetDirectoryName(K_ExePath);

if (!CreateProcessAsUser(hUserTokenDup,
    null,
    K_ExePath,
    ref sa,
    ref sa,
    false,
    createFlags,
    envBlock,
    exeDir,
    ref si,
    out procInfo))
{
    // 错误处理逻辑
}

// 释放环境块
DestroyEnvironmentBlock(envBlock);
排查辅助手段

创建进程成功后,可以获取进程句柄等待进程退出,通过GetExitCodeProcess拿到退出码:如果退出码是0xC0000142说明是桌面/窗口站权限不足或DLL加载失败,如果是0xC0000022是权限校验失败,可以快速定位具体问题。

内容的提问来源于stack exchange,提问作者Thomas Balduz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 11:45:34