一、突破SESSION 0隔离创建用户进程
在Windows XP、Windows Server 2003,以及更老版本的Windows操作系统中,服务和应用程序使用相同的会话(SESSION)来运行,而这个会话是由第一个登录到控制台的用户来启动的,该会话就称为SESSION 0。将服务和用户应用程序一起在SESSION 0中运行会导致安全风险,因为服务会使用提升后的权限来运行,而用户应用程序使用用户特权(大部分都是非管理员用户)运行,这会使得恶意软件把某个服务作为攻击目标,通过“劫持”该服务以达到提升自己权限级别的目的。
从Windows VISTA开始,只有服务可以托管到SESSION 0中,用户应用程序和服务之间会进行隔离,并需要运行在用户登录系统时创建的后续会话中。如第一个登录用户创建Session 1,第二个登录用户创建Session 2,以此类推。
使用不同会话运行的实体(应用程序或服务)如果不将自己明确标注为全局命名空间,并提供相应的访问控制设置,那么将无法互相发送消息,共享UI元素或共享内核对象。
二、API
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;
}