使用AdvApi跨会话启动进程X时出现0xc06d007e异常问题
问题描述
我们有一个运行在系统会话中的服务,执行以下操作启动进程'X'时出现异常:
- 获取用户'B'的交互式登录令牌
- 将该令牌的会话ID修改为用户'A'的会话ID
- 使用修改后的令牌通过
CreateProcessAsUser启动进程
进程启动后立即抛出错误:未知软件异常(0xc06d007e),发生在应用程序位置0x0000000076855232
以下是实现逻辑的代码:
boolean res = Advapi32.INSTANCE.LogonUser( user, domain, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, userToken); WinBase.SECURITY_ATTRIBUTES sa = new WinBase.SECURITY_ATTRIBUTES(); sa.dwLength = new WinDef.DWORD(sa.size()); WinNT.HANDLEByReference hUserTokenDup = new WinNT.HANDLEByReference(); boolean duplicateTokenEx = Advapi32.INSTANCE.DuplicateTokenEx( userToken.getValue(), WinNT.TOKEN_ALL_ACCESS|WinNT.TOKEN_ADJUST_SESSIONID, sa, SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, TOKEN_TYPE.TokenPrimary, hUserTokenDup); if (!duplicateTokenEx) { Kernel32.INSTANCE.CloseHandle(userToken.getValue()); Kernel32.INSTANCE.CloseHandle(hUserTokenDup.getValue()); throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); } boolean setTokenInfo = Advapi32.INSTANCE.SetTokenInformation( hUserTokenDup.getValue(), TOKEN_INFORMATION_CLASS.TokenSessionId, new IntByReference(sessionId), DWORD.SIZE); if (!setTokenInfo) { Kernel32.INSTANCE.CloseHandle(hUserTokenDup.getValue()); Kernel32.INSTANCE.CloseHandle(userToken.getValue()); throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); } final STARTUPINFO startupInfo = new STARTUPINFO(); startupInfo.lpDesktop = "Winsta0\Winlogon"; // final STARTUPINFO startupInfo = new STARTUPINFO(); final PROCESS_INFORMATION processInformation = new PROCESS_INFORMATION(); final PointerByReference ref = new PointerByReference(); try { boolean createProcess = Advapi32.INSTANCE.CreateProcessAsUser( hUserTokenDup.getValue(), null, cmd, null, null, false, CREATE_UNICODE_ENVIRONMENT, ref.getValue(), null, startupInfo, processInformation); if (!createProcess) { throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); } } finally { Kernel32.INSTANCE.CloseHandle(hUserTokenDup.getValue()); Kernel32.INSTANCE.CloseHandle(userToken.getValue()); }
错误原因分析
错误码0xc06d007e对应E_UNEXPECTED,本质是进程启动时的环境、桌面会话或令牌配置出了问题,结合你的代码,主要有几个关键问题:
- 桌面路径转义错误:Java字符串中的反斜杠必须用双反斜杠
\\转义,你写的"Winsta0\Winlogon"会被解析为无效路径,导致进程无法找到正确的桌面会话上下文。 - 令牌复制的权限与模拟级别不匹配:
- 使用
SecurityIdentification模拟级别权限过低,无法完成会话ID修改和跨会话进程启动的操作,应替换为SecurityImpersonation级别。 TOKEN_ALL_ACCESS权限冗余且存在安全风险,仅保留TOKEN_ADJUST_SESSIONID | TOKEN_QUERY | TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY这些必需权限即可。
- 使用
- 环境变量处理无效:你传递了一个空的
PointerByReference作为环境变量参数,这会导致进程启动时无法获取目标用户的环境变量,进而引发初始化异常。 - 令牌关闭顺序不合理:
finally块中先关闭复制后的令牌再关闭原令牌,虽不直接触发当前错误,但可能导致后续资源释放异常,建议调整关闭顺序。
修复后的代码
boolean res = Advapi32.INSTANCE.LogonUser( user, domain, password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, userToken); if (!res) { throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); } WinBase.SECURITY_ATTRIBUTES sa = new WinBase.SECURITY_ATTRIBUTES(); sa.dwLength = new WinDef.DWORD(sa.size()); WinNT.HANDLEByReference hUserTokenDup = new WinNT.HANDLEByReference(); // 精简令牌权限,使用正确的模拟级别 boolean duplicateTokenEx = Advapi32.INSTANCE.DuplicateTokenEx( userToken.getValue(), WinNT.TOKEN_ADJUST_SESSIONID | WinNT.TOKEN_QUERY | WinNT.TOKEN_DUPLICATE | WinNT.TOKEN_ASSIGN_PRIMARY, sa, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenPrimary, hUserTokenDup); if (!duplicateTokenEx) { Kernel32.INSTANCE.CloseHandle(userToken.getValue()); throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); } // 修改会话ID IntByReference sessionIdRef = new IntByReference(sessionId); boolean setTokenInfo = Advapi32.INSTANCE.SetTokenInformation( hUserTokenDup.getValue(), TOKEN_INFORMATION_CLASS.TokenSessionId, sessionIdRef, DWORD.SIZE); if (!setTokenInfo) { Kernel32.INSTANCE.CloseHandle(hUserTokenDup.getValue()); Kernel32.INSTANCE.CloseHandle(userToken.getValue()); throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); } final STARTUPINFO startupInfo = new STARTUPINFO(); // 修复桌面路径转义问题 startupInfo.lpDesktop = "Winsta0\\Winlogon"; final PROCESS_INFORMATION processInformation = new PROCESS_INFORMATION(); PointerByReference envBlock = new PointerByReference(); try { // 生成目标用户的环境变量块 if (!Advapi32.INSTANCE.CreateEnvironmentBlock(envBlock, hUserTokenDup.getValue(), false)) { throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); } boolean createProcess = Advapi32.INSTANCE.CreateProcessAsUser( hUserTokenDup.getValue(), null, cmd, null, null, false, CREATE_UNICODE_ENVIRONMENT, envBlock.getValue(), null, startupInfo, processInformation); if (!createProcess) { throw new Win32Exception(Kernel32.INSTANCE.GetLastError()); } } finally { // 调整令牌关闭顺序,先关原令牌,再关复制后的令牌 Kernel32.INSTANCE.CloseHandle(userToken.getValue()); Kernel32.INSTANCE.CloseHandle(hUserTokenDup.getValue()); // 销毁环境变量块 if (envBlock != null && envBlock.getValue() != null) { Advapi32.INSTANCE.DestroyEnvironmentBlock(envBlock.getValue()); } }
额外注意事项
- 确保服务进程拥有
SE_ASSIGNPRIMARYTOKEN_NAME和SE_INCREASE_QUOTA_NAME权限,否则CreateProcessAsUser会直接失败。 - 验证用户'A'的会话ID是否为有效交互式会话ID,可通过
WTSQueryUserToken等API获取正确的会话标识。 - 系统会话(Session 0)无法直接显示UI,若进程需要桌面交互,必须确保目标会话是活跃的用户交互式会话。
内容的提问来源于stack exchange,提问作者Mukesh Methaniya
相关产品推荐
相关产品推荐

