Windows服务启动应用程序无法看不到界面
一、无法看到桌面的根本原因
桌面应用程序和服务在不同的会话中。每个用户登录到计算机时,系统都会为他们创建一个会话,以便他们可以与系统进行交互。以Windows 服务启动的软件通常没有用户交互界面或图标显示的根本原因,是因为服务在后台运行,与用户界面分离。在 Windows 操作系统中,windows vista之后,系统会为每一个登录用户分配一个会话,后台服务在系统启动时最先启动,分配的会话ID为0,其后每登录一个用户会话,会话ID便会加1。
服务会话和用户会话是 Windows 操作系统中的两种不同类型的会话,用于管理进程、应用程序和用户交互。它们在目的、权限和特性上有一些区别:
服务会话(Service Session):
-
目的: 服务会话是用于运行 Windows 服务的会话。Windows 服务是在后台运行的应用程序,通常不需要用户交互。它们可能是自动启动并在系统启动时开始运行,提供系统级别的功能,如网络服务、数据库服务等。
-
用户交互: 服务会话通常不与用户界面交互,因为它们是在系统后台运行的。它们没有可见的窗口、任务栏图标或用户界面。
-
权限: 服务会话通常在系统权限下运行,具有较高的权限。这些权限允许服务访问系统资源和执行需要特权的操作。
-
会话特性: 服务会话通常不关注用户界面和交互特性,因此不涉及桌面、窗口管理和用户输入等。
用户会话(User Session):
-
目的: 用户会话是用户登录到计算机后创建的交互式环境。每个用户会话都包括用户桌面、任务栏、窗口管理器等,允许用户与操作系统和应用程序进行交互。
-
用户交互: 用户会话允许用户通过图形界面与计算机进行交互,打开应用程序、浏览文件、执行任务等。
-
权限: 用户会话的权限取决于用户的身份和权限。普通用户会话通常具有受限的权限,而管理员用户会话可能有更高的权限。
-
会话特性: 用户会话包括了与用户界面和交互有关的特性,如窗口管理、任务栏、壁纸设置等。
总结起来,服务会话主要用于后台运行系统级别的服务,不需要用户界面交互,并具有较高的权限。用户会话则是用户登录到操作系统后创建的交互式环境,包括图形界面和用户交互特性。这两种会话类型在功能和特性上有很大的差异,用于不同的应用场景。所有由于会话隔离,无法在一个会话值去直接启动另一个会话的程序,但是windows系统中有一个特殊的进程,对于每个会话会有一个对应的进程,这个进程就是winlogin.exe.
winlogon.exe 是 Windows 操作系统中的一个重要进程,负责管理用户登录、注销以及用户交互过程。它是用户登录会话的初始进程,扮演着连接用户与操作系统的桥梁,具有以下主要作用:
-
用户登录和注销管理:
winlogon.exe负责处理用户的登录和注销操作。当用户输入用户名和密码时,winlogon.exe启动验证过程,确认用户的身份后,会启动用户会话,并为用户创建一个安全的工作环境。在用户注销时,winlogon.exe负责关闭会话并清理用户环境。 -
创建用户会话: 当用户成功登录后,
winlogon.exe负责创建用户会话的进程树,包括用户桌面、任务栏、启动项等。它还启动用户的默认壁纸和显示设置。 -
Ctrl+Alt+Delete 屏幕:
winlogon.exe还管理着 Windows 安全屏幕,即 Ctrl+Alt+Delete 屏幕。这个屏幕提供了一种安全方式来登录、注销、更改密码以及启动任务管理器等操作。 -
用户环境初始化:
winlogon.exe负责初始化用户环境,包括加载用户配置文件、注册表设置、用户权限等。这确保用户在登录后能够访问其个人设置和文件。 -
处理注销和关闭: 当用户选择注销或关闭计算机时,
winlogon.exe会向运行中的应用程序发送关闭请求,并确保所有进程正常关闭。这有助于避免数据丢失或进程异常终止。
可以发现winlogin进程是后台服务进程,但所属登录用户会话,有了winlogin进程,我们可以在后台服务中先查询到winlogin进程信息,获取其访问令牌,最后通过CreateProcessAsUser将进程启动到活动登录用户当前活动会话。由于和前台界面所属同一会话,启动后的程序便可以进行交互。
二、示例代码
#include <iostream>
#include <windows.h>
#include <tlhelp32.h>
#include <userenv.h>
#include <wtsapi32.h>
#pragma comment(lib, "wtsapi32.lib")
#pragma comment(lib, "userenv.lib")
int main() {
PROCESS_INFORMATION pi;
STARTUPINFO si;
BOOL bResult = FALSE;
DWORD dwSessionId = 0, winlogonPid = 0;
HANDLE hUserToken, hUserTokenDup, hPToken, hProcess;
DWORD dwCreationFlags;
//获取当前活动的会话ID
dwSessionId = WTSGetActiveConsoleSessionId();
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnap == INVALID_HANDLE_VALUE) {
return 1;
}
PROCESSENTRY32 procEntry;
procEntry.dwSize = sizeof(PROCESSENTRY32);
if (!Process32First(hSnap, &procEntry)) {
return 1;
}
do {
if (_wcsicmp(procEntry.szExeFile, L"winlogon.exe") == 0) {
DWORD winlogonSessId = 0;
BOOL bRet = ProcessIdToSessionId(procEntry.th32ProcessID, &winlogonSessId);
if (bRet && winlogonSessId == dwSessionId) {
winlogonPid = procEntry.th32ProcessID;
break;
}
}
} while (Process32Next(hSnap, &procEntry));
const char* pDesktop = "winsta0\\default";
wchar_t desktop[256] = { 0 };
size_t convertedChars = 0;
errno_t result = mbstowcs_s(&convertedChars, desktop, strlen(pDesktop) + 1, pDesktop, _TRUNCATE);
WTSQueryUserToken(dwSessionId, &hUserToken);
dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE;
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.lpDesktop = desktop;
ZeroMemory(&pi, sizeof(pi));
TOKEN_PRIVILEGES tp;
LUID luid;
hProcess = OpenProcess(MAXIMUM_ALLOWED, FALSE, winlogonPid);
if (!::OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY
| TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY | TOKEN_ADJUST_SESSIONID
| TOKEN_READ | TOKEN_WRITE, &hPToken)) {
std::cout << "Process token open Error:" << GetLastError();
return 1;
}
if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) {
std::cout << "Lookup Privilege value Error:" << GetLastError();
return 1;
}
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, NULL,
SecurityIdentification, TokenPrimary, &hUserTokenDup);
int dup = GetLastError();
SetTokenInformation(hUserTokenDup, TokenSessionId, (void*)dwSessionId, sizeof(DWORD));
if (!AdjustTokenPrivileges(hUserTokenDup, FALSE, &tp, sizeof(TOKEN_PRIVILEGES),
(PTOKEN_PRIVILEGES)NULL, NULL)) {
std::cout << "Adjust Privilege value Error:" << GetLastError();
}
if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
{
printf("Token does not have the provilege\n");
std::cout << "Token does not have the provilege" << std::endl;
return 1;
}
LPVOID pEnv = NULL;
if (CreateEnvironmentBlock(&pEnv, hUserTokenDup, TRUE)) {
dwCreationFlags |= CREATE_UNICODE_ENVIRONMENT;
const char* pProcessName = "cmd.exe";
wchar_t processName[256] = { 0 };
errno_t result = mbstowcs_s(&convertedChars, processName, strlen(pProcessName) + 1, pProcessName, _TRUNCATE);
// Launch the process in the client's logon session.
bResult = CreateProcessAsUser(
hUserTokenDup, // client's access token
processName, // file to execute
NULL, // command line
NULL, // pointer to process SECURITY_ATTRIBUTES
NULL, // pointer to thread SECURITY_ATTRIBUTES
FALSE, // handles are not inheritable
dwCreationFlags, // creation flags
pEnv, // pointer to new environment block
NULL, // name of current directory
&si, // pointer to STARTUPINFO structure
&pi // receives information about new process
);
int iResultOfCreateProcessAsUser = GetLastError();
if (!bResult) {
std::cout << "create process as user error:" << std::endl;
return 1;
}
}
CloseHandle(hProcess);
CloseHandle(hUserToken);
CloseHandle(hUserTokenDup);
CloseHandle(hPToken);
return 1;
}
三、函数的使用介绍
1.WTSGetActiveConsoleSessionId()
WTSGetActiveConsoleSessionId 是 Windows Terminal Services(现在称为远程桌面服务)相关的函数,用于获取当前活动的控制台会话的会话 ID。在多用户环境下,可以使用这个函数来获取当前用户的会话 ID,以便执行与会话相关的操作。以下是 WTSGetActiveConsoleSessionId 函数的简要介绍和使用方法:
函数签名:
DWORD WTSGetActiveConsoleSessionId();
返回值:
返回当前活动的控制台会话的会话 ID。如果函数失败,将返回 INVALID_SESSION_ID。
使用示例:
#include <Windows.h>
#include <Wtsapi32.h>
int main() {
DWORD sessionId = WTSGetActiveConsoleSessionId();
if (sessionId != INVALID_SESSION_ID) {
// 成功获取会话 ID,可以使用 sessionId 进行操作
// 例如,执行与会话相关的任务
printf("Active Console Session ID: %lu\n", sessionId);
} else {
// 获取会话 ID 失败,可以使用 GetLastError() 获取错误信息
printf("Failed to get Active Console Session ID.\n");
}
return 0;
}
需要注意的是,WTSGetActiveConsoleSessionId 函数在不同的环境下(如 Windows 版本、终端服务配置等)可能会有不同的行为。它通常用于确定当前活动的用户会话,以便进行与会话相关的操作,例如在特定会话中执行任务、发送消息等。 如果正在编写一个需要与会话交互的应用程序,可以使用此函数获取会话 ID,并根据需要执行相应的操作。
2.ProcessIdToSessionId()
ProcessIdToSessionId 是 Windows API 中的一个函数,用于获取指定进程的会话 ID。每个用户登录到系统时,会创建一个会话,用户的进程在特定的会话中运行。这个函数可以用于确定特定进程所在的会话,以便进行与会话相关的操作。以下是 ProcessIdToSessionId 函数的简要介绍和使用方法:
函数签名:
BOOL ProcessIdToSessionId( DWORD dwProcessId, DWORD *pSessionId );
参数说明:
dwProcessId:要查询会话 ID 的进程的 ID。pSessionId:用于存储查询到的会话 ID。
返回值:
- 如果函数成功,将返回非零值,并将查询到的会话 ID 存储在
pSessionId变量中。如果函数失败,将返回零。
使用示例:
#include <Windows.h>
int main() {
DWORD processId = GetCurrentProcessId();
DWORD sessionId;
if (ProcessIdToSessionId(processId, &sessionId)) {
// 成功获取会话 ID,可以使用 sessionId 进行操作
printf("Process ID: %lu, Session ID: %lu\n", processId, sessionId);
} else {
// 获取会话 ID 失败,可以使用 GetLastError() 获取错误信息
printf("Failed to get Session ID.\n");
}
return 0;
}
3.WTSQueryUserToken()
WTSQueryUserToken 是 Windows API 中的一个函数,用于获取与指定用户会话关联的用户令牌(token)。这个函数通常用于在用户会话中启动进程,以便在指定会话中以用户的身份执行操作。以下是 WTSQueryUserToken 函数的简要介绍和使用方法:
函数签名:
BOOL WTSQueryUserToken(
ULONG SessionId,
PHANDLE phToken
);
参数说明:
SessionId:要查询的用户会话的会话 ID。phToken:用于存储查询到的用户令牌(token)的句柄。
返回值:
- 如果函数成功,将返回非零值,并将查询到的用户令牌句柄存储在
phToken变量中。如果函数失败,将返回零。
使用示例:
#include <Windows.h>
#include <Wtsapi32.h>
int main() {
DWORD sessionId = WTSGetActiveConsoleSessionId(); // 获取当前活动的控制台会话 ID
HANDLE hToken;
if (WTSQueryUserToken(sessionId, &hToken)) {
// 成功获取用户令牌句柄,可以使用 hToken 进行操作
// 例如,使用 CreateProcessAsUser 函数以用户身份启动进程
printf("User Token obtained successfully.\n");
CloseHandle(hToken); // 使用完毕后关闭令牌句柄
} else {
// 获取用户令牌失败,可以使用 GetLastError() 获取错误信息
printf("Failed to obtain User Token.\n");
}
return 0;
}
WTSQueryUserToken 函数的主要用途是获取特定会话的用户令牌,以便以该用户的身份启动进程。需要注意的是,您需要具有足够的权限来查询其他会话的用户令牌。在使用查询到的用户令牌时,务必了解 Windows 安全和权限的相关概念,并正确处理令牌句柄以避免资源泄漏和安全问题。
4.OpenProcess()
OpenProcess 是 Windows API 中的一个函数,用于打开一个已存在的进程的句柄,以便在操作中引用该进程。这个函数通常用于在一个进程中获取另一个进程的句柄,以便执行诸如读取内存、写入内存、终止进程等操作。以下是 OpenProcess 函数的简要介绍和使用方法:
函数签名:
HANDLE OpenProcess(
DWORD dwDesiredAccess,
BOOL bInheritHandle,
DWORD dwProcessId
);
OpenProcess 是 Windows API 中的一个函数,用于打开一个已存在的进程的句柄,以便在操作中引用该进程。这个函数通常用于在一个进程中获取另一个进程的句柄,以便执行诸如读取内存、写入内存、终止进程等操作。以下是 OpenProcess 函数的简要介绍和使用方法:
函数签名:
HANDLE OpenProcess( DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId ); 参数说明:
dwDesiredAccess:要求的访问权限。常用的权限包括PROCESS_ALL_ACCESS(完全访问权限)和PROCESS_QUERY_INFORMATION(查询信息权限)等。bInheritHandle:是否继承句柄。通常设置为 FALSE。dwProcessId:要打开的进程的进程 ID。
返回值:
- 如果函数成功,将返回已打开进程的句柄(
HANDLE)。如果函数失败,将返回NULL。
使用示例:
#include <Windows.h> int main() { DWORD processId = 1234; // 要打开的进程的进程 ID HANDLE hProcess; hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId); if (hProcess != NULL) { // 成功打开进程句柄,可以使用 hProcess 进行操作 // 例如,读取或写入进程内存 printf("Process handle obtained successfully.\n"); CloseHandle(hProcess); // 使用完毕后关闭进程句柄 } else { // 打开进程句柄失败,可以使用 GetLastError() 获取错误信息 printf("Failed to obtain process handle.\n"); } return 0; }
5.OpenProcessToken()
OpenProcessToken 是 Windows API 中的一个函数,用于打开指定进程的访问令牌(token)句柄。访问令牌是一种安全标识,代表了一个用户或进程的安全上下文,它用于控制对资源的访问权限。以下是 OpenProcessToken 函数的简要介绍和使用方法:
函数签名:
BOOL OpenProcessToken( HANDLE ProcessHandle, DWORD DesiredAccess, PHANDLE TokenHandle );
参数说明:
ProcessHandle:要打开访问令牌的进程的句柄。DesiredAccess:要求的访问权限,例如TOKEN_QUERY或TOKEN_ALL_ACCESS。TokenHandle:用于存储打开的令牌句柄的指针。
返回值:
- 如果函数成功,将返回非零值,并将打开的令牌句柄存储在
TokenHandle变量中。如果函数失败,将返回零。
使用示例:
#include <Windows.h>
int main() {
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, 1234); // 替换为实际的进程句柄
if (hProcess != NULL) {
HANDLE hToken;
if (OpenProcessToken(hProcess, TOKEN_QUERY, &hToken)) {
// 成功打开进程的令牌句柄,可以使用 hToken 进行操作
printf("Token handle obtained successfully.\n");
CloseHandle(hToken); // 使用完毕后关闭令牌句柄
} else {
// 打开令牌句柄失败,可以使用 GetLastError() 获取错误信息
printf("Failed to obtain token handle.\n");
}
CloseHandle(hProcess);
} else {
// 打开进程句柄失败,可以使用 GetLastError() 获取错误信息
printf("Failed to obtain process handle.\n");
}
return 0;
}
6.DuplicateTokenEx()
DuplicateTokenEx 是 Windows API 中的一个函数,用于复制一个访问令牌(token)。这个函数通常用于创建令牌的副本,以便在不同上下文中使用。一个常见的用途是在不同的会话中启动进程,从而实现进程间的权限传递和授权。以下是 DuplicateTokenEx 函数的简要介绍和使用方法:
函数签名:
BOOL DuplicateTokenEx( HANDLE hExistingToken, DWORD dwDesiredAccess, LPSECURITY_ATTRIBUTES lpTokenAttributes, SECURITY_IMPERSONATION_LEVEL ImpersonationLevel, TOKEN_TYPE TokenType, PHANDLE phNewToken );
参数说明:
hExistingToken:要复制的现有令牌的句柄。dwDesiredAccess:新令牌的访问权限。lpTokenAttributes:新令牌的安全描述符。ImpersonationLevel:模拟级别,指定令牌的模拟级别。TokenType:新令牌的类型,可以是主令牌或模拟令牌。phNewToken:用于存储复制后的新令牌句柄的指针。
返回值:
- 如果函数成功,将返回非零值,并将复制后的新令牌句柄存储在
phNewToken变量中。如果函数失败,将返回零。
使用示例:
#include <Windows.h>
int main() {
HANDLE hExistingToken; // 要复制的现有令牌句柄
HANDLE hNewToken;
// 从某种方式获得 hExistingToken,例如通过 LogonUser 或 OpenProcessToken
if (DuplicateTokenEx(
hExistingToken,
MAXIMUM_ALLOWED,
NULL,
SecurityImpersonation,
TokenPrimary, // 或 TokenImpersonation,根据情况选择
&hNewToken
)) {
// 成功复制令牌
// 可以使用 hNewToken 进行操作
CloseHandle(hNewToken); // 使用完毕后关闭令牌句柄
} else {
// 复制令牌失败,可以使用 GetLastError() 获取错误信息
}
return 0;
}
7.SetTokenInformation()
SetTokenInformation 是 Windows API 中的一个函数,用于设置访问令牌(token)的属性。令牌是一种安全标识,代表了一个用户或进程的安全上下文,而 SetTokenInformation 允许您更改令牌的一些属性。以下是 SetTokenInformation 函数的简要介绍和使用方法:
函数签名:
BOOL SetTokenInformation( HANDLE TokenHandle, TOKEN_INFORMATION_CLASS TokenInformationClass, LPVOID TokenInformation, DWORD TokenInformationLength );
参数说明:
TokenHandle:要设置属性的令牌的句柄。TokenInformationClass:要设置的令牌属性的类型,例如TokenElevationType、TokenPrivileges等。TokenInformation:指向存储属性值的缓冲区的指针。TokenInformationLength:存储在TokenInformation中的属性值的长度。
返回值:
- 如果函数成功,将返回非零值。如果函数失败,将返回零。
使用示例:
#include <Windows.h>
#include <Sddl.h> // 需要包含 Sddl.h 头文件
int main() {
HANDLE hToken; // 要设置属性的令牌句柄
TOKEN_ELEVATION_TYPE elevationType = TokenElevationTypeFull;
// 从某种方式获得 hToken,例如通过 LogonUser 或 OpenProcessToken
if (SetTokenInformation(
hToken,
TokenElevationType,
&elevationType,
sizeof(TOKEN_ELEVATION_TYPE)
)) {
// 成功设置令牌属性
printf("Token information set successfully.\n");
} else {
// 设置令牌属性失败,可以使用 GetLastError() 获取错误信息
printf("Failed to set token information.\n");
}
CloseHandle(hToken);
return 0;
}
8.AdjustTokenPrivileges()
AdjustTokenPrivileges 是 Windows API 中的一个函数,用于调整访问令牌(token)的特权级别。特权是允许执行特定系统操作的权限。这个函数通常用于提升或降低进程的特权级别,从而实现特定权限的操作。以下是 AdjustTokenPrivileges 函数的简要介绍和使用方法:
函数签名:
BOOL AdjustTokenPrivileges( HANDLE TokenHandle, BOOL DisableAllPrivileges, PTOKEN_PRIVILEGES NewState, DWORD BufferLength, PTOKEN_PRIVILEGES PreviousState, PDWORD ReturnLength );
参数说明:
TokenHandle:要调整特权的令牌的句柄。DisableAllPrivileges:是否禁用所有特权。NewState:一个指向TOKEN_PRIVILEGES结构的指针,其中包含要应用的特权状态。BufferLength:NewState缓冲区的大小。PreviousState:可选参数,用于存储旧的特权状态。可以为NULL。ReturnLength:用于存储PreviousState缓冲区所需的大小。可以为NULL。
返回值:
- 如果函数成功,将返回非零值。如果函数失败,将返回零。
#include <Windows.h>
#include <Sddl.h> // 需要包含 Sddl.h 头文件
int main() {
HANDLE hToken; // 要调整特权的令牌句柄
TOKEN_PRIVILEGES tp;
LUID luid;
// 从某种方式获得 hToken,例如通过 LogonUser 或 OpenProcessToken
if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) {
// 获取特权的 LUID 失败
printf("LookupPrivilegeValue failed.\n");
return 1;
}
tp.PrivilegeCount = 1;
tp.Privileges[0].Luid = luid;
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if (AdjustTokenPrivileges(
hToken,
FALSE,
&tp,
sizeof(TOKEN_PRIVILEGES),
NULL,
NULL
)) {
// 成功调整特权
printf("Privilege adjusted successfully.\n");
} else {
// 调整特权失败,可以使用 GetLastError() 获取错误信息
printf("Failed to adjust privilege.\n");
}
CloseHandle(hToken);
return 0;
}
9.CreateEnvironmentBlock()
CreateEnvironmentBlock 是 Windows API 中的一个函数,用于创建一个新的环境块(环境变量集合)。环境块是一组键值对,表示了进程的环境变量。这个函数通常用于为一个进程创建一个新的环境块,以便在调用函数如 CreateProcessAsUser 时,为新进程提供自定义的环境变量。以下是 CreateEnvironmentBlock 函数的简要介绍和使用方法:
函数签名:
BOOL CreateEnvironmentBlock( LPVOID *lpEnvironment, HANDLE hToken, BOOL bInherit );
参数说明:
lpEnvironment:用于存储创建的环境块的指针。hToken:一个令牌句柄,通常是一个用户令牌,用于生成用户的环境变量。bInherit:是否继承当前进程的环境变量。
返回值:
- 如果函数成功,将返回非零值,并将创建的环境块的指针存储在
lpEnvironment变量中。如果函数失败,将返回零。
使用示例:
#include <Windows.h>
int main() {
HANDLE hToken; // 要使用的用户令牌句柄
LPVOID lpEnvironment = NULL;
// 从某种方式获得 hToken,例如通过 LogonUser 或 OpenProcessToken
if (CreateEnvironmentBlock(&lpEnvironment, hToken, FALSE)) {
// 成功创建环境块
// 可以使用 lpEnvironment 进行操作,例如在 CreateProcessAsUser 中使用
CloseHandle(hToken);
DestroyEnvironmentBlock(lpEnvironment); // 使用完毕后释放环境块
} else {
// 创建环境块失败,可以使用 GetLastError() 获取错误信息
printf("Failed to create environment block.\n");
CloseHandle(hToken);
}
return 0;
}
10.CreateProcessAsUser()
CreateProcessAsUser 是 Windows API 中的一个函数,用于以指定用户的身份创建一个新进程。这个函数通常用于在指定用户的会话中启动进程,以便以该用户的身份执行操作。以下是 CreateProcessAsUser 函数的简要介绍和使用方法:
函数签名:
BOOL CreateProcessAsUser( HANDLE hToken, LPCTSTR lpApplicationName, LPTSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCTSTR lpCurrentDirectory, LPSTARTUPINFO lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation );
参数说明:
hToken:要用于创建进程的用户令牌句柄。lpApplicationName:要执行的可执行文件的路径或名称。lpCommandLine:命令行参数。lpProcessAttributes:进程的安全性描述符。lpThreadAttributes:线程的安全性描述符。bInheritHandles:是否继承句柄。dwCreationFlags:创建标志,例如CREATE_NEW_CONSOLE、DETACHED_PROCESS等。lpEnvironment:要使用的环境块。可以为NULL。lpCurrentDirectory:进程的当前工作目录。可以为NULL。lpStartupInfo:STARTUPINFO结构,用于指定启动信息。lpProcessInformation:用于存储创建的进程信息。
返回值:
- 如果函数成功,将返回非零值,并将创建的进程信息存储在
lpProcessInformation变量中。如果函数失败,将返回零。
使用示例:
#include <Windows.h>
int main() {
HANDLE hToken; // 要用于创建进程的用户令牌句柄
PROCESS_INFORMATION pi;
STARTUPINFO si;
// 从某种方式获得 hToken,例如通过 LogonUser 或 OpenProcessToken
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
if (CreateProcessAsUser(
hToken,
L"C:\\Path\\To\\Your\\Executable.exe",
NULL,
NULL,
NULL,
FALSE,
CREATE_NEW_CONSOLE,
NULL,
NULL,
&si,
&pi
)) {
// 成功创建进程
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
} else {
// 创建进程失败,可以使用 GetLastError() 获取错误信息
printf("Failed to create process as user.\n");
}
CloseHandle(hToken);
return 0;
}
四、winsta0 和 default的区别
在Windows操作系统中,"winsta0" 和 "default" 都是与用户界面和窗口站(Window Station)相关的概念,但它们代表不同的实体和含义:
-
winsta0:
- "winsta0" 是Windows中的默认窗口站的名称。
- 窗口站是一个隔离的用户界面对象,用于组织用户界面元素,包括桌面(Desktop)和窗口(Window)。
- "winsta0" 通常包含了多个桌面,每个桌面代表一个用户会话。这些会话可以是交互式用户登录会话,也可以是服务会话。
- 通常,"winsta0" 包含一个名为 "default" 的桌面,用于交互式用户登录后显示的默认桌面。
-
default:
- "default" 是 "winsta0" 窗口站中的一个特定桌面的名称。
- 桌面是窗口站中的工作区域,包含任务栏、图标、窗口等用户界面元素。
- "default" 桌面通常是用户登录后看到的默认桌面,包括桌面背景、任务栏和桌面图标。
- 除了 "default" 桌面,"winsta0" 可能还包含其他桌面,用于不同的用户会话或用途。
总结来说,"winsta0" 是一个窗口站,可以包含多个桌面,而 "default" 是 "winsta0" 窗口站中的一个特定桌面。 "default" 桌面通常是交互式用户登录后显示的默认桌面,包括桌面背景、任务栏和桌面图标。不同的用户会话可能在 "winsta0" 中有不同的桌面。这些概念用于管理和隔离不同用户和应用程序的用户界面。
五、简化版示例
HANDLE MonitorService::LaunchProcessWin(const char* command)
{
HANDLE hProcess = NULL;
HANDLE hToken = NULL;
if (GetSessionUserTokenWin(&hToken)) {
wchar_t cmdLine[256] = {0};
mbstowcs(cmdLine, command, strlen(command) + 1);
STARTUPINFO si;
ZeroMemory(&si, sizeof si);
si.cb = sizeof si;
si.dwFlags = STARTF_USESHOWWINDOW;
PROCESS_INFORMATION pi;
if (CreateProcessAsUser(hToken, NULL, cmdLine, NULL, NULL, FALSE, DETACHED_PROCESS, NULL, NULL, &si, &pi))
{
CloseHandle(pi.hThread);
hProcess = pi.hProcess;
}
CloseHandle(hToken);
}
return hProcess;
}
BOOL MonitorService::GetSessionUserTokenWin(OUT LPHANDLE lphUserToken)
{
BOOL bResult = FALSE;
//获取当前活动的会话ID
DWORD dwSessionId = WTSGetActiveConsoleSessionId();
DWORD Id = GetLogonPid(dwSessionId);
qDebug() << "LogonPid:" << Id;
if (HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, Id))
{
bResult = OpenProcessToken(hProcess, TOKEN_ALL_ACCESS, lphUserToken);
CloseHandle(hProcess);
}
return bResult;
}
DWORD MonitorService::GetLogonPid(DWORD dwSessionId)
{
DWORD dwLogonPid = 0;
HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnap != INVALID_HANDLE_VALUE) {
PROCESSENTRY32 procEntry;
procEntry.dwSize = sizeof procEntry;
if (!Process32First(hSnap, &procEntry)) {
qDebug() << "Process32First error:" << GetLastError();
return false;
}
do {
if (_wcsicmp(procEntry.szExeFile, L"winlogon.exe") == 0) {
DWORD winlogonSessId = 0;
BOOL bRet = ProcessIdToSessionId(procEntry.th32ProcessID, &winlogonSessId);
if (bRet && winlogonSessId == dwSessionId) {
dwLogonPid = procEntry.th32ProcessID;
break;
}
}
} while (Process32Next(hSnap, &procEntry));
CloseHandle(hSnap);
}
return dwLogonPid;
}
六、桌面检测和切换
1.检测当前桌面与输入桌面是否为同一个桌面:
bool desktop_change_required()
{
return !input_desktop_selected();
}
bool input_desktop_selected()
{
HDESK current = GetThreadDesktop(GetCurrentThreadId());
HDESK input = OpenInputDesktop(0, FALSE,
DESKTOP_CREATEMENU | DESKTOP_CREATEWINDOW |
DESKTOP_ENUMERATE | DESKTOP_HOOKCONTROL |
DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS |
DESKTOP_SWITCHDESKTOP | GENERIC_WRITE);
if (!input) {
qDebug() << "unable to OpenInputDesktop:" << GetLastError();
return false;
}
DWORD size;
char currentname[256] = {0};
char inputname[256] = {0};
if (!GetUserObjectInformation(current, UOI_NAME, currentname, 256, &size)) {
qDebug() << "unable to GetUserObjectInformation:" << GetLastError();
CloseDesktop(input);
return false;
}
if (!GetUserObjectInformation(input, UOI_NAME, inputname, 256, &size)) {
qDebug() << "unable to GetUserObjectInformation:" << GetLastError();
CloseDesktop(input);
return false;
}
if (!CloseDesktop(input)) {
qDebug() << "unable to close input desktop:" << GetLastError();
}
bool result = strcmp(currentname, inputname) == 0;
return result;
}
2.如果当前桌面不是输入桌面,则切换到输入桌面
bool change_desktop()
{
return select_input_desktop();
}
bool select_input_desktop()
{
HDESK desktop = OpenInputDesktop(0, FALSE,
DESKTOP_CREATEMENU | DESKTOP_CREATEWINDOW |
DESKTOP_ENUMERATE | DESKTOP_HOOKCONTROL |
DESKTOP_WRITEOBJECTS | DESKTOP_READOBJECTS |
DESKTOP_SWITCHDESKTOP | GENERIC_WRITE);
if (!desktop) {
qDebug() << "unable to OpenInputDesktop:" << GetLastError();
return false;
}
// - Switch into it
if (!switch_to_desktop(desktop)) {
CloseDesktop(desktop);
return false;
}
DWORD size = 256;
char currentname[256] = {0};
if (GetUserObjectInformation(desktop, UOI_NAME, currentname, 256, &size)) {
qDebug() << "switched to :" << currentname;
}
qDebug() << "switched to input desktop";
return true;
}
bool switch_to_desktop(HDESK desktop)
{
HDESK old_desktop = GetThreadDesktop(GetCurrentThreadId());
if (!SetThreadDesktop(desktop)) {
qDebug() << "switch to desktop failed:" << GetLastError();
return false;
}
if (!CloseDesktop(old_desktop)) {
qDebug() << "unable to close old desktop:" << GetLastError();
}
return true;
}

浙公网安备 33010602011771号