穿透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加入驱动保护过滤白名单即可。

相关推荐
dllmayday25 分钟前
Linux 上用终端连接 WiFi
linux·服务器·windows
Curtain_Gin7 小时前
windows nvim lazy
windows
生而为虫9 小时前
Claude Code 最新版安装教程(Windows/Mac/Linux 全平台) 面向普通用户的 Claude Code 安装与模型接入指南
linux·windows·macos
DevilSeagull9 小时前
Windows 批处理 (Batch) 编程: 从入门到入土. (一) 基础概念与环境配置
开发语言·windows·后端·batch·语言
CyL_Cly10 小时前
Parsec(parsec安卓/windows/macos下载)
windows
2601_9583205711 小时前
【小白零基础上手 】钉钉内部机器人接入 OpenClaw 完整流程讲解(含安装包)
人工智能·windows·机器人·钉钉·open claw·open claw安装
love530love11 小时前
f2 项目(多平台的作品下载与接口数据处理)源码部署记录
人工智能·windows·f2
生而为虫12 小时前
在VScode中使用Claude Code agent并配置模型(仅mac电脑实际操作,windows电脑未实际操作如有问题可留言)
windows·vscode·macos
叶小鸡12 小时前
Java 篇-项目实战-天机学堂(从0到1)-day10
windows·microsoft
love530love13 小时前
如何在 Google Chrome 中强制开启 Gemini AI 侧边栏(完整图文教程)
前端·人工智能·chrome·windows