为什么某些Win32技术在Windows NT服务中行为不当?

弗兰克·金

服务是具有特殊要求的后台Win32进程。它需要一个独特的入口点和一个回调函数。回调函数被称为服务控制处理程序。新的入口点通常称为ServiceMain,但您可以任意命名。

本文假定您熟悉Win32

Frank Kim是微软专门从事内核/基础技术的支持工程师。他喜欢花空闲时间玩股票市场。他可以通过franki@microsoft.com联系到他

四年前, 我被介绍给Windows NT® 服务。像许多其他程序员一样,我发现它们都很有趣,也很奇怪。不像的Windows ® 为基础的应用程序,服务通常没有任何直接的用户交互。开发人员经常告诉我,某些代码可以在基于Windows的应用程序中运行,但不能从他们的服务中运行。主要问题是对“环境”差异缺乏了解。服务的“环境”或Outlook与用户发起的基于Windows或控制台应用程序的“环境”有很大的不同。我所说的“环境”指的不仅是过程的环境变量,您还必须包括窗口站点和桌面,注册表配置单元, 
许多的Win32 
® 技术,如MFC,ODBC和MAPI,可以表现不同的服务,由于在服务的差异“的环境。” 让我们来探讨一下为什么这些不同的技术在服务中显得不合时宜。 
在我解释“环境”差异之前,让我们仔细看看服务是什么。服务是具有特殊要求的后台Win32进程。它需要一个独特的入口点和一个回调函数。回调函数被称为服务控制处理程序。新的入口点通常称为ServiceMain,但是您可以通过SERVICE_TABLE_ENTRY结构中的LPSERVICE_MAIN_FUNCTION成员命名它。

 typedef struct _SERVICE_TABLE_ENTRY {// ste
      LPTSTR lpServiceName;
      LPSERVICE_MAIN_FUNCTION lpServiceProc;
  } SERVICE_TABLE_ENTRY,
 *LPSERVICE_TABLE_ENTRY; 
The service is manipulated (installed, started, stopped, and configured) through a subset of the Win32 API. You can pass a server name to manipulate services remotely over a network. A service can be configured to run in either a specific user account or the LocalSystem account (see Figure 1). Either account is referred to as the service account. A service can be configured to start running when the system is first booted or at the discretion of a user with the proper security.
图1配置服务
Figure 1 Configuring a Service


Since a service is a background process, it can continue to run when there are no interactively logged-on users. Services are usually not directly manipulated by the interactive user. (I'll go over some of the exceptions later.) They are intended to be background processes processing data or offering services on a Windows NT server locked in a closet somewhere. This means that a service should not display dialog boxes to prompt users for input. It should not display information on the system tray. It shouldn't send messages to a user-launched application. 
Another feature of services is the ability for many services to share the same process. If you look at the configuration information for all the services on your system, you'll notice that many of the services originate from the same process, services.exe:

Alerter 
Computer Browser 
DHCP Client 
Eventlog 
Server 
Workstation 
TCP/IP NetBios Helper 
Messenger 
NT LM Security Support Provider 
Plug And Play
For further information, please refer to the Platform SDK documentation and "Design a Windows NT service to Exploit Special Operating System Facilities," by Jeffrey Richter (MSJ, October 1997). 
I'd like to emphasize that a process launched from a service using the CreateProcess API is not a service itself and should not be referred to as a service. It is definitely a background process running in the same "environment" as the service—but that's it. The documentation of the Windows NT Resource Kit misleads users into thinking that using the Srvany utility will magically turn their application into a service. This is not true. The utility just launches your application via a CreateProcess API call. This is very similar to the AT command available on Windows NT. But even though your app launched via CreateProcess is technically not a service, it is still running in the same "environment," so many of my comments will also apply to it. 
In this article, I will describe the environmental differences between a service and a process launched by the interactive user. I define the interactive user as somebody who logs onto a Windows NT machine via the Ctrl+Alt+ Delete secured key combination dialog box (see Figure 2).
图2登录对话框
Figure 2 Logon Dialog


I wanted to make this very clear since you can technically have other processes running in different security contexts that can be considered interactive. In addition to the differences, I will point out some items you should consider when designing a service and the solutions to the problems caused by the environmental differences.

Windows NT Security

Before I begin discussing the environmental security differences, let me briefly explain Windows NT security. One of the requirements of C2 level security is the ability to control access to a secured resource. This is done by granting or denying access to one or more users or groups to a particular securable object. Windows NT accomplishes this through two basic components, the access token and the security descriptor. When a user first logs on, a username and password is given to the system. If this information is correct, the system assigns an access token to all processes associated with the user. The access token contains information on the user's ID (known as the security identifier or SID), the user's group memberships, and the list of privileges granted to the user. The system uses the access token as an identification badge. 
The security descriptor, on the other hand, is associated with a securable object. It indicates who owns the object and defines what kind of access control a user or group may have to the object. This is known as the discretionary access control list (DACL). The DACL contains a list of access-control entries (ACE). Each ACE describes a user or group based on its SID (also known as trustees) and what kind of access can be granted or denied to the securable object. 
The system uses these two components to determine whether a handle to access the securable object should be returned to the user. It does so by walking the list of ACEs in the security descriptor of the object. The system will check every ACE to see if the user is granted the specified access to the object. If none of the ACEs grant the user the access requested, the system does not return a handle for the object. If the DACL contains an ACE denying access to the user, the search immediately ends and no handle is returned to the user. If the DACL does not contain any ACEs, all users are denied access to the object. If the security descriptor does not contain a DACL, all users are granted access to the object (see Figure 3). This is better known as a NULL DACL.
图3安全组件
Figure 3  Security Components


当然,Windows NT安全性是开发人员遇到的第一个主要的环境差异。许多Win32 API包含SECURITY_ ATTRIBUTES参数,例如CreateDesktop,CreateDirectory,CreateDirectoryEx,CreateEvent,CreateFile,CreateFileMapping和CreateMailSlot。


 typedef struct _SECURITY_ATTRIBUTES {// sa
      DWORD nLength;
      LPVOID lpSecurityDescriptor;
      BOOL bInheritHandle;
 } SECURITY_ATTRIBUTES; 
对于大多数开发人员来说,传递NULL参数就足够了。只要操作安全对象的所有进程都在相同的安全上下文中运行,就没有问题。 
在Windows NT服务中,此技术可能会或可能不会工作。服务帐户允许服务以不同的用户身份运行。如果您曾经查看过安装在Windows NT计算机上的服务,则知道其中大部分都是为系统帐户(也称为LocalSystem帐户)配置的。系统帐户是仅在您的计算机上本地识别的特殊帐户。这意味着此帐户不能用于访问依赖NT LAN Manager(NTLM)身份验证的网络资源。这些资源包括文件共享,命名管道,注册表以及对远程计算机事件日志或服务控制管理器的访问。为什么这不可能? 
NTLM身份验证基于包含用户名和密码的加密凭据。如果操作系统遇到没有任何凭据的用户,则用户被认为具有NULL凭证。当系统尝试访问基于NULL凭证的安全网络资源时,这被称为NULL会话。只有当远程机器允许NULL会话访问时才允许访问。这可以通过注册表来配置。(有关详细信息,请参阅知识库文章Q122702。)唯一的其他解决方法是使用有效凭据模拟用户或使用可访问安全网络资源的服务帐户。Winsock和NetBIOS不是安全的NTLM资源,所以它们不会遇到上述安全限制。 
这似乎是一个主要问题。那么为什么配置这么多的服务来使用本地系统帐户呢?本地系统帐户被授予在操作系统上可用的所有权限。权限允许用户操纵各种系统资源,例如更改系统时间,交互式登录和从远程系统关闭(参见图4)。
图4用户管理器授予权限
图4用户管理器授予权限


权限是相对于本地机器授予的。这意味着域帐户必须被授予本地机器本身的权限,而不是域控制器上的权限。例如,如果属于Redmond域的用户Fester登录到计算机A和B,则每台计算机都需要向Redmond \ Fester授予“本地登录”权限,以便Fester以交互方式登录到两台计算机。特权的一个令人困惑的方面是它们有一个关联的属性,指示它们是启用还是禁用。某些Win32 API需要用户拥有该权限并且有权限才能成功使用API​​。您可以考虑启用特权作为安全开关。例如,LogonUser API需要“充当操作系统的一部分”。程序员不需要启用这个特权。这个API支持你。 
另一方面,需要启用“备份文件和目录”权限才能获得用户无权访问的文件句柄。(有关更多信息,请参阅知识库文章Q106383)。API会根据所需的权限以及是否需要启用权限而有所不同; 请参阅Platform SDK文档以获取更多信息。 
那么属于本地管理员组的用户呢?用户帐户是否可以访问安全的网络资源并被授予所有可用权限?事实证明,管理员组没有被授予所有权限。您可以通过用户管理器进行验证。(请确保选中显示高级用户权限。)这使LocalSystem帐户非常有吸引力。您不必担心服务帐户是否具有必要的权限。你可以认为它具有所有这些。 
如果您决定使用非本地系统服务帐户,则可以通过两种方法向用户授予权限。试图授予其他用户权限的用户必须属于本地管理员组或LocalSystem帐户。第一种方法是通过用户管理器应用程序手动授予权限。编程方法要求您使用本地安全机构(LSA)API编写大量代码。(知识库文章Q132958有此详细信息。) 
许多程序员认为,如果两个帐户具有相同的权限,他们具有相同的安全访问权限。这是不正确的。尽管一些对象将仅允许用户基于用户在其令牌中拥有的权限进行访问,但其他对象将允许基于该用户的帐户名称和/或该用户令牌中的组进行访问,而不管其权限如何。请注意,与用户关联的权限不会动态更改。与访问令牌关联的权限仅在创建令牌时生成。这意味着如果权限正在改变,用户需要注销并重新接收新授予的权限。 
为您的服务考虑使用LocalSystem帐户的另一个原因是,您将拥有对Windows NT系统中通过默认DACL机制创建的所有对象的完全访问控制。当没有定义DACL时,操作系统使用此机制将DACL应用于安全对象。作为一名程序员,您始终使用默认的DACL机制。许多Win32 API包含一个SECURITY_ATTRIBUTES结构。这个结构包含安全对象的安全和继承信息。在大多数情况下,程序将忽略此参数,并将NULL传递给它。通过指定NULL,您正在指示操作系统为安全对象创建一个DACL。系统根据位于用户访问令牌中的默认DACL信息为对象创建DACL。图5列出了Windows NT机器上不同用户的默认DACL。 
操作系统使用GENERIC访问来映射与安全对象有关的特定访问权限。例如,文件的GENERIC_READ实际上映射到FILE_GENERIC_READ; 而对于服务对象,GENERIC_READ被映射为以下访问权限:STANDARD_RIGHTS_READ,SERVICE_QUERY_CONFIG,SERVICE_QUERY_STATUS,SERVICE_ENUMERATE_DEPENDENT,SERVICE_INTERROGATE。尽管没有用于确定与安全对象的GENERIC映射有关的特定访问权限的API,但您可以通过MapGenericMask API创建自己的通用掩码。如果您想知道是否可以更改用户进程标记的默认DACL,那么答案是肯定的。您可以自定义默认的DACL,以便在为SECURITY_ ATTRIBUTES参数传递NULL时, 
为什么我花了很多时间向你解释这个?服务开发人员遇到的第一个问题是访问被拒绝错误。假设您想在您的服务和由交互式用户启动的客户端应用程序之间共享一些内存。正如您一直为SECURITY_ATTRIBUTES参数所做的那样,您将为CreateFileMapping传递一个NULL值。您编写该服务,然后编写客户端应用程序,该应用程序使用OpenFileMapping获取具有对已命名内存映射文件的完全访问权限的句柄。第一次测试应用程序时,客户端应用程序在调用OpenFileMapping时失败,出现错误代码5(“拒绝访问”)。它失败的原因是因为用于为内存映射文件创建DACL的默认DACL机制。正如我所解释的,内存映射文件的DACL使GENERIC_ALL可以访问本地系统帐户,但只能访问本地管理员组的GENERIC_ READ和GENERIC_EXECUTE。如果启动客户端应用程序的用户不属于本地管理员组,则客户端进程将无法完全访问内存映射文件,这是正常的,除非客户端应用程序需要写入内存映射文件。上面的问题仅在开始在不同安全上下文中运行的进程之间共享命名对象时才会发生。当所有进程都在相同的安全上下文中运行时,这不是一个问题,但它可以与服务一起使用,因为它们可以配置为在不同的安全上下文中运行。如果启动客户端应用程序的用户不属于本地管理员组,则客户端进程将无法完全访问内存映射文件,这是正常的,除非客户端应用程序需要写入内存映射文件。上面的问题仅在开始在不同安全上下文中运行的进程之间共享命名对象时才会发生。当所有进程都在相同的安全上下文中运行时,这不是一个问题,但它可以与服务一起使用,因为它们可以配置为在不同的安全上下文中运行。如果启动客户端应用程序的用户不属于本地管理员组,则客户端进程将无法完全访问内存映射文件,这是正常的,除非客户端应用程序需要写入内存映射文件。上面的问题仅在开始在不同安全上下文中运行的进程之间共享命名对象时才会发生。当所有进程都在相同的安全上下文中运行时,这不是一个问题,但它可以与服务一起使用,因为它们可以配置为在不同的安全上下文中运行。上面的问题仅在开始在不同安全上下文中运行的进程之间共享命名对象时才会发生。当所有进程都在相同的安全上下文中运行时,这不是一个问题,但它可以与服务一起使用,因为它们可以配置为在不同的安全上下文中运行。上面的问题仅在开始在不同安全上下文中运行的进程之间共享命名对象时才会发生。当所有进程都在相同的安全上下文中运行时,这不是一个问题,但它可以与服务一起使用,因为它们可以配置为在不同的安全上下文中运行。 
有几种方法可以解决这个问题。最简单的方法是将NULL DACL应用于对象,如果保护对象不是问题。正如我前面所述,NULL DACL使每个用户和组都可以完全访问对象。(知识库文章Q106387将在这里帮助你。)如果对对象的访问控制是一个问题,最好的方法是创建一个授予受托者特定访问控制的ACE。这个新的ACE将被应用到DACL中,以保证对象的安全。最后一种方法是修改进程访问令牌的默认DACL。使用此方法,仍然可以为安全属性参数传递NULL,并让系统为您创建DACL。唯一的区别是您正在控制将为您的对象创建的DACL。这意味着你赢了'

窗口站和桌面

窗口工作站和桌面可能是Windows NT服务中最困难的一个方面。大多数程序员不会直接遇到这些对象,尽管用户总是碰到它们。与其他Windows NT对象(如事件,互斥和信号量)一样,窗口工作站和桌面对象也是可以保证的。窗口工作站对象包含一个剪贴板,一组全局原子和零个或多个桌面对象。窗口站可以是可见的或不可见的。一个可见的窗口站接收用户输入,可能来自鼠标或键盘。显示设备也与之相关联,以便信息可以显示给交互式用户(见图6)。
图6 Window Station和台式机
图6 Window Station和台式机


在Windows NT 4.0上,只有一个窗口站点可见,WinSta0。可视窗口站也被定义为交互式。一个不可见的窗口站是非交互的。一个不可见的窗口站没有收到任何用户输入,也没有显示设备与之关联。 
如前所述,桌面包含在窗口工作站对象中,就像目录包含文件一样。桌面对象包含一个逻辑显示表面,并包含窗口,菜单和挂钩。只有属于可视窗口站点的桌面才能被看到并接收用户输入,并且一次只能看到一个桌面并接收输入。这个桌面被称为活动桌面。 
作为交互式用户,您同时遇到三个不同的桌面:默认,Winlogon和屏幕保护程序。Winlogon桌面包含按Ctrl + Alt + Delete组合键时出现的登录对话框。“默认”桌面包含资源管理器或您指定的外壳以及交互式用户启动的所有进程。这被称为交互式或应用程序桌面。最后的桌面是屏幕保护程序,它显示您的屏幕保护程序。您可能已经注意到操作系统可以在桌面之间切换。当用户按下Ctrl + Alt + Delete时,操作系统从默认切换到Winlogon桌面。当您在登录对话框中点击取消时,系统切换回默认桌面。有时我会问,在切换时,其他桌面上的所有内容是否都被破坏了?答案是不。你没有看到其他的桌面,但仍然在那里。 
系统中的所有进程都与窗口站和桌面相关联。当用户首次登录时,交互式窗口工作站WinSta0和默认桌面与用户的shell进程相关联。这允许用户看到外壳。如果不是这样,用户什么也看不到。在此之后,shell启动的所有进程也将与WinSta0和Default相关联(参见图7)。

图7交互式流程与服务
图7交互式流程与服务


你也可以通过STARTUPINFO结构的lpDesktop成员来指定你希望你的进程与哪个窗口工作站和桌面相关联(见图8)。这个结构被传递给CreateProcess和CreateProcessAsUser。您可以将lpDesktop初始化为NULL,这将指示CreateProcess使用调用进程使用的同一个窗口工作站和桌面。您可以指定您自己的窗口工作站和桌面组合,例如“WinSta0 \ Default”,或者您可以传入一个空字符串。这指示操作系统为启动的进程创建一个新的不可见窗口站和桌面。与新对象关联的安全性授予Everyone组对这两个对象的完全访问权限。 
窗口站和桌面是安全的对象。与窗口站和桌面相关的进程必须有适当的访问这些对象。如果进程没有访问权限,您将看到两条消息之一“User32.dll初始化失败”或“Kernel32.dll初始化失败”。进程返回的退出代码将为128或ERROR_NO_WAIT_CHILDREN。我的意思是适当的访问?假设你有一个对象,如文件。您可以为文件创建一个DACL,允许用户读取文件的读取权限。窗口站和桌面以相同的方式工作。 
其中一个桌面对象的访问权限是DESKTOP_CREATEWINDOW。如果用户没有被授予访问权限,则该用户启动的任何进程将不能创建窗口。不幸的是,当CreateFile或CreateMutex这样的安全问题出现时,CreateWindow等USER API不会返回“Access Denied”消息。Windows应用程序将死于User32.dll,导致DLL初始化错误消息。创建控制台窗口时会发生Kernel32.dll初始化。其中一个例子是当它没有正确访问窗口站和桌面对象时启动cmd.exe。不幸的是,CreateProcess没有任何机制来检查这个错误。如果用户没有对窗口工作站和桌面进行适当的访问控制,则不会返回错误。 
程序员遇到“User32.dll初始化失败”消息还有另外一种方法。系统有一个堆分配窗口站对象的内存。这个记忆是有限的。默认设置允许创建七个或八个窗口工作站对象。如果你用完内存,你会得到这个消息。幸运的是,有一个注册表项来增加这个设置。(有关更多信息,请参阅知识库文章Q142676。) 
如果您不开发服务,则窗口工作站和桌面并不是真正的问题。您的应用程序将只与交互式桌面WinSta0 \ Default相关联。如果您正在开发服务,则可以将其与以下窗口站和桌面组合之一相关联:

  • WinSta0 \ DEFAULT
  • 服务0x0-3e7 $ \ DEFAULT
  • 服务帐户的登录SID \默认
WinSta0 \ Default与在LocalSystem帐户中运行的服务关联,并与桌面进行交互。(ServiceType必须包含SERVICE_INTERACTIVE_PROCESS标志。)如果服务不与桌面进行交互,它将与Service-0x0-3e7 $ \ Default关联。这是一个不可见的窗口站。如果您想知道0x0-3e7 $是什么,它就是服务的登录SID。登录SID对于此特定的登录会话是唯一的。它指的是用户所属的组。系统上的所有用户都将具有登录SID。上面的最终选择是基于服务帐户登录SID的服务的不可见窗口站。 
一个有趣的事情要注意的是,即使您可能有两个服务配置为相同的服务帐户,每个将有一个独特的窗口站和桌面,因为他们的登录SID是不同的。用户SID是相同的,但每个都有一个唯一的登录会话,因为每个都在单独的服务帐户登录会话中运行。交互式和非交互式LocalSystem服务不是这种情况,因为它们与WinSta0或Service-0x0-3e7 $相关联。 
现在,为什么知道哪个窗口站点和桌面与服务相关联是非常重要的?第一个问题是交互性。如果您的服务没有与WinSta0 \ Default相关联,则默认情况下,它将无法向用户显示任何USER对象。这意味着您不能显示窗口或获取用户输入。您可以显示一个非交互式服务的MessageBox。键标志类型是MB_SERVICE_NOTIFICATION和MB_DEFAULT_DESKTOP。(有关更多信息,请参阅Platform SDK文档。)另外,您不能安装窗口挂钩,甚至不能发送消息。这两个操作与您的进程所关联的桌面相关。如果不使用正确的窗口工作站和桌面重新分配进程,则无法完成这两项任务。这意味着窗口挂钩和消息不能跨桌面。 
另外两个基于服务帐户的登录SID的窗口工作站和桌面组合是不可见的。正如我刚刚提到的,与不可见的窗口站相关联的进程永远不能显示或接收来自交互式用户的输入(使用两个特殊标志的MessageBox除外)。您仍然可以创建USER对象 - 但用户永远不会看到它们。很多服务开发者犯的一个错误就是显示一个对话框,提示用户输入一些信息。当服务被测试时,开发者会注意到服务正在挂起。挂起是由于服务与不可见的窗口站相关联的事实造成的。操作系统已成功创建对话框。问题是用户看不到它。 
那么如何让你的服务显示和获取用户的信息呢?编写一个客户端应用程序供用户启动。客户端应用程序将显示并获取来自用户的信息,然后使用某种进程间通信将信息发送回服务。关于这一点的好处是您不必担心窗口站点和桌面。缺点是这需要用户做一些事情来启动你的客户端应用程序。 
另一种方法要求您为LocalSystem帐户配置服务,将服务类型指定为SERVICE_INTERACTIVE_PROCESS。这将使您的服务与正确的窗口站和桌面相关联。唯一的问题是,该服务将不会有任何通过NTLM安全的网络访问。这可以通过两种方式来解决。NULL会话访问可以通过注册表在服务器端进行管理。如果服务开发人员对服务器具有正确的安全访问权限,则可以更改注册表项以允许访问LocalSystem进程。 
另一种方法是模拟有权访问NTLM安全网络资源的用户。唯一的问题是服务必须知道用户的密码才能通过LogonUser API生成用户的令牌。为什么用户不能为一个服务帐户配置服务,然后重新分配服务与交互式窗口站和桌面?问题是安全。唯一有权访问交互式窗口站和桌面的用户是LocalSystem帐户和交互式用户(如果有的话)。我提到完全访问的原因是因为属于本地管理员组的用户可以部分访问交互式窗口工作站和桌面。某些USER API调用可能会或可能不会基于安全性工作。 
最好的选择是使用本地管理员帐户完全访问,风险自负。交互式用户允许的访问权限是基于组登录SID而不是个人用户的SID。这意味着您可能为交互式用户配置了与该帐户相同的服务,但组登录SID将不同。允许为服务帐户配置的服务访问交互式窗口工作站和桌面的唯一方法是让交互式用户修改安全性以允许服务访问或让另一个进程在LocalSystem帐户中运行。(有关更多信息,请参阅知识库文章Q165194。) 
现在您已经为交互式窗口工作站和桌面配置了一项服务,那么您可以运行哪些陷阱?首先考虑的是用户注销时发生的情况。如果一个服务是交互式的,这是否意味着默认的桌面被破坏?不。默认桌面仍然在那里。唯一的区别是Winlogon桌面现在是活动桌面。当下一个用户登录到本机时,系统将切换回默认桌面。用户显示的任何内容都将被交互式用户看到。 
交互式服务与交互式用户启动的进程之间的一个区别当然是,当没有人登录时服务可以运行。这导致一些有趣的问题。例如,出于安全原因,当交互式用户注销时,操作系统清除全局原子表。如果交互式服务依赖于存储在全局原子表中的信息,则当交互式用户注销时,信息将消失。另一个例子是一个自动启动的交互式服务。默认桌面直到第一个交互式用户登录才会创建。这意味着在用户登录之前尝试显示信息的交互式服务会遇到问题。 
我想讨论的最后一件事情是交互式服务暴露给交互式用户,如果服务具有顶层窗口,交互式用户可以通过任务管理器终止服务。如果在LocalSystem帐户中运行服务,则交互式用户没有必要的安全性来终止进程。说你进入任务管理器,并选择进程列表。如果按LocalSystem帐户中运行的进程的“结束进程”按钮,则会按预期得到“访问被拒绝”消息框。但是,如果此服务具有顶层窗口,则可以在任务管理器中选择应用程序列表。如果您点击“结束任务”按钮,则可以通过此暴露的窗口来终止服务。避免这种情况的一种方法是防止您的窗口出现在应用程序选项卡中。创建一个隐藏的窗口; 
请记住,只有作为最后的手段才能使您的服务互动。最好的选择是创建一个交互式的客户端应用程序。

注册表蜂箱

操作系统使用配置单元来存储用户特定的注册表信息。存储在注册表中的信息包括桌面,应用程序,打印和网络设置。系统在称为配置单元的文件中备份用户特定的注册表信息。Windows NT上的每个用户都被分配一个配置单元。这个配置单元可以位于本地,也可以位于远程服务器(漫游蜂房)上。当用户登录到Windows NT计算机时,系统将用户的配置单元加载到注册表中。该配置单元在HKEY_USERS项下加载。表示配置单元的注册表项名称基于用户的SID。如果您通过regedt32.exe或regedit.exe检查HKEY_USERS下的注册表项,您将会看到至少两个子项:.DEFAULT和一个以S-1开始的长字符串 - 等等等等等等等等。这是用户的SID。如果你' 有没有想过HKEY_CURRENT_USER代表什么,它基本上是HKEY_USERS \用户的SID的映射。您可以使用regedt32.exe或regedit.exe来验证这一点(请参阅图9)。
图9 Regedt32.exe
图9 Regedt32.exe


HKEY_USER \用户的SID和HKEY_CURRENT_USER的子项将完全相同。当应用程序引用HKEY_CURRENT_USER时,系统会根据用户的安全上下文将用户映射到正确的配置单元。这将包含用户的SID,然后用于在HKEY_USERS下找到正确的配置单元。如果由于某种原因没有加载用户的配置单元,系统会将用户的配置单元映射到.DEFAULT。用户配置单元加载的唯一时间是服务帐户或交互式用户。如果服务器应用程序模拟客户端,然后将注册表引用到HKEY_CURRENT_USER,则用户的配置单元将映射到.​​DEFAULT。系统将尝试根据其SID来查找用户的配置单元。如果在HKEY_USERS下找不到该配置单元,系统会将用户的配置单元映射到.DEFAULT。 
这听起来不错,但会导致很多问题。首先,服务可能取决于用户特定的注册表信息。大多数应用程序在HKEY_CURRENT_USER中存储用户特定的信息。例如,用户的打印信息存储在用户的配置单元中。如果服务配置为不同的帐户,服务将不会有任何打印信息。并非所有服务都使用.DEFAULT配置单元。如果服务配置为拥有配置单元的服务帐户,则服务控制管理器将加载用户的配置单元。此功能已添加到Windows NT 4.0。 
服务控制管理器不会做的一件事是正确地为服务准备一个用户的环境。环境变量存储在用户的配置单元中。服务将继承的唯一环境变量来自服务控制管理器。其环境基于系统环境变量。如果对系统环境变量进行了更改,则在重新引导计算机之前,这些变量不会传播回您的服务。服务控制管理器不处理WM_SETTINGCHANGE消息,因为其他系统进程在系统环境变量更改时执行此操作。 
让我们回到LocalSystem帐户的情况。你怎么能得到一个正确配置的蜂巢?你有几个选择。如果您有控制创建注册表项的应用程序,则可以创建两次密钥。一个引用是HKEY_CURRENT_USER,另一个是HKEY_USERS \ Default。另一种方法是确定应用程序创建和再现它们的键。这可以手动或以编程方式完成。 
如果你不想混淆注册表,你可以通过编程方式自己加载配置单元。如果您知道具有正确初始化的配置单元的用户,该服务可以根据用户确定配置单元的位置。通过RegLoadKey API加载配置单元,然后模拟用户,以便对HKEY_CURRENT_USER的任何引用都映射到正确的配置单元。 
问题是你需要一个进程标记来模拟一个用户。进程标记可以通过LogonUser API调用生成。当然,你需要用户的密码。如果由于某种原因,您感兴趣的用户在系统上运行了一些进程,则该服务可以枚举进程,直到找到在目标用户的安全上下文中运行的进程。一旦找到一个进程,服务就可以通过调用OpenProcessToken来为用户获取一个令牌。在这一点上,服务可以用这个令牌来模拟用户。(有关加载用户配置单元的详细信息,请参阅知识库文章Q168877,HOWTO:以编程方式加载用户配置单元 。 
我想在模拟用户和使用他们的配置单元时提到的一个问题是HKEY_CURRENT_USER的映射。假设您为LocalSystem帐户配置了一项服务。该服务需要访问用户HOSUN的注册信息。HOSUN的密码是已知的,你加载用户的配置单元和模拟HOSUN。请记住,HKEY_CURRENT_USER是映射到HKEY_USERS \用户的SID。如果由于某种原因没有加载用户的配置单元,则系统使用HKEY_USERS \ .DEFAULT。现在,如果服务在模拟用户HOSUN之前引用了HKEY_CURRENT_USER,那么在HOSUN的安全上下文中对HKEY_CURRENT_USER的任何引用都将继续到HKEY_USERS \ .DEFAULT而不是HKEY_USERS \ HOSUN的SID。该系统不够聪明,重新映射HKEY_USERS到正确的安全上下文。要解决这个问题,您可以使用HKEY_CURRENT_ USER调用RegCloseKey。这将告诉系统在用HOSUN的安全上下文进行另一个引用时重新映射HKEY_CURRENT_USER。

包起来

我讨论了Windows NT服务与交互式用户启动的应用程序之间的一些“环境”差异。我讨论了服务开发人员关心的三个主要问题:Windows NT安全性,窗口站点和桌面以及注册表配置单元。这些信息应该使开发Windows NT服务的经验更容易,并且可以应用于服务中使用的其他技术来解释这些偶然的神秘行为。
https://www.microsoft.com/msj/0398/service2.aspx

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值