Windows服务启动应用程序无法看不到界面

一、无法看到桌面的根本原因

桌面应用程序和服务在不同的会话中。每个用户登录到计算机时,系统都会为他们创建一个会话,以便他们可以与系统进行交互。以Windows 服务启动的软件通常没有用户交互界面或图标显示的根本原因,是因为服务在后台运行,与用户界面分离。在 Windows 操作系统中,windows vista之后,系统会为每一个登录用户分配一个会话,后台服务在系统启动时最先启动,分配的会话ID为0,其后每登录一个用户会话,会话ID便会加1。

服务会话和用户会话是 Windows 操作系统中的两种不同类型的会话,用于管理进程、应用程序和用户交互。它们在目的、权限和特性上有一些区别:

服务会话(Service Session):

  1. 目的: 服务会话是用于运行 Windows 服务的会话。Windows 服务是在后台运行的应用程序,通常不需要用户交互。它们可能是自动启动并在系统启动时开始运行,提供系统级别的功能,如网络服务、数据库服务等。

  2. 用户交互: 服务会话通常不与用户界面交互,因为它们是在系统后台运行的。它们没有可见的窗口、任务栏图标或用户界面。

  3. 权限: 服务会话通常在系统权限下运行,具有较高的权限。这些权限允许服务访问系统资源和执行需要特权的操作。

  4. 会话特性: 服务会话通常不关注用户界面和交互特性,因此不涉及桌面、窗口管理和用户输入等。

用户会话(User Session):

  1. 目的: 用户会话是用户登录到计算机后创建的交互式环境。每个用户会话都包括用户桌面、任务栏、窗口管理器等,允许用户与操作系统和应用程序进行交互。

  2. 用户交互: 用户会话允许用户通过图形界面与计算机进行交互,打开应用程序、浏览文件、执行任务等。

  3. 权限: 用户会话的权限取决于用户的身份和权限。普通用户会话通常具有受限的权限,而管理员用户会话可能有更高的权限。

  4. 会话特性: 用户会话包括了与用户界面和交互有关的特性,如窗口管理、任务栏、壁纸设置等。

总结起来,服务会话主要用于后台运行系统级别的服务,不需要用户界面交互,并具有较高的权限。用户会话则是用户登录到操作系统后创建的交互式环境,包括图形界面和用户交互特性。这两种会话类型在功能和特性上有很大的差异,用于不同的应用场景。所有由于会话隔离,无法在一个会话值去直接启动另一个会话的程序,但是windows系统中有一个特殊的进程,对于每个会话会有一个对应的进程,这个进程就是winlogin.exe.

winlogon.exe 是 Windows 操作系统中的一个重要进程,负责管理用户登录、注销以及用户交互过程。它是用户登录会话的初始进程,扮演着连接用户与操作系统的桥梁,具有以下主要作用:

  1. 用户登录和注销管理: winlogon.exe 负责处理用户的登录和注销操作。当用户输入用户名和密码时,winlogon.exe 启动验证过程,确认用户的身份后,会启动用户会话,并为用户创建一个安全的工作环境。在用户注销时,winlogon.exe 负责关闭会话并清理用户环境。

  2. 创建用户会话: 当用户成功登录后,winlogon.exe 负责创建用户会话的进程树,包括用户桌面、任务栏、启动项等。它还启动用户的默认壁纸和显示设置。

  3. Ctrl+Alt+Delete 屏幕: winlogon.exe 还管理着 Windows 安全屏幕,即 Ctrl+Alt+Delete 屏幕。这个屏幕提供了一种安全方式来登录、注销、更改密码以及启动任务管理器等操作。

  4. 用户环境初始化: winlogon.exe 负责初始化用户环境,包括加载用户配置文件、注册表设置、用户权限等。这确保用户在登录后能够访问其个人设置和文件。

  5. 处理注销和关闭: 当用户选择注销或关闭计算机时,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_QUERYTOKEN_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:要设置的令牌属性的类型,例如 TokenElevationTypeTokenPrivileges 等。
  • 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 结构的指针,其中包含要应用的特权状态。
  • BufferLengthNewState 缓冲区的大小。
  • 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_CONSOLEDETACHED_PROCESS 等。
  • lpEnvironment:要使用的环境块。可以为 NULL
  • lpCurrentDirectory:进程的当前工作目录。可以为 NULL
  • lpStartupInfoSTARTUPINFO 结构,用于指定启动信息。
  • 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)相关的概念,但它们代表不同的实体和含义:

  1. winsta0:

    • "winsta0" 是Windows中的默认窗口站的名称。
    • 窗口站是一个隔离的用户界面对象,用于组织用户界面元素,包括桌面(Desktop)和窗口(Window)。
    • "winsta0" 通常包含了多个桌面,每个桌面代表一个用户会话。这些会话可以是交互式用户登录会话,也可以是服务会话。
    • 通常,"winsta0" 包含一个名为 "default" 的桌面,用于交互式用户登录后显示的默认桌面。
  2. 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;
}

 

posted @ 2023-08-25 18:04  TechNomad  阅读(2285)  评论(0编辑  收藏  举报