Windows NT Session 概念的原理与应用浅析 [1] 遍历并获取信息

来源:互联网 发布:深入浅出mysql 编辑:程序博客网 时间:2024/04/29 15:58

Windows NT Session 概念的原理与应用浅析 [1] 遍历并获取信息 [草稿]

Posted on 2004-07-19 23:00 Flier Lu 阅读(3167) 评论(7)  编辑 收藏 网摘

Body:46.875,BeforeCate:0,62.5

    我在上一篇文章《DACL, NULL or not NULL》中曾简要地介绍了 Windows 系统中 Session 的概念,并且通过一个自己编写的小工具 KeSession 列出当前系统 Session信息。本文中我将就遍历并获取当前系统 Session 信息的方法进行详细分析,并借此机会让大家能够从 Session 的角度对 WinNT底层帐号和权限管理机制有更深入的了解。因为文章是以随笔方式写的,可能在内容上有所遗漏,希望大家补充和指正。

    虽然 Session 的概念非常重要,但因为其过于底层,除了处理用户登陆和安全相关事务的程序员,它对高层开发基本上是透明的。故而这方面的介绍文章和工具实在不多。
    而要学习研究 Session,必要的文章和工具肯定是必不可少的,首推就是 www.sysinternals.com 提供的 LoggonSessions。这个小工具能够将当前系统所有正在使用的 Session 以及相关信息列举出来,加上 -p 参数还可以进一步列出 Session 包括的进程列表。大概因为 www.sysinternals.com 的牛人认为程序太简单,就没有给出源码,呵呵,待会我们来看看如何自己实现一个功能完全相同的 KeSession 工具。
    此外 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 中也提供了遍历和信息获取的两个例子。例如:

void __fastcall TSessionManager::Refresh(void)
{
  LsaData<PLUID> sessions;
  ULONG count = 0;

  m_sessions.clear();

  Win32Check(STATUS_SUCCESS == LsaEnumerateLogonSessions(&count, &sessions));

  for(int i=0; i<(int)count; i++)
  {
    LsaData<PSECURITY_LOGON_SESSION_DATA> data;

    Win32Check(STATUS_SUCCESS == LsaGetLogonSessionData(&sessions[i], &data));

    if(data) m_sessions.push_front(TKernelSession(data));
  }
}

   值得注意的是如何将进程和 Session 关联起来。这需要使用 NTDLL::ZwQuerySystemInformation 或PSAPI::EnumProcesses 遍历系统进程,对每个进程获取其令牌,并使用 GetTokenInformation函数从令牌中获取统计信息 TokenStatistics,其中 TOKEN_STATISTICS::AuthenticationId就是此令牌所在 Session 的 ID。

const LUID __fastcall TSystemProcess::GetLogonID(void) const
{
  if(FID == 0 || FID == 4) return LUID();

  TSystemHandle hProc = ::OpenProcess(MAXIMUM_ALLOWED, FALSE, FID);

  if(!hProc.valid()) return LUID();

  TSystemHandle hToken;

  if(!::OpenProcessToken(hProc, TOKEN_QUERY, &hToken)) return LUID();

  TOKEN_STATISTICS stat;
  DWORD dwSize = 0;

  Win32Check(::GetTokenInformation(hToken, TokenStatistics, &stat, sizeof(stat), &dwSize));

  return stat.AuthenticationId;
}

    另外一个值得注意的是如何获取Session 的 SID。每个 Session 在启动时,系统会自动为其注册一个 SID,便于对 WinStation等对象进行权限控制。因为一个 WinStation 是可以由多个 Session 公用的,但又存在一个相同帐号的多个 Session 可能对此WinStation 访问权限不同的情况。所以系统为每个 Session 建立一个全局唯一的 SID,以使 WinStation等用户对象的控制粒度,能够细到针对每个 Session,哪怕这些 Session 使用相同的登陆帐号。
    使用 www.sysinternals.com提供的 Process Explorer 工具,查看 winlogon.exe 进程打开的 WinStation 对象,可以发现除了普通的Administrator 和 SYSTEM 等帐号外,还会有一个 S-1-5-5-xxx-yyy 的 ACE。这个 S-1-5-5开头的就是 Session 的 SID,而 xxx-yyy 一般是 Session 的编号。
    此 SID 可以通过在此Session 中的任意进程令牌获取。每个令牌可以有多个组 SID,而其中会有一个 Group SID 的类型属性包括SE_GROUP_LOGON_ID 标志,这个就是此令牌所属 Session 的 SID,通过此 SID 可以将对权限的控制在 Session这个粒度进行。可以通过 TokenMaster 工具查看令牌的此 Group SID。

Sid #8
SID:  S-1-5-5-0-51085
Use:  Logon SID
Name:  [Logon SID]
Domain Name:

SID Attribs:
  SE_GROUP_MANDATORY
  SE_GROUP_ENABLED_BY_DEFAULT
  SE_GROUP_ENABLED
  SE_GROUP_LOGON_ID

    至此,枚举并获取系统 Session 信息的基本功能已经介绍完了,下一节将进一步深入探讨系统内部是如何实现与使用 Session 概念。

原创粉丝点击