在 Windows 操作系统中,服务进程通常运行在 Session 0,而用户界面应用程序运行在用户会话中。为了在用户会话中启动 GUI 应用程序,可以使用 CreateProcessAsUser 函数。以下是一个示例代码,展示了如何在服务进程中使用 CreateProcessAsUser 函数穿透 Session 0 并在用户会话中启动 GUI 应用程序:

#include <windows.h>
#include <wtsapi32.h>
#include <stdio.h>

#pragma comment(lib, "wtsapi32.lib")

BOOL CreateProcessAsUserInSession(DWORD dwSessionId, LPCTSTR lpApplicationName, LPTSTR lpCommandLine) {
    HANDLE hToken = NULL;
    HANDLE hNewToken = NULL;
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    BOOL bResult = FALSE;

    // 打开指定会话的用户令牌
    if (!WTSQueryUserToken(dwSessionId, &hToken)) {
        printf("WTSQueryUserToken failed (%d)\n", GetLastError());
        goto Cleanup;
    }

    // 复制令牌
    if (!DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &hNewToken)) {
        printf("DuplicateTokenEx failed (%d)\n", GetLastError());
        goto Cleanup;
    }

    // 初始化 STARTUPINFO 结构
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    si.lpDesktop = TEXT("winsta0\\default"); // 指定桌面

    // 创建进程
    if (!CreateProcessAsUser(hNewToken, lpApplicationName, lpCommandLine, NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi)) {
        printf("CreateProcessAsUser failed (%d)\n", GetLastError());
        goto Cleanup;
    }

    // 关闭进程和线程句柄
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);

    bResult = TRUE;

Cleanup:
    if (hToken) CloseHandle(hToken);
    if (hNewToken) CloseHandle(hNewToken);

    return bResult;
}

int main() {
    DWORD dwSessionId = WTSGetActiveConsoleSessionId(); // 获取当前活动会话ID
    TCHAR szCommandLine[] = TEXT("notepad.exe");

    if (CreateProcessAsUserInSession(dwSessionId, NULL, szCommandLine)) {
        printf("Process created successfully in session %d\n", dwSessionId);
    } else {
        printf("Failed to create process in session %d\n", dwSessionId);
    }

    return 0;
}

说明

  1. WTSQueryUserToken: 这个函数用于获取指定会话的用户令牌。需要注意的是,这个函数只能在服务进程中调用,并且需要有足够的权限。
  2. DuplicateTokenEx: 复制令牌以便后续使用。
  3. CreateProcessAsUser: 使用复制的令牌在指定会话中创建进程。
  4. WTSGetActiveConsoleSessionId: 获取当前活动控制台会话的 ID。