在Windows XP、Windows Server 2003,以及更老版本的Windows操作系统中,服务和应用程序使用相同的会话(SESSION)来运行,而这个会话是由第一个登录到控制台的用户来启动的,该会话就称为SESSION 0。将服务和用户应用程序一起在SESSION 0中运行会导致安全风险,因为服务会使用提升后的权限来运行,而用户应用程序使用用户特权(大部分都是非管理员用户)运行,这会使得恶意软件把某个服务作为攻击目标,通过“劫持”该服务以达到提升自己权限级别的目的。
从Windows VISTA开始,只有服务可以托管到SESSION 0中,用户应用程序和服务之间会进行隔离,并需要运行在用户登录系统时创建的后续会话中。如第一个登录用户创建Session 1,第二个登录用户创建Session 2,以此类推。
使用不同会话运行的实体(应用程序或服务)如果不将自己明确标注为全局命名空间,并提供相应的访问控制设置,那么将无法互相发送消息,共享UI元素或共享内核对象。
1、WTSGetActiveConsoleSessionId函数
检索控制台会话的标识符Session Id。控制台会话时当前连接到物理控制台的会话。
https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-wtsgetactiveconsolesessionid
2、WTSQueryUserToken函数
获取由Session Id指定的登录用户的主访问令牌。要想成功调用此功能,则调用应用程序必须在本地系统账户的上下文中运行,并具有SE_TCB_NAME特权。
https://docs.microsoft.com/en-us/windows/win32/api/wtsapi32/nf-wtsapi32-wtsqueryusertoken
3、DuplicateTokenEx函数
创建一个新的访问令牌,它与现有令牌重复。此功能可以创建主令牌或模拟令牌。
https://docs.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-duplicatetokenex
4、CreateEnvironmentBlock函数
检索指定用户的环境变量,然后可以将此块传递给CreateProcessAsUser函数。
https://docs.microsoft.com/en-us/windows/win32/api/userenv/nf-userenv-createenvironmentblock
5、CreateProcessAsUser函数
创建一个新进程及主进程,新进程在指定令牌表示的用户安全的上下文中运行。
https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessasusera
由于SESSION 0的隔离,使得在系统服务进程内不能直接调用CreateProcess等函数创建进程,而只能通过CreateProcessAsUser函数来创建。这样,创建的进程才会显示UI界面,与用户进行交互。
在SESSION 0中创建用户桌面进程具体的实现流程如下所示。
首先,调用WTSGetActiveConsoleSessionId函数来获取当前程序的会话ID,即Session Id。调用该函数不需要任何参数,直接返回Session Id。根据Session Id继续调用WTSQueryUserToken函数来检索用户令牌,并获取对应的用户令牌句柄。在不需要使用用户令牌句柄时,可以调用CloseHandle函数来释放句柄。
其次,使用DuplicateTokenEx函数创建一个新令牌,并复制上面获取的用户令牌。设置新令牌的访问权限为MAXIMUM_ALLOWED,这表示获取所有令牌权限。新访问令牌的模拟级别为SecurityIdentification,而且令牌类型为TokenPrimary,这表示新令牌是可以在CreateProcessAsUser函数中使用的主令牌。
最后,根据新令牌调用CreateEnvironmentBlock函数创建一个环境块,用来传递给CreateProcessAsUser使用。在不需要使用进程环境块时,可以通过调用DestroyEnvironmentBlock函数进行释放。获取环境块后,就可以调用CreateProcessAsUser来创建用户桌面进程。CreateProcessAsUser函数的用法以及参数含义与CreateProcess函数的用法和参数含义类似。新令牌句柄作为用户主令牌的句柄,指定创建进程的路径,设置优先级和创建标志,设置STARTUPINFO结构信息,获取PROCESS_INFORMATION结构信息。
经过上述操作后,就完成了用户桌面进程的创建。但是,上述方法创建的用户桌面进程并没有继承服务程序的系统权限,只具有普通权限。要想创建一个有系统权限的子进程,这可以通过设置进程访问令牌的安全描述符来实现,具体的实现步骤在此就不详细介绍了。
创建服务进程代码:001.exe(以管理员身份运行创建服务)
ServiceOperate.h
#ifndef _SERVICE_OPERATE_H_ #define _SERVICE_OPERATE_H_ #include <Windows.h> #include <Shlwapi.h> #pragma comment(lib, "Shlwapi.lib") // 0 加载服务 1 启动服务 2 停止服务 3 删除服务 BOOL SystemServiceOperate(char* lpszDriverPath, int iOperateType); #endif
ServiceLoader.cpp
// ServiceLoader.cpp : 定义控制台应用程序的入口点。 // #include "ServiceOperate.h" #include<stdio.h> #include<tchar.h> int _tmain(int argc, _TCHAR* argv[]) { BOOL bRet = FALSE; char szExePath[] = "C:\\C C++\\winhack\\session0\\001\\x64\\Release\\002.exe"; // 加载服务 bRet = SystemServiceOperate(szExePath, 0); if (bRet) { printf("INSTALL OK.\n"); } else { printf("INSTALL ERROR.\n"); } // 启动服务 bRet = SystemServiceOperate(szExePath, 1); if (bRet) { printf("START OK.\n"); } else { printf("START ERROR.\n"); } system("pause"); // 停止服务 bRet = SystemServiceOperate(szExePath, 2); if (bRet) { printf("STOP OK.\n"); } else { printf("STOP ERROR.\n"); } // 卸载服务 bRet = SystemServiceOperate(szExePath, 3); if (bRet) { printf("UNINSTALL OK.\n"); } else { printf("UNINSTALL ERROR.\n"); } return 0; }
ServiceOperate.cpp
#include "ServiceOperate.h" #include <stdio.h> #include<tchar.h> void ShowError(char* lpszText) { char szErr[MAX_PATH] = { 0 }; ::wsprintf(szErr, "%s Error!\nError Code Is:%d\n", lpszText, ::GetLastError()); #ifdef _DEBUG ::MessageBox(NULL, szErr, "ERROR", MB_OK | MB_ICONERROR); #endif } // 0 加载服务 1 启动服务 2 停止服务 3 删除服务 BOOL SystemServiceOperate(char* lpszExePath, int iOperateType) { BOOL bRet = TRUE; char szName[MAX_PATH] = { 0 }; lstrcpy(szName, lpszExePath); // 过滤掉文件目录,获取文件名 PathStripPath(szName); SC_HANDLE shOSCM = NULL, shCS = NULL; SERVICE_STATUS ss; DWORD dwErrorCode = 0; BOOL bSuccess = FALSE; // 打开服务控制管理器数据库 shOSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); if (!shOSCM) { ShowError("OpenSCManager"); return FALSE; } if (0 != iOperateType) { // 打开一个已经存在的服务 shCS = OpenService(shOSCM, szName, SERVICE_ALL_ACCESS); if (!shCS) { ShowError("OpenService"); ::CloseServiceHandle(shOSCM); shOSCM = NULL; return FALSE; } } switch (iOperateType) { case 0: { // 创建服务 // SERVICE_AUTO_START 随系统自动启动 // SERVICE_DEMAND_START 手动启动 shCS = ::CreateService(shOSCM, szName, szName, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, lpszExePath, NULL, NULL, NULL, NULL, NULL); if (!shCS) { ShowError("CreateService"); bRet = FALSE; } break; } case 1: { // 启动服务 if (!::StartService(shCS, 0, NULL)) { ShowError("StartService"); bRet = FALSE; } break; } case 2: { // 停止服务 if (!::ControlService(shCS, SERVICE_CONTROL_STOP, &ss)) { ShowError("ControlService"); bRet = FALSE; } break; } case 3: { // 删除服务 if (!::DeleteService(shCS)) { ShowError("DeleteService"); bRet = FALSE; } break; } default: break; } // 关闭句柄 if (shCS) { ::CloseServiceHandle(shCS); shCS = NULL; } if (shOSCM) { ::CloseServiceHandle(shOSCM); shOSCM = NULL; } return bRet; }
创建用户的进程002.exe
CreateProcessAsUser_Test.cpp
#include <Windows.h> #include <UserEnv.h> #include <WtsApi32.h> #include <tchar.h> #pragma comment(lib, "UserEnv.lib") #pragma comment(lib, "WtsApi32.lib") // 服务入口函数以及处理回调函数 void __stdcall ServiceMain(DWORD dwArgc, char* lpszArgv); void __stdcall ServiceCtrlHandle(DWORD dwOperateCode); void DoTask(); // 显示消息对话框 void ShowMessage(TCHAR* lpszMessage, TCHAR* lpszTitle); // 创建用户进程 BOOL CreateUserProcess(char* lpszFileName); // 全局变量 char g_szServiceName[MAX_PATH] = "002.exe"; // 服务名称 SERVICE_STATUS g_ServiceStatus = { 0 }; SERVICE_STATUS_HANDLE g_ServiceStatusHandle = { 0 }; int _tmain(int argc, _TCHAR* argv[]) { // 注册服务入口函数 SERVICE_TABLE_ENTRY stDispatchTable[] = { { g_szServiceName, (LPSERVICE_MAIN_FUNCTION)ServiceMain }, { NULL, NULL } }; ::StartServiceCtrlDispatcher(stDispatchTable); return 0; } void __stdcall ServiceMain(DWORD dwArgc, char* lpszArgv) { g_ServiceStatus.dwServiceType = SERVICE_WIN32; g_ServiceStatus.dwCurrentState = SERVICE_START_PENDING; g_ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; g_ServiceStatus.dwWin32ExitCode = 0; g_ServiceStatus.dwServiceSpecificExitCode = 0; g_ServiceStatus.dwCheckPoint = 0; g_ServiceStatus.dwWaitHint = 0; g_ServiceStatusHandle = ::RegisterServiceCtrlHandler(g_szServiceName, ServiceCtrlHandle); g_ServiceStatus.dwCurrentState = SERVICE_RUNNING; g_ServiceStatus.dwCheckPoint = 0; g_ServiceStatus.dwWaitHint = 0; ::SetServiceStatus(g_ServiceStatusHandle, &g_ServiceStatus); // 自己程序实现部分代码放在这里 DoTask(); } void __stdcall ServiceCtrlHandle(DWORD dwOperateCode) { switch (dwOperateCode) { case SERVICE_CONTROL_PAUSE: { // 暂停 g_ServiceStatus.dwCurrentState = SERVICE_PAUSED; break; } case SERVICE_CONTROL_CONTINUE: { // 继续 g_ServiceStatus.dwCurrentState = SERVICE_RUNNING; break; } case SERVICE_CONTROL_STOP: { // 停止 g_ServiceStatus.dwWin32ExitCode = 0; g_ServiceStatus.dwCurrentState = SERVICE_STOPPED; g_ServiceStatus.dwCheckPoint = 0; g_ServiceStatus.dwWaitHint = 0; ::SetServiceStatus(g_ServiceStatusHandle, &g_ServiceStatus); break; } case SERVICE_CONTROL_INTERROGATE: { // 询问 break; } default: break; } } void DoTask() { // 自己程序实现部分代码放在这里 // 显示对话框 ShowMessage("Hi Demon·Gan\nThis Is From Session 0 Service!\n", "HELLO"); // 创建用户桌面进程 CreateUserProcess("C:\\C C++\\winhack\\createmeutex\\x64\\Release\\createmeutex.exe"); } void ShowMessage(TCHAR* lpszMessage, TCHAR* lpszTitle) { // 获取当前的Session ID DWORD dwSessionId = ::WTSGetActiveConsoleSessionId(); // 显示消息对话框 DWORD dwResponse = 0; ::WTSSendMessage(WTS_CURRENT_SERVER_HANDLE, dwSessionId, lpszTitle, (1 + ::lstrlen(lpszTitle)), lpszMessage, (1 + ::lstrlen(lpszMessage)), 0, 0, &dwResponse, FALSE); } // 突破SESSION 0隔离创建用户进程 BOOL CreateUserProcess(char* lpszFileName) { BOOL bRet = TRUE; DWORD dwSessionID = 0; HANDLE hToken = NULL; HANDLE hDuplicatedToken = NULL; LPVOID lpEnvironment = NULL; STARTUPINFO si = { 0 }; PROCESS_INFORMATION pi = { 0 }; si.cb = sizeof(si); do { // 获得当前Session ID dwSessionID = ::WTSGetActiveConsoleSessionId(); // 获得当前Session的用户令牌 if (FALSE == ::WTSQueryUserToken(dwSessionID, &hToken)) { ShowMessage("WTSQueryUserToken", "ERROR"); bRet = FALSE; break; } // 复制令牌 if (FALSE == ::DuplicateTokenEx(hToken, MAXIMUM_ALLOWED, NULL, SecurityIdentification, TokenPrimary, &hDuplicatedToken)) { ShowMessage("DuplicateTokenEx", "ERROR"); bRet = FALSE; break; } // 创建用户Session环境 if (FALSE == ::CreateEnvironmentBlock(&lpEnvironment, hDuplicatedToken, FALSE)) { ShowMessage("CreateEnvironmentBlock", "ERROR"); bRet = FALSE; break; } // 在复制的用户Session下执行应用程序,创建进程 if (FALSE == ::CreateProcessAsUser(hDuplicatedToken, lpszFileName, NULL, NULL, NULL, FALSE, NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT, lpEnvironment, NULL, &si, &pi)) { ShowMessage("CreateProcessAsUser", "ERROR"); bRet = FALSE; break; } } while (FALSE); // 关闭句柄, 释放资源 if (lpEnvironment) { ::DestroyEnvironmentBlock(lpEnvironment); } if (hDuplicatedToken) { ::CloseHandle(hDuplicatedToken); } if (hToken) { ::CloseHandle(hToken); } return bRet; }