虽然 Session 的概念非常重要,但因为其过于底层,除了处理用户登陆和安全相关事务的程序员,它对高层开发基本上是透明的。故而这方面的介绍文章和工具实在不多。
而要学习研究 Session,必要的文章和工具肯定是必不可少的,首推就是 www.sysinternals.com 提供的 LoggonSessions。这个小工具能够将当前系统所有正在使用的 Session 以及相关信息列举出来,加上 -p 参数还可以进一步列出 Session 包括的进程列表。但是 www.sysinternals.com 的牛人并没有给出源码,所以只有我们自己来实现一个功能完全相同的工具了。
此外 Keith Brown 的 Handle Logons in Windows NT and Windows 2000 with Your Own Logon Session Broker 一文,以及文章所附源码工具 cmdasuser 也是必不可少的。此工具能够以指定用户身份,建立新的 Session,并载入用户配置和环境,创建一个完整的 Session 试验环境。
最后就是 Jeffrey Richter 和 Jason Clark 在 Programming Server-Side Applications for Microsoft Windows 一书的第11章 User Context 中,提供的非常强大的令牌管理工具,Token Master,可以查看当前系统任意进程、线程的令牌 (Token)。
LoggonSessions 程序的功能实际上很容易实现,通过调用本机安全授权服务 (LSASS) 提供的 LsaEnumerateLogonSessions 函数枚举当前登陆会话,对每个会话调用 LsaGetLogonSessionData 函数获取进一步的会话信息。
NTSTATUS NTAPI LsaEnumerateLogonSessions(
PULONG LogonSessionCount,
PLUID* LogonSessionList
);
NTSTATUS LsaFreeReturnBuffer(
PVOID Buffer
);
LsaEnumerateLogonSessions 函数使用非常简单,直接返回会话数量和保存每个会话ID的数组。此数组每个元素是一个 LUID 类型,实际上就是一个 64bit 整数,使用完后通过 LSAFreeReturnBuffer 函数释放缓冲区。
NTSTATUS NTAPI LsaGetLogonSessionData(
PLUID LogonId,
PSECURITY_LOGON_SESSION_DATA* ppLogonSessionData
);
对每个会话的 LUID,可以调用 LsaGetLogonSessionData 函数获取进一步的信息。此函数将返回一个保存会话信息的结构,使用完后通过 LSAFreeReturnBuffer 函数释放缓冲区。
typedef struct _SECURITY_LOGON_SESSION_DATA {
ULONG Size;
LUID LogonId;
LSA_UNICODE_STRING UserName;
LSA_UNICODE_STRING LogonDomain;
LSA_UNICODE_STRING AuthenticationPackage;
ULONG LogonType;
ULONG Session;
PSID Sid;
LARGE_INTEGER LogonTime;
LSA_UNICODE_STRING LogonServer;
LSA_UNICODE_STRING DnsDomainName;
LSA_UNICODE_STRING Upn;
} SECURITY_LOGON_SESSION_DATA, *PSECURITY_LOGON_SESSION_DATA;
SECURITY_LOGON_SESSION_DATA 结构保存的会话数据包括:
Size 保存此结构的大小,为兼容以后大小变化提供的可变数据模式
LogonId 保存此 Session 的 LUID 内部编号,用户全局定位,如计算机登陆的 SYSTEM 帐号的 LogonId 为 999(0x3e7)
UserName 和 LogonDomain 保存此 Session 的登陆帐号和域,以 LogonDomain\UserName 组合后就是完整帐号名,如 SKY\FLIER$ 或 FLIER\Administrator
AuthenticationPackage 保存此 Session 的认证方式。一般来说本机都是 NTLM,网络会话是 Negotiate
LogonType 则相对复杂,除了最常见的交互登陆和网络登陆外,还有批处理和服务登陆,以及更少见的代理等方式
typedef enum _SECURITY_LOGON_TYPE {
Interactive = 2, // Interactively logged on (locally or remotely)
Network, // Accessing system via network
Batch, // Started via a batch queue
Service, // Service started by service controller
Proxy, // Proxy logon
Unlock, // Unlock workstation
NetworkCleartext, // Network logon with cleartext credentials
NewCredentials, // Clone caller, new default credentials
RemoteInteractive, // Remote, yet interactive. Terminal server
CachedInteractive // Try cached credentials without hitting the net.
} SECURITY_LOGON_TYPE, *PSECURITY_LOGON_TYPE;
更为完整的登陆类型列表在 LogonUserEx 函数的参数介绍中可以看到。
交互式(Interactive)登陆就是用户通过计算机控制台以物理方式登陆到系统的会话;
网络(Network)登陆则是通过网络邻居等方式远程访问此计算机时的会话;
批处理(Batch)登陆一般在使用批处理队列时使用,如 COM SCM
服务(Service)登陆则适用于后台服务以系统帐号运行,如 System SCM
以上四种登陆类型是最为常见的,而附加的登陆类型适用于特殊情况
代理(Proxy)登陆基本上很少被用到,MSDN里面干脆说此类型不被支持
解锁(Unlock)登陆是当用户锁屏后,GINA与LogonUser函数配合进行解锁时用到
网络明文(NetworkCleartext)登陆允许用户在通过 LogonUserEx 登陆到系统后,用户名和密码被保存以再次登陆到其他服务器
新凭证(NewCredentials)登陆允许用户对本地访问使用克隆的当前令牌,当对外部网络连接使用单独的凭证
远程交互(RemoteInteractive)登陆是终端服务连接到服务器时使用的登陆方式,允许与交互登陆类似的桌面操作,当无需在本地进行
缓存交互(CachedInteractive)登陆则对用户的凭证进行缓存,避免冗余网络验证等等
登陆类型直接决定了此会话的能力以及所受限制,后面有机会再分别解析。
Session 字段保存了一个显式会话编号。此编号一般是用户终端服务器,本地会话和交互登陆会话此编号都为 0,而远程交互登陆则能够获取新的编号。系统通过此编号,在对象管理器中,将 \BaseNamedObjects 映射到 \Sessions\1\ 等等名字空间中,有兴趣的朋友可以进一步参考 NT 对象管理器相关文章。
Sid 字段保存了此会话登陆帐号的 Sid,与前面的登陆名对应;
LogonTime 则是一个非本地时区的 FILETIME 格式的时间,表示会话开始时间;
最后的 LogonServer、DnsDomainName 和 Upn 则一般在使用活动目录时,提供帐号的进一步信息。
在了解这些信息后,很容易就能写出一个遍历登陆会话信息的程序来,MSDN 中也提供了遍历和信息获取的两个例子。
在此,我只是把MSDN中的例子进行了扩展,具体内容用户可以参考程序。程序很乱不当之处很指正。并没有实现LoggonSessions中的/p参数功能。
#include
#include
#include
#include
// The following constant may be defined by including NtStatus.h.
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
// The LSA authentication functions are available in Unicode only.
#define UNICODE
void GetSessionData(
PLUID session
);
BOOL GetTextualSid(
PSID pSid, // binary Sid
LPTSTR TextualSid, // buffer for Textual representaion of Sid
LPDWORD cchSidSize // required/provided TextualSid buffersize
);
int _cdecl main()
{
PLUID sessions;
ULONG count;
NTSTATUS retval;
int i;
wprintf(L"LogonSessions version 1.0 \n");
wprintf(L"Copyright (c) 2007 suiyingjie \n");
wprintf(L"SafeSofts - www.chinasafe.org.cn \n");
retval = LsaEnumerateLogonSessions(&count, &sessions);
if (retval != STATUS_SUCCESS) {
wprintf (L"LsaEnumerate failed %lu\n",
LsaNtStatusToWinError(retval));
return 1;
}
wprintf (L"\nEnumerate sessions received %lu sessions.\n", count);
// Process the array of session LUIDs...
for (i =0;i < (int) count; i++) {
wprintf(L"\n[%lu] ", i);
GetSessionData (&sessions[i]);
}
// Free the array of session LUIDs allocated by the LSA.
LsaFreeReturnBuffer(sessions);
return 0;
}
void GetSessionData(PLUID session)
{
PSECURITY_LOGON_SESSION_DATA sessionData = NULL;
NTSTATUS retval;
WCHAR buffer[256];
WCHAR *usBuffer;
int usLength;
// Check for a valid session.
if (!session )
{
wprintf(L"Error - Invalid logon session identifier.\n");
return;
}
// Get the session information.
retval = LsaGetLogonSessionData (session, &sessionData);
if (retval != STATUS_SUCCESS)
{
// An error occurred. Tell the world.
wprintf (L"LsaGetLogonSessionData failed %lu \n",
LsaNtStatusToWinError(retval));
// If session information was returned, free it.
if (sessionData)
{
LsaFreeReturnBuffer(sessionData);
}
return;
}
// Determine whether there is session data to parse.
if (!sessionData)
{ // no data for session
wprintf(L"Invalid logon session data. \n");
return;
}
// Get the user name.
if (sessionData->UserName.Buffer != NULL)
{
usBuffer = (sessionData->UserName).Buffer;
usLength = (sessionData->UserName).Length;
if(usLength < 256)
{
lstrcpynW (buffer, usBuffer, usLength);
lstrcatW (buffer,L"");
}
else
{
wprintf(L"User name too long for buffer. Exiting program.\n");
exit(1);
}
wprintf(L"\tLogon session %08x:%08x \n", sessionData->LogonId.HighPart, sessionData->LogonId.LowPart);
wprintf (L"\tUser name: \t%s",buffer);
}
else
{
wprintf (L"\nMissing user name.\n");
LsaFreeReturnBuffer(sessionData);
return;
}
// get logon method
switch((SECURITY_LOGON_TYPE)sessionData->LogonType)
{
case Interactive:
wprintf(L"\n\tLogon type: \tinteractively ");
break;
case Network:
wprintf(L"\n\tLogon type: \tNetwork ");
break;
case Batch:
wprintf(L"\n\tLogon type: \tBatch ");
break;
case Service:
wprintf(L"\n\tLogon type: \tService ");
break;
case Proxy:
wprintf(L"\n\tLogon type: \tProxy ");
break;
case Unlock:
wprintf(L"\n\tLogon type: \tUnlock ");
break;
default:
wprintf(L"\n\tLogon type: \tnone ");
break;
}
// Get the authentication package name.
memset(buffer, 0, 256);
if (sessionData->AuthenticationPackage.Buffer != NULL)
{
usBuffer = (sessionData->AuthenticationPackage).Buffer;
usLength = (sessionData->AuthenticationPackage).Length;
if(usLength < 256)
{
lstrcpynW (buffer, usBuffer, usLength);
lstrcatW (buffer,L"");
}
else
{
wprintf(L"\nAuthentication package too long for buffer. Exiting program.");
exit(1);
}
wprintf(L"\n\tAuth package: \t%s ",buffer);
}
else
{
wprintf (L"\nMissing authentication package.");
LsaFreeReturnBuffer(sessionData);
return;
}
// Get the Terminal Services session identifier
wprintf(L"\n\tSession: \t%d ", sessionData->Session);
// Get the SID
char szSid[MAX_PATH];
DWORD dwRet;
dwRet = 255;
GetTextualSid(sessionData->Sid, szSid, &dwRet);
WCHAR wszSid[MAX_PATH];
DWORD dwMinSize;
dwMinSize = MultiByteToWideChar (CP_ACP, 0, szSid, -1, NULL, 0);
MultiByteToWideChar (CP_ACP, 0, szSid, -1, wszSid, dwMinSize);
wprintf(L"\n\tSid: \t\t%s", wszSid);
// get the logon time
// LogonTime is a FILETIME 格式的时间表示方式。
// typedef struct STRUCT tagFILETIME {
// DWORD dwLowDateTime;
// DWORD dwHighDateTime;
// } FILETIME;
FILETIME m_FileTime;
m_FileTime.dwHighDateTime = sessionData->LogonTime.HighPart;
m_FileTime.dwLowDateTime = sessionData->LogonTime.LowPart;
CTime m_Time(m_FileTime);
wprintf(L"\n\tLogon time: \t%04d-%02d-%02d %02d:%02d:%02d", m_Time.GetYear(), m_Time.GetMonth(), m_Time.GetDay(), m_Time.GetHour(), m_Time.GetMinute(), m_Time.GetSecond());
// Get the Logon server.
memset(buffer, 0, 256);
if(sessionData->LogonServer.Buffer != NULL)
{
usBuffer = (sessionData->LogonServer).Buffer;
usLength = (sessionData->LogonServer).Length;
if(usLength < 256)
{
lstrcpynW (buffer, usBuffer, usLength);
lstrcatW (buffer,L"");
}
else
{
wprintf(L"Logon server too long for buffer. Exiting program.\n");
exit(1);
}
wprintf(L"\n\tLogon server: \t%s", buffer);
}
else
{
wprintf(L"\n\tLogon server: \t<none>");
}
// Get the domain name.
memset(buffer, 0, 256);
if (sessionData->LogonDomain.Buffer != NULL)
{
usBuffer = (sessionData->LogonDomain).Buffer;
usLength = (sessionData->LogonDomain).Length;
if(usLength < 256)
{
lstrcpynW (buffer, usBuffer, usLength);
lstrcatW (buffer,L"");
}
else
{
wprintf(L"\nLogon domain too long for buffer. Exiting program.\n");
exit(1);
}
wprintf(L"\n\tDNS domain: \t%s ",buffer);
}
else
{
wprintf (L"\nMissing authenticating domain information. \n");
LsaFreeReturnBuffer(sessionData);
return;
}
// get the user principal name (UPN) for the owner of the logon session.
memset(buffer, 0, 256);
if(sessionData->Upn.Buffer != NULL)
{
usBuffer = (sessionData->Upn).Buffer;
usLength = (sessionData->Upn).Length;
if(usLength < 256)
{
lstrcpynW (buffer, usBuffer, usLength);
lstrcatW (buffer,L"");
}
else
{
wprintf(L"Upn too long for buffer. Exiting program.\n");
exit(1);
}
wprintf(L"\n\tUPN: \t%s\n", buffer);
}
else
{
wprintf(L"\n\tUPN: \t<none> \n");
}
// Free the memory returned by the LSA.
LsaFreeReturnBuffer(sessionData);
return;
}
// 得到SID的字符串表示。
BOOL GetTextualSid(
PSID pSid, // binary Sid
LPTSTR TextualSid, // buffer for Textual representaion of Sid
LPDWORD cchSidSize // required/provided TextualSid buffersize
)
{
PSID_IDENTIFIER_AUTHORITY psia;
DWORD dwSubAuthorities;
DWORD dwCounter;
DWORD cchSidCopy;
//
// test if Sid passed in is valid
//
if(!IsValidSid(pSid)) return FALSE;
// obtain SidIdentifierAuthority
psia = GetSidIdentifierAuthority(pSid);
// obtain sidsubauthority count
dwSubAuthorities = *GetSidSubAuthorityCount(pSid);
//
// compute approximate buffer length
// S-SID_REVISION- + identifierauthority- + subauthorities- + NULL
//
cchSidCopy = (15 + 12 + (12 * dwSubAuthorities) + 1) * sizeof(TCHAR);
//
// check provided buffer length.
// If not large enough, indicate proper size and setlasterror
//
if(*cchSidSize < cchSidCopy) {
*cchSidSize = cchSidCopy;
SetLastError(ERROR_INSUFFICIENT_BUFFER);
return FALSE;
}
//
// prepare S-SID_REVISION-
//
cchSidCopy = wsprintf(TextualSid, TEXT("S-%lu-"), SID_REVISION );
//
// prepare SidIdentifierAuthority
//
if ( (psia->Value[0] != 0) || (psia->Value[1] != 0) ) {
cchSidCopy += wsprintf(TextualSid + cchSidCopy,
TEXT("0x%02hx%02hx%02hx%02hx%02hx%02hx"),
(USHORT)psia->Value[0],
(USHORT)psia->Value[1],
(USHORT)psia->Value[2],
(USHORT)psia->Value[3],
(USHORT)psia->Value[4],
(USHORT)psia->Value[5]);
} else {
cchSidCopy += wsprintf(TextualSid + cchSidCopy,
TEXT("%lu"),
(ULONG)(psia->Value[5] ) +
(ULONG)(psia->Value[4] << 8) +
(ULONG)(psia->Value[3] << 16) +
(ULONG)(psia->Value[2] << 24) );
}
//
// loop through SidSubAuthorities
//
for(dwCounter = 0 ; dwCounter < dwSubAuthorities ; dwCounter++) {
cchSidCopy += wsprintf(TextualSid + cchSidCopy, TEXT("-%lu"),
*GetSidSubAuthority(pSid, dwCounter) );
}
//
// tell the caller how many chars we provided, not including NULL
//
*cchSidSize = cchSidCopy;
return TRUE;
}
编译环境:VC6+SP6+windows2003.