本文中我将就遍历并获取当前系统 Session 信息的方法进行详细分析,并借此机会让大家能够从 Session 的角度对 WinNT 底层帐号和权限管理机制有更深入的了解。文章可能在内容上有所遗漏,希望大家补充和指正。

    虽然 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.