1、前言
在 Windows XP 和 Windows Server 2003 之前,用户和服务会共享同一个会话,而这个会话是由第一个登录到控制台的用户来启动的,该会话就称为Session 0。
而从Windows Vista 开始,Windows 采取了会话隔离的措施,确保系统进程和服务与普通用户会话隔离,即只有系统服务和后台进程可以运行在Session 0中。
由于不同的Session之间工作站互相独立,无法直接从一个Session中创建属于另一个Session的GUI进程,因此就需要下面要讲述的Session穿透技术。
2、核心技术
1. 进程创建
Windows提供了多个可以以指定用户上下文创建新进程的API函数,包括CreateProcessAsUser
、CreateProcessWithLogon
、CreateProcessWithTokenW
。
其中使用CreateProcessWithLogon
必须要持有明文的用户名和密码,而CreateProcessWithLogon
函数不支持XP系统,因此选择CreateProcessAsUser
。
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
检索指定用户的环境变量。
BOOL CreateEnvironmentBlock([out] LPVOID *lpEnvironment,[in, optional] HANDLE hToken,[in] BOOL bInherit
);
3. 用户令牌获取
想要通过CreateProcessAsUser
创建用户进程,最关键的一步就在于获取用户的令牌。而比较常用的获取用户令牌的方式有两种:
- 通过
WTSQueryUserToken
获取指定Session的令牌
BOOL WTSQueryUserToken([in] ULONG SessionId,[out] PHANDLE phToken
);
- 通过
SetTokenInformation
替换自身的令牌的Session
BOOL SetTokenInformation([in] HANDLE TokenHandle,[in] TOKEN_INFORMATION_CLASS TokenInformationClass,[in] LPVOID TokenInformation,[in] DWORD TokenInformationLength
);
4. 获取活动Session
虽然Windows提供了WTSGetActiveConsoleSessionId
函数可以直接获取当前活动会话,但实际使用中远程桌面访问的过程中,获取的活动会话与实际不符。
因此,更多情况下会使用WTSEnumerateSessionsW
枚举所有的会话,找到活动的会话ID。
BOOL WTSEnumerateSessionsW([in] HANDLE hServer,[in] DWORD Reserved,[in] DWORD Version,[out] PWTS_SESSION_INFOW *ppSessionInfo,[out] DWORD *pCount
);
3 具体实现
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++) {// 找到活动的会话IDif (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_DENIED
(Win7 )CreateProcessAsUser
失败,错误代码ERROR_FILE_NOT_FOUND
(Win - XP)
以上两个问题的本质上都是因为执行系统函数的过程中将具体的实现委托给第三方进程,而第三方进程在又反过来访问当前进程,导致第三方进程被驱动拦截。
因此,将第三方进程lsm.exe
和winlogon.exe
加入驱动保护过滤白名单即可。