概述
传统令牌窃取技术如下:
OpenProcess / NtOpenProcess -> OpenProcessToken -> DuplicateTokenEx -> CreateProcessWithTokenW.
这种方法目前很容易被EDR、XDR或者其他安全解决方案检测到。普通的令牌模拟会操作进程令牌,而WTS技术在进程中仅调用OpenProcessToken(在我们自己的进程上),其余操作通过RPC命名管道PipeLSM_API_service完成,另一个进程为我们完成了繁重的任务。
分析过程
WTSQueryUserToken
BOOL WTSQueryUserToken(
[in] ULONG SessionId,
[out] PHANDLE phToken
);
传入一个登录会话ID:SessionId,返回登录用户的令牌句柄。文档还给出了怎么获取会话ID:
A Remote Desktop Services session identifier. Any program running in the context of a service will have a session identifier of zero (0). You can use the WTSEnumerateSessions[1] function to retrieve the identifiers of all sessions on a specified RD Session Host server.
WTSEnumerateSessions
BOOL WTSEnumerateSessionsA(
[in] HANDLE hServer,
[in] DWORD Reserved,
[in] DWORD Version,
[out] PWTS_SESSION_INFOA *ppSessionInfo,
[out] DWORD *pCount
);
通过整理各种碎片信息可以构建以下函数序列:
WTSEnumerateSessionsA → WTSQuerySessionInformationA -> WTSQueryUserToken -> CreateProcessAsUserW
1.WTSEnumerateSessionsA
: 这是用于枚举当前计算机上所有用户会话的函数。它可以列出所有已登录用户的会话,以及它们的会话ID等信息。2.WTSQuerySessionInformationA
: 这个函数用于查询特定用户会话的信息,通常用于获取用户会话的详细信息,如用户名、域名、连接状态等。3.WTSQueryUserToken
: 这个函数用于获取指定用户会话的用户令牌(token)。用户令牌是用于验证用户身份和访问权限的关键对象。通常,通过这个函数获取的用户令牌用于后续的操作,以确保进程在正确的上下文中运行。4.CreateProcessAsUserW
: 这是一个用于在指定用户会话中创建一个新进程的函数。它接受用户令牌和其他参数,以创建一个以指定用户身份运行的进程。
但是需要注意一个问题。WTSQueryUserToken
需要服务的上下文环境才能生效,并且该服务必须具有SeDelegateSessionUserImpersonatePrivilege
权限和SE_TCB_NAME
权限。另外枚举阶段可以通过使用名为pipeLSM_API_service的RPC命名管道(WTSEnumeratureSessionsA→ WTSQuerySessionInformationA),这意味着我们可以使用RPC来发现每个站点哪些用户连接到,而无需在系统上部署代码。由于这个名称管道几乎没有文档记录,我们可以猜测LSM代表Local Security Manager(只是猜测)。下面借助WTSImpersonator[2]项目介绍窃取过程。
代码简述
枚举功能,可以使用WTSOpenServer或WTSOpenServerEx函数来检索特定服务器的句柄,也可以使用WTS_CURRENT_server_handle来使用承载应用程序的RD会话主机服务器:
HANDLE hServer = ServerName ? WTSOpenServerA(ServerName) : WTS_CURRENT_SERVER_HANDLE;
PWTS_SESSION_INFOA pSessionInfo;
bSuccess = WTSEnumerateSessionsA(hServer, 0, 1, &pSessionInfo, &count);
if (!bSuccess)
{
printf("WTSEnumerateSessions failed: %dn", (int)GetLastError());
return 0;
}
pSessionInfo结构如下:
typedef struct _WTS_SESSION_INFOA {
DWORD SessionId;
LPSTR pWinStationName;
WTS_CONNECTSTATE_CLASS State;
} WTS_SESSION_INFOA, * PWTS_SESSION_INFOA;
循环读取获取到的sessionid信息并打印出来:
WTSQueryUserToken根据指定的sessionid获取其token,并把token相关信息打印输出:
再使用该token的权限打开目标进程,这里后面使用cmd测试:
if (CreateProcessAsUserW(hToken, PATH, NULL, NULL,
NULL, FALSE, 0, NULL, NULL, &si, &pi))
{
/* Process has been created; work with the process and wait for it to
terminate. */
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
执行效果
我们现在普通环境下枚举当前的会话ID:
WTSImpersonator.exe -m enum
此时我们使用id为1的token进行窃取是没啥效果的:
WTSImpersonator.exe -m exec -id 1 -c C:WindowsSystem32cmd.exe
因为不在windows服务环境下,我们尝试使用
PsExec64.exe -accepteula -s cmd.exe
,启动cmd,然后同样执行上述命令,发现就成功使用目标token创建cmd:
总结
道高一尺魔高一丈
References
[1]
WTSEnumerateSessions: https://learn.microsoft.com/en-us/windows/desktop/api/wtsapi32/nf-wtsapi32-wtsenumeratesessionsa[2]
WTSImpersonator: https://github.com/OmriBaso/WTSImpersonator
信息安全吹水:
原文始发于微信公众号(TIPFactory情报工厂):WTS API 令牌窃取技术
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论