WindowStations窗口站

WindowStations窗口站

    Window stations 和桌面可能是Windows NT服务中最与众不同的了 绝大多数编程者都不会直接接触这两种对象 尽管用户每时每刻都要碰到它们 window station 和桌面对象就象其它Windows NT对象 如事件 互斥量和信号量一样 是安全(securable)的 一个window station 对象包括一个剪贴板 一个全局原子集和更多的桌面对象 一个window station或者是可见的或者是不可见的 一个可见的window station接收用户来自于鼠标或键盘的输入 一个显示设备也与之相连这样信息可以显示给交互式用户

    在 Windows NT 中 只有一个window station 能被看得到 就是WinSta 可见的window station也被定义成交互式的 一个不可见的window station是不可交互的 而且也不能接收任何用户的输入也没有显示设备与之相连

    如前所述 桌面包含在window station对象中 一个桌面对象包含一个逻辑的显示表面 和窗口 菜单等 只有属于可见window station的桌面才能被看见并接收用户的输入 这个桌面叫做活动桌面

    作为交互式用户你在不同的时候碰到三种不同的桌面 缺省(Default) Winlogon和Screen saver Winlogon 桌面是当你按下Ctrl+Alt+Delete组合键时显示在你面前的对话框 缺省(Default)桌面是浏览器(Explorer)或者是由交互式用户启动的所有进程 它更应当被理解成交互式的应用程序桌面 最终的桌面是Screen saver 它显示你的屏幕saver 你可能已经注意到可以在不同的桌面之间切换 当一个用户按下Ctrl+Alt+Delete组合键时 操作系统可以从缺省状态切换到Winlogon桌面 当你在登录对话框中选择取消 系统将再切换回缺省桌面 有人问我当切换进行的时候 是否其它桌面上的东西都被破坏掉了 答案是 不 虽然你看不到其它桌面 但它们仍然在那里

    系统中所有的进程都与window station 和桌面相联系 当一个用户第一次登录时 交互式window station WinSta 和缺省桌面都与这个用户的Shell进程相关联 这样用户就能看到shell了 如果不是这样 用户是什么也看不到的 而且在这之后 由shell启动的所有进程也会和WinSta 及缺省桌面相关联

    你还可以通过STARTUPINFO 数据结构的lpDesktop 成员指定你的进程同哪个window station 和桌面相关联 这个数据结构传递给CreateProcess 和 CreateProcessAsUser两个函数 你可以将lpDesktop 初始化为NULL 意思是让CreateProcess函数使用和调用进程相同的window station 和桌面 你可以将你自己的window station 和桌面组合定义成 WinSta Default 或者就定义成空字符串 这个参数会让操作系统为启动进程创建一个新的不可见的window station 及桌面 与这两个新对象关联的安全性授予每个组对它们的完全访问权限

    typedef struct _STARTUPINFO { // si

    DWORD cb;

    LPTSTR lpReserved;

    LPTSTR lpDesktop;

    LPTSTR lpTitle;

    DWORD dwX;

    DWORD dwY;

    DWORD dwXSize;

    DWORD dwYSize;

    DWORD dwXCountChars;

    DWORD dwYCountChars;

    DWORD dwFillAttribute;

    DWORD dwFlags;

    WORD wShowWindow;

    WORD cbReserved ;

    LPBYTE lpReserved ;

    HANDLE hStdInput;

    HANDLE hStdOutput;

    HANDLE hStdError;

    } STARTUPINFO *LPSTARTUPINFO;

    Window stations和桌面是具有安全性的对象 与window station 和桌面将关联的进程必须由对这些对象的合适的访问权限 如果进程没有访问权 你会看到这两个消息之一 User dll initialization failure(User dll初始化失败) 或 Kernel dll initialization failure (Kernel dll初始化失败) 由进程返回的退出码为 或 ERROR_NO_WAIT_CHILDREN 那么我所指的合适的访问权是什么意思呢?假如你有一个文件这样的对象 你可以为这个文件创建一个DACL以使用户具有对这个文件的读访问权 Window station 和 桌面是以相同的方式工作的

    对于一个桌面对象的一个访问权限叫做DESKTOP_CREATEWINDOW 如果用户没有被授予这个访问权限 任何由这个用户启动的进程都不能创建窗口 不幸的是 象CreateWindow 这样的USER APIs 在发生同CreateFile 或 CreateMutex API类似的安全问题时 不会返回 Access Denied(访问被拒绝) 消息 User dll 中的Windows 应用程序将会被终止 导致DLL初始化错误的消息 Kernel dll 初始化过程是在创建一个控制板窗口时发生的 一个例程在没有对window station和桌面的合适的访问权限的时候启动cmd exe 然而不幸的是 CreateProcess 没有任何机制来检查这个错误 当用户不具有对window station和桌面的合适访问权限时 它并不返回一个错误信息 CreateProcess将会启动这个应用程序 然后这个应用程序本身在DLL失败后终止

    编程人员还可以有一种方法越过 User dll initialization failure(User dll初始化失败 消息 系统有一个堆用来为window station分配内存 内存是有限的 缺省设置允许创建七个或八个window station对象 如果你用光了所有的内存 你就会看到这个消息 不过 值得庆幸的是 有一个注册表关键字可以用来增加这个设置 (参见Knowledge Base article Q )

    如果你没在开发服务而只是普通的应用程序 Window stations 和桌面就不是真正的问题 你的应用程序只同交互式桌面WinSta Default 相关联 如果你是在开发一个服务 那么它可能就会同下面的window station 及桌面组合关联

    WinSta Default

    Service x e $Default

    Service Account s Logon SIDDefault

    WinSta Default 同运行在LocalSystem帐户的 并且与桌面交互的服务关联 (在ServiceType必须指明SERVICE_INTERACTIVE_PROCESS标志) 如果服务不同桌面交互 那么它是与Service x e $Default相关联的 这是个不可见的window station 你一定很疑惑这乱七八糟的 x e $ 是什么 它是服务的登录SID 登录SID是独一无二的 它指的是用户所属的组 系统中的所有用户都会有一个登录SID

    有一个有趣的现象是尽管你为同一个服务帐户配置了两个服务 但由于它们的登录SID是不同的 所以每个都将有一个独一无二的window station 及桌面 用户的SID是相同的但是由于它们运行在不同的服务帐户登录段中所以它们每一个都有独一无二的登录段 对于交互式服务和非交互式的LocalSystem 服务 情况就不同了 因为它们既同WinSta 关联又同Service x e $关联

    为什么知道哪个window station和桌面与服务相关联是如此的重要呢?第一 交互性 如果你的服务没有和WinSta Default相关联 它就不能以缺省状态把任何USER对象展示给用户 这意味着你不能显示一个窗口或者是获得用户的输入 你可以为一个非交互式的服务显示一个MessageBox 关键字标志的类型是MB_SERVICE_NOTIFICATION 和 MB_DEFAULT_DESKTOP 你不能用它来发送消息 这个动作与你的进程相关联的桌面有关系 如果你不为进程重新分配正确的window station 及桌面 你就始终不能做这件事

    另外两种基于服务帐号的登录SID的window station 和桌面组合是不可见的 正如我刚刚提到的 与一个不可见window station相关联的桌面永远不能显示或接收来自交互式用户的输入信息(除非MessageBox使用两个特殊的标志) 你仍然可以创建USER对象 但是用户永远不会看到它们 很多服务的开发者们经常要犯的一个错误是显示一个对话框提示用户输入信息 当服务被测试时 开发者会注意到服务被挂起了 这就是因为服务同一个不可见的window station相关联 操作系统是成功地创建了对话框 问题是用户看不到它

    所以你应当怎样才能使你的服务能显示并获得从用户那来的信息呢?首先为用户写一个启动的客户应用程序 客户应用程序将实现显示及从用户处获得信息的功能 然后使用进程间通信机制把信息发回给服务 这样做的最大好处是你不必再为window stations及桌面操心 一个缺点就是需要用户做一些事情来启动你的客户应用程序

    另外一个方法要求你为LocalSystem帐户配置你的服务 要将服务类型说明为SERVICE_INTERACTIVE_PROCESS 这将把你的服务与正确的window station及桌面相关联 唯一的问题就是服务将不能通过NTLM进行任何的网络访问 有两种办法可以解决 第一种办法是 NULL 段访问可以在服务器端通过注册表调整 如果服务开发者有对服务器的合适的安全访问权限 注册表的关键字将会改变 允许对LocalSystem 进程的访问

    另一个解决的办法是扮演一个已经访问过NTLM 安全网络资源的用户 一个问题是服务必须知道用户的口令以通过LogonUser API生成用户的标志 为什么用户不能为服务帐户配置一个服务 那样就只须重新为服务分配交互式的window station 及桌面就可以了?答案只有一个 就是安全 对交互式window station 及桌面具有完全访问权限的唯一用户是LocalSystem帐户和交互式用户(如果有的话) 我特别指出完全访问权限的原因是属于本地管理员组的用户对交互式window station 及桌面拥有部分访问权限 有一部分USER API 调用可能会由于安全的原因无法运行

    最好的赌注是冒点风险使用本地管理员帐户 交互式用户的访问权限是基于组的登录SID 而不是单个用户的SID 这意味着你可能有一个与交互式用户相同帐户的服务但是组的登录SID是不同的 允许一个为服务帐户配置的服务具有对交互式window station及桌面访问的唯一条件是为交互式用户更改安全性以允许服务访问

    现在你有了一个为交互式window station 及桌面配置的服务 你还会碰上什么样的问题呢?第一个要考虑的问题就是当用户退出时将会发生什么 如果服务是交互式的 那么是否缺省桌面被破坏了呢?不 缺省桌面仍然存在 唯一的区别是Winlogon 桌面现在是活动桌面 当第二个用户登录系统时 系统将会切换回缺省桌面 用户显示的任何事物都可以被交互式用户看到

    交互式服务和由交互式用户启动的进程之间的区别是 当没有用户登录时 服务仍然可以运行 这就导致了一些有趣的问题 例如 基于安全的原因 当交互式用户退出系统后 操作系统竟全局原子表清为 如果一个交互式服务依赖于存储在全局原子表中的信息 那么当这个交互式用户退出后 信息全部消失 另一个例子是自启动交互式服务 第一个用户登录之前缺省桌面还没有创建 这意味着在用户登录之前试图显示信息的交互式服务一定会出问题

    我想讨论的最后一件事是交互式服务是暴露给交互式用户的 这个交互式用户可以通过任务管理器杀死服务 如果你有一个运行在LocalSystem 帐户上的服务 则交互式用户将不具备必须的安全性来杀死你的进程 假设你进入任务管理器列出所有的进程 如果你点击了End Process(终止进程)按纽来终止运行在LocalSystem帐户上的进程时 你将会得到意料之中的 Access is denied(访问被拒绝) 消息框 但是如果这个服务有一个显示在外层的窗口 你可以在任务管理器中列出所有应用程序 当你点击End Task(终止任务)按纽 你就可以通过这个暴露的窗口杀死这个服务 避免这种情况发生的一种方法就是让你的窗口不要显示在应用程序标签中 创建一个隐藏的窗口 然后将可见的窗口作为这个隐藏窗口的儿子窗口

    记住 最后的手段才是将你的服务交互化 最好的选择是创建一个交互式的客户应用程序

    注册表 蜂箱

    蜂箱 是操作系统用来存储用户注册信息的 在注册表中存储的注册信息包括桌面 应用程序 打印和网络设置 系统将用户的注册信息备份在一个象蜂箱一样的文件中 Windows NT的每个用户都分配一个蜂箱 这个蜂箱能放在本地的或远程的服务器上 当一个用户登录到Windows NT机器上时 系统把用户的蜂箱装载到注册表中 在HKEY_USERS 关键字下 注册表关键字名称代表蜂箱时基于用户的SID的 如果你通过regedt exe 或regedit exe检测到HKEY_USERS下的注册表关键字 你将看到至少两个子关键字 DEFAULT 和以S 开头的长字符串 这是用户的SID 如果你还不清楚HKEY_CURRENT_USER代表的是什么意思 那么我告诉你 基本上说 它是一个HKEY_USERSuser s SID的映射 通过regedt exe 或regedit exe可以得到验证

    HKEY_USERuser s SID和HKEY_CURRENT_USER的子关键字实际上是一模一样的 当一个应用程序是关于HKEY_CURRENT_USER的 系统将会基于用户的安全上下文把用户映射到合适的蜂箱中 包括用户的SID 它用来在HKEY_USERS下查找正确的蜂箱 如果由于某种原因用户的蜂箱没有被装载 系统将会把用户的蜂箱映射到 DEFAULT

    现在 看起来一切都好了 但是还有很多问题 首先 服务要依赖用户的注册信息 绝大多数应用程序在HKEY_CURRENT_USER中存储用户的注册信息 例如 用户的打印信息存储在用户的蜂箱中 如果服务配置配置给不同的帐户 那这个服务将没有任何打印信息 不是所有的服务都使用 DEFAULT 蜂箱 如果一个服务配置给一个具有蜂箱的服务帐户 那么 Service Control Manager将会装载哟功能户的蜂箱 Windows NT 中已经增添了这一功能

    Service Control Manager不能做的一件事是为服务正确地准备用户的环境 环境变量存储早用户的蜂箱中 服务继承的唯一一个环境变量来自于Service Control Manager 它的环境基于系统环境变量 如果你改变系统环境变量 那么只有在重新启动机器后 所做的改变才能反映到服务中 当系统环境变量改变时 Service Control Manager不象其它系统进程一样处理WM_SETTINGCHANGE 消息

    让我们返回到LocalSystem 帐户的情况中 怎样得到一个已合理配置的 蜂箱 呢?有这样几种选择 如果你有创建注册关键字的应用程序的控制权 就可以创建两次关键字 一次是为HKEY_CURRENT_USER 另一次是为HKEY_USERSDefault 另一种方法是判断应用程序创建了那些关键字 进行复制 这种方法可以通过手工或编程实现

    如果你不想和注册表搅在一起 你可以用编程的方式装载你自己的 蜂箱 如果你知道一个已经正确初始化的 蜂箱 的用户名 服务将基于用户名决定蜂箱的位置 通过RegLoadKey API装载 蜂箱 然后扮演这个用户

    问题是你需要一个进程标志来扮演这个用户 一个进程标志可以通过LogonUser API调用生成 当然 你需要一个用户口令 如果由于某种原因你所感兴趣的用户有一些进程运行在系统中 服务将会列举这些进程知道运行在目标用户的安全上下文中的进程 一旦一个进程被定位 这个服务将通过调用OpenProcessToken获得用户标志 在这时 服务将用获得标志扮演这个用户

    结尾

    讨论了在Windows NT服务和由交互式用户启动的应用程序之间的区别 也讨论了服务开发人员最关心的三个领域 Windows NT 安全 window stations 和桌面以及注册表 蜂箱 这些信息将使Windows NT服务的开发变得容易一些并能应用于服务中使用的其它技术中

WindowStations窗口站

相关文章:

你感兴趣的文章:

标签云: