Windows 窗口站的基本原理

Windows 窗口站的基本原理

在Windows 下面有一个比较特殊的概念,就是Windows Station;对于会话,桌面这些东西还算比较好理解(对于会话的基本概念可以参考Windows 会话内存隔离原理),但是什么是Windows Station呢?本文来讨论一下Windows Station的基本知识。

注意:下面的代码来自REACTOS的代码分析,Windows的真实代码可能和其有出入,但是实现原理基本一致,我们探讨原理使用的是REACTOS源码(因此下面的源码只是说明原理,并不代表Windows的真实实现)。

1. 基本概念

Window station 主要的一个作用是安全特性,它被设计成为一个限制操作系统中窗口环境的沙箱。我们知道,对于一个文件或者内核对象,都通过一个安全描述符的东西来控制棋访问权限,Windows通过对访问控制列表(ACL)的控制,使得每个用户(TOKEN)具有不同的访问权限。

但是,如果将ACL这一套放置到每个GUI窗口对象中,并且对每一个窗口消息都进行相关性的权限检查的话,将会导致性能大大的下降。那么Windows就引入了Windows Station,Windows让窗口相互通知而不需要执行任何的安全检查,当然我们是在一个Windows Station的私有环境中才允许这样做。

Windows Station有如下的特性:

  • 每个窗口站包含一个剪贴板,一个原子表,一个或者多个桌面对象
  • 每个窗口站对象都是一个安全对象
  • 当一个窗口站创建时,它被关联到创建它的进程并且赋给当前的会话。
  • 交互的窗口站, WinSta0是唯一的一个可以显示用户界面和接收用户输入的窗口站。它被赋给交互用户的登录会话,包括键盘,鼠标,显示设备。其他所有的窗口站都是非交互的,也就说它们不能显示用户界面,也不能接收用户输入。
  • windows station基本上可以被描述为包含桌面和进程的安全边界。因此,一个session可以包含多个windows station,而每个windows station又可拥有多个桌面。

2. Station的一个例子

这里我们用一个例子展示session0的基本组成(如下图),其中有个名为Bob的用户登入。正如你所看到的,Winsta0包含用户控制台中的所有进程还有任何被标记为可交互(Interactive)的任何服务。

本例中,Winsta0包括winlogon.exe,explorer.exe和其他需要与用户交互的服务

名为service-0x0-3e7$的Windows station 拥有在Local system帐号下且不与用户交互的所有服务。本例中service.exe正是这样的服务。

SQL进程被载入到其自身的windows station并且使用自己的证书认证,所以它不属于其他两个windows station。
在这里插入图片描述

因此,我们可以将上图总结如下:

  1. 整个图展示的是session0.
  2. 在Bob帐号下的所有进程都载入到Winsta0。
  3. 在local system帐号下可交互进程载入到winsta0。
  4. 在local system帐号下不可交互进程载入到Service-0x0-3e7$ windows station 。
  5. 在私有证书下启动的进程载入到其自己的windows station(像SQL)。

3. WINSTATION_OBJECT的基本结构

typedef struct _WINSTATION_OBJECT
{
    DWORD dwSessionId;

    LIST_ENTRY DesktopListHead;
    PRTL_ATOM_TABLE AtomTable;
    HANDLE ShellWindow;
    HANDLE ShellListView;

    ULONG Flags;
    struct _DESKTOP* ActiveDesktop;

    PTHREADINFO    ptiClipLock;
    PTHREADINFO    ptiDrawingClipboard;
    PWND           spwndClipOpen;
    PWND           spwndClipViewer;
    PWND           spwndClipOwner;
    PCLIP          pClipBase;     // Not a clip object.
    DWORD          cNumClipFormats;
    INT            iClipSerialNumber;
    INT            iClipSequenceNumber;
    INT            fClipboardChanged : 1;
    INT            fInDelayedRendering : 1;

    PWND           spwndClipboardListener;
    LUID           luidEndSession;
    LUID           luidUser;
    PVOID          psidUser;

} WINSTATION_OBJECT, *PWINSTATION_OBJECT;

在这里我们可以看到如下信息:

  • dwSessionId : 窗口站所处的会话。
  • AtomTable : 原子表。
  • DesktopListHead;窗口站下面的桌面通过这个结构体连接。
  • ActiveDesktop;当前活动的桌面。

在Windows下面真实的窗口站的类型为tagWINDOWSTATION,与上面的声明不太一致,但是并不影响整个流程的理解。

3.1 原子表

这里有一个概念就是原子表,那么什么是原子表呢?

Win32系统中,为了实现信息共享,系统维护了一张全局原子表,用于保存字符串与之对应的标志符的组合。应用程序在原子表中可以放置字符串,并接收一个16位整数值(叫做原子,即atom),它可以用来提取该字符串,放在原子表中的字符串叫做原子的名字。

系统提供许多原子表,每个原子表用于不同的目的;例如,动态数据交换(DDE)应用程序使用全局原子表(global atom table)与其他应用程序共享项目名称和主题名称字符串,不用传递实际的字符串,一个DDE应用程序传递全局原子给它的父进程,父进程使用原子提取原子表中的字符串。

关于对原子的操作,有一组专门的API函数:

  • GlobalAddAtom : 在表中增加全局原子。
  • GlobalDeleteAtom : 在表中删除全局原子。
  • GlobalFindAtom : 在表中搜索全局原子。
  • GlobalGetAtomName : 从表中获取全局原子。

其中原子表RTL_ATOM_TABLE的结构如下:

typedef struct _RTL_ATOM_TABLE
{
    ULONG Signature;
    union
    {
#ifdef NTOS_MODE_USER
        RTL_CRITICAL_SECTION CriticalSection;
#else
        FAST_MUTEX FastMutex;
#endif
    };
    union
    {
#ifdef NTOS_MODE_USER
        RTL_HANDLE_TABLE RtlHandleTable;
#else
        PHANDLE_TABLE ExHandleTable;
#endif
    };
    ULONG NumberOfBuckets;
    PRTL_ATOM_TABLE_ENTRY Buckets[1];
} RTL_ATOM_TABLE, *PRTL_ATOM_TABLE;

typedef struct _RTL_ATOM_TABLE_ENTRY
{
    struct _RTL_ATOM_TABLE_ENTRY *HashLink;
    USHORT HandleIndex;
    USHORT Atom;
    USHORT ReferenceCount;
    UCHAR Flags;
    UCHAR NameLength;
    WCHAR Name[1];
} RTL_ATOM_TABLE_ENTRY, *PRTL_ATOM_TABLE_ENTRY;

从这里我们可以看到原子表大概类似一个哈希表或者一个句柄表之类的数据结构来管理(其实就是可ATOM到字符串的转换)。

4. WinSta0

我们经常说,只有WinSta0 这个窗口站,才能外接交互式输入设备,这个是为什么呢?我们知道交互式输入线程是RawInputThread,我们看一下这个线程的相关实现流程:

VOID NTAPI
RawInputThreadMain(VOID)
{
    //...
    Status = ObOpenObjectByPointer(InputWindowStation,
                                   0,
                                   NULL,
                                   MAXIMUM_ALLOWED,
                                   ExWindowStationObjectType,
                                   UserMode,
                                   (PHANDLE)&hWinSta);
    if (NT_SUCCESS(Status))
    {
        UserSetProcessWindowStation(hWinSta);
    }
    else
    {
        ASSERT(FALSE);
    }
    //...
}

InputWindowStation这个就是我们的WinSta0窗口站,输入线程通过UserSetProcessWindowStation将进程进行绑定窗口站,因此所有交互式输入设备被限制在改窗口站中生效了。

5. 沙箱隔离

Windows Station是怎么隔离相关操作的呢?我们有一个操作可以看一下,这个操作就是剪切板(为什么使用剪切板而不是窗口消息呢?因为窗口消息在桌面就被隔离了)。

HANDLE NTAPI
UserSetClipboardData(UINT fmt, HANDLE hData, PSETCLIPBDATA scd)
{
    //...
    IntAddFormatedData(pWinStaObj, fmt, hData, scd->fGlobalHandle, FALSE);
    //...
}

static PCLIP NTAPI
IntAddFormatedData(PWINSTATION_OBJECT pWinStaObj, UINT fmt, HANDLE hData, BOOLEAN fGlobalHandle, BOOL bEnd)
{
    PCLIP pElement = NULL;
    //...
    pElement = IntGetFormatElement(pWinStaObj, fmt);
    //...
}

static PCLIP FASTCALL
IntGetFormatElement(PWINSTATION_OBJECT pWinStaObj, UINT fmt)
{
    DWORD i;

    for (i = 0; i < pWinStaObj->cNumClipFormats; ++i)
    {
        if (pWinStaObj->pClipBase[i].fmt == fmt)
            return &pWinStaObj->pClipBase[i];
    }

    return NULL;
}

从这里可以发现,剪切板是从窗口站中取出的相关结构体,那么剪切板也就会达到相应隔离的效果。

在每一个进程中存在一个窗口站的管理结构,表示进程属于哪个窗口站,通过如下方法获取进程所属的窗口站:

HWINSTA FASTCALL
UserGetProcessWindowStation(VOID)
{
    PPROCESSINFO ppi = PsGetCurrentProcessWin32Process();

    return ppi->hwinsta;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值