穿透Session 0隔离

1、前言

在 Windows XP 和 Windows Server 2003 之前,用户和服务会共享同一个会话,而这个会话是由第一个登录到控制台的用户来启动的,该会话就称为Session 0。

而从Windows Vista 开始,Windows 采取了会话隔离的措施,确保系统进程和服务与普通用户会话隔离,即只有系统服务和后台进程可以运行在Session 0中。

由于不同的Session之间工作站互相独立,无法直接从一个Session中创建属于另一个Session的GUI进程,因此就需要下面要讲述的Session穿透技术。

2、核心技术

1. 进程创建

Windows提供了多个可以以指定用户上下文创建新进程的API函数,包括CreateProcessAsUserCreateProcessWithLogon CreateProcessWithTokenW

其中使用CreateProcessWithLogon 必须要持有明文的用户名和密码,而CreateProcessWithLogon 函数不支持XP系统,因此选择CreateProcessAsUser

c 复制代码
BOOL CreateProcessAsUserW(
  [in, optional]      HANDLE                hToken,
  [in, optional]      LPCWSTR               lpApplicationName,
  [in, out, optional] LPWSTR                lpCommandLine,
  [in, optional]      LPSECURITY_ATTRIBUTES lpProcessAttributes,
  [in, optional]      LPSECURITY_ATTRIBUTES lpThreadAttributes,
  [in]                BOOL                  bInheritHandles,
  [in]                DWORD                 dwCreationFlags,
  [in, optional]      LPVOID                lpEnvironment,
  [in, optional]      LPCWSTR               lpCurrentDirectory,
  [in]                LPSTARTUPINFOW        lpStartupInfo,
  [out]               LPPROCESS_INFORMATION lpProcessInformation
);
2. 环境变量

由于不同用户的环境变量存在差异,因此创建进程时需要考虑是否需要指定用户的环境变量还是继承当前用户。如果需要可以使用CreateEnvironmentBlock检索指定用户的环境变量。

c 复制代码
BOOL CreateEnvironmentBlock(
  [out]          LPVOID *lpEnvironment,
  [in, optional] HANDLE hToken,
  [in]           BOOL   bInherit
);
3. 用户令牌获取

想要通过CreateProcessAsUser创建用户进程,最关键的一步就在于获取用户的令牌。而比较常用的获取用户令牌的方式有两种:

  1. 通过WTSQueryUserToken获取指定Session的令牌
c 复制代码
BOOL WTSQueryUserToken(
  [in]  ULONG   SessionId,
  [out] PHANDLE phToken
);
  1. 通过SetTokenInformation替换自身的令牌的Session
c 复制代码
BOOL SetTokenInformation(
  [in] HANDLE                  TokenHandle,
  [in] TOKEN_INFORMATION_CLASS TokenInformationClass,
  [in] LPVOID                  TokenInformation,
  [in] DWORD                   TokenInformationLength
);
4. 获取活动Session

虽然Windows提供了WTSGetActiveConsoleSessionId函数可以直接获取当前活动会话,但实际使用中远程桌面访问的过程中,获取的活动会话与实际不符。

因此,更多情况下会使用WTSEnumerateSessionsW枚举所有的会话,找到活动的会话ID。

c 复制代码
BOOL WTSEnumerateSessionsW(
  [in]  HANDLE             hServer,
  [in]  DWORD              Reserved,
  [in]  DWORD              Version,
  [out] PWTS_SESSION_INFOW *ppSessionInfo,
  [out] DWORD              *pCount
);

3 具体实现

c 复制代码
DWORD wts_session_count;
PWTS_SESSION_INFO wts_session;
// 枚举当前系统所有的会话
if (!WTSEnumerateSessionsW(WTS_CURRENT_SERVER_HANDLE, 0, 1, &wts_session, &wts_session_count)) {
	return false;
}
DWORD active_session_id = -1;
for (auto i = 0ul; i < wts_session_count; i++) {
    // 找到活动的会话ID
	if (wts_session[i].State == WTSActive) {
		active_session_id = wts_session[i].SessionId;
		break;
	}
}
// 释放资源
WTSFreeMemory(wts_session);

HANDLE htoken = nullptr;
LPVOID env = nullptr;
do {
    // 查询用户令牌
	HANDLE user_token;
	if (!WTSQueryUserToken(active_session_id, &user_token)) {
		break;
	}
    
	TOKEN_ELEVATION_TYPE token_type;
	DWORD token_size;
    // 获取令牌类型
	if (!GetTokenInformation(user_token, TokenElevationType, (VOID*)&token_type, sizeof(token_type), &token_size)) {
		CloseHandle(user_token);
		break;
	}
    // 判断令牌类型
	if (TokenElevationTypeLimited == token_type) {
        // 从限制令牌获取提权令牌
		if (!GetTokenInformation(user_token, TokenLinkedToken, (VOID*)&htoken, sizeof(htoken), &token_size)) {
			CloseHandle(user_token);
			break;
		}
		CloseHandle(user_token);
	}
	else {
		htoken = user_token;
	}
    
	PROCESS_INFORMATION pi;
	STARTUPINFO si;
	ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
	ZeroMemory(&si, sizeof(STARTUPINFO));
	si.cb = sizeof(STARTUPINFO);
	// 创建用户环境
	if (!CreateEnvironmentBlock(&env, htoken, FALSE)) {
		break;
	}
    // 创建指定用户进程
	if (!CreateProcessAsUser(htoken, NULL, (LPWSTR)command,
		NULL, NULL, FALSE, CREATE_NEW_CONSOLE | CREATE_UNICODE_ENVIRONMENT, env, NULL, &si, &pi)) {
		break;
	}
} while (false);
// 释放资源
if (env) {
    DestroyEnvironmentBlock(env);
}
if (htoken) {
    CloseHandle(htoken);
}

4、后话

这里简单描述一下笔者在开发安全产品的过程中,遇到的一些问题。

  • WTSQueryUserToken失败,错误代码ERROR_ACCESS_DENIEDWin7
  • CreateProcessAsUser失败,错误代码ERROR_FILE_NOT_FOUNDWin - XP

以上两个问题的本质上都是因为执行系统函数的过程中将具体的实现委托给第三方进程,而第三方进程在又反过来访问当前进程,导致第三方进程被驱动拦截。

因此,将第三方进程lsm.exewinlogon.exe加入驱动保护过滤白名单即可。

相关推荐
Mast Sail19 小时前
windows下authas调试tomcat
java·windows·tomcat·authas
疯狂的挖掘机19 小时前
记一次从windows连接远程Linux系统来控制设备采集数据方法
linux·运维·windows
前进的程序员21 小时前
C++ 在 Windows 和 Linux 平台上的开发差异及常见问题
linux·c++·windows
大笨象、小笨熊1 天前
【Win32 API】 lstrcpyA()
windows
双叶8361 天前
(C语言)超市管理系统 (正式版)(指针)(数据结构)(清屏操作)(文件读写)
c语言·开发语言·数据结构·c++·windows
繁星无法超越1 天前
详解Windows(九)——系统性能优化
windows·stm32·性能优化
IT小郭.1 天前
使用 Docker Desktop 安装 Neo4j 知识图谱
windows·python·sql·docker·知识图谱·database·neo4j
qh0526wy1 天前
金融接口基方法Python
windows·python·金融
王有品1 天前
Java 集合框架对比全解析:单列集合 vs 双列集合
java·windows·python
感谢地心引力2 天前
【Matlab】最新版2025a发布,深色模式、Copilot编程助手上线!
开发语言·windows·matlab·copilot