创建windows进程,需要考虑两个点,即session和权限问题。了解这两点,网络上服务创建界面进程,管理员权限进程创建普通权限进程的代码则很好理解。
1、基础知识
(1) session
(2) 权限
CreateProcessAsUser需要传入一个token,而token即用户的主令牌
用户的主令牌包含以下信息:
- 用户的安全标识符(SID):唯一地标识了这个用户。
- 用户的权限:决定了用户可以访问哪些资源和执行哪些操作。
- 用户的组成员资格:决定了用户可以访问哪些资源和执行哪些操作。
- 用户的特权:允许用户执行一些需要特殊权限的操作,例如修改系统时间、关闭系统等。
用户的主令牌是一个非常重要的概念,它决定了用户可以访问哪些资源和执行哪些操作。在 Windows 中,每个进程都有一个访问令牌,它包含了进程的安全上下文信息。当使用 CreateProcessAsUser 函数启动一个新进程时,新进程会继承调用进程的访问令牌,也就是说,新进程的安全上下文信息与调用进程的安全上下文信息相同。如果需要在新进程中使用不同的安全上下文信息,就需要使用 hToken 参数来指定一个不同的访问令牌。
2、创建进程代码分析
(1) 服务创建界面进程
bool ServerRunWndProcess(LPSTR lpExePath) {
HANDLE hToken = NULL;
//获取当前服务的令牌
OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, &hToken);
HANDLE hTokenDup = NULL;
//复制服务的令牌,创建一个新的令牌
DuplicateTokenEx(hToken, TOKEN_ALL_ACCESS, NULL, SecurityIdentification, TokenPrimary, &hTokenDup);
//设置新令牌的session信息,为当前登录的用户session,实现session跨越
SetTokenInformation(hTokenDup, TokenSessionId, &WTSGetActiveConsoleSessionId(), sizeof(DWORD);
CreateEnvironmentBlock(&pEnv, hTokenDup, FALSE);
//指定创建时进程的主窗口的窗口工作站、桌面、标准句柄和外观。
STARTUPINFO si;
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.lpDesktop = ("WinSta0\\Default");
si.wShowWindow = SW_SHOW;
si.dwFlags = STARTF_USESHOWWINDOW;
//创建进程
CreateProcessAsUser(hTokenDup, NULL, lpExePath, NULL, NULL, FALSE, dwCreationFlag, pEnv, NULL, &si, &processInfo);
DestroyEnvironmentBlock(pEnv);
...
}
上例中实现的是在windows服务中创建界面进程。
直接调用ShellExecuteA拉起的界面进程,屏幕上是无法看不到界面的,但在任务管理器的详细信息中能找到进程,是因为它生成在session0中了。
上述代码使用了Active Console的session id,实现了跨session的进程创建,因此界面程序可以在屏幕上看到。
但能看到的是,该界面程序的用户名一栏是SYSTEM,因为复制了服务的token,所以服务的权限也被复制过来了。
(2) SYSTEM/管理员权限进程以普通权限打开进程
bool StartProcess(LPSTR lpExePath) {
//获取会话 ID 指定的登录用户的主要访问令牌,该令牌即普通权限
WTSQueryUserToken(WTSGetActiveConsoleSessionId(), &hToken);
DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, SecurityImpersonation, TokenPrimary, &hTokenDup);
CreateEnvironmentBlock(&lpvEnv, hTokenDup, TRUE);
STARTUPINFO si;
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.lpDesktop = "winsta0\\default";
CreateProcessAsUser(hTokenDup, process_absolute_path.c_str(), NULL, NULL, NULL,
FALSE, CREATE_UNICODE_ENVIRONMENT | NORMAL_PRIORITY_CLASS, lpvEnv, NULL, &si, &pi);
DestroyEnvironmentBlock(pEnv);
...
}
上述代码,就只是通过WTSQueryUserToken,获取到了一个普通权限的token。复制该token打开进程就是普通权限了。