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; }