Wednesday, April 20, 2011

starting a UAC elevated process from a non-interactive service

I'm using a thrid party Windows service that handles some automation tasks by running scripts and executables using CreateProcessAsUser(). I'm running into problems on Windows Server 2008 due to UAC and way the LUA elevation is handled through the APIs. The service runs as LocalSystem and does not have "Interact With Desktop" enabled. The processes are being run as users in the Administrators group, but not the Administrator account (which is exempted from many UAC restrictions). All the UAC default settings in place.
I can pass arbitrary commands or powershell code to the service, but I can't seem to 'break out' of the non-elevated, non-interactive process that gets kicked off by the service.
The crux of the issue seems to be that the only (public) API option for starting an elevated process is ShellExecute() with the 'runas' verb, but as far as i can tell that can't be called from a non-interactive service or you get errors like "This operation requires an interactive window station".
The only workaround I've found is mentioned here: http://www.eggheadcafe.com/software/aspnet/29620442/how-to-proper-use-sendinp.aspx
In Vista, the official documented way to elevate a process is only using the shell API ShellExecute(Ex)(not CreateProcess or CreateProcessAsUser). So your application must call ShellExecute(Ex) to launch a helper elevated to call SendInput. Furthermore, due to Session 0 isolation, a service can only use CreateProcessAsUser or CreateProcessWithLogonW(can not use ShellExecute(Ex)) to specify the interactive desktop.
..I think there is no direct way to spawn an elevated process from a windows service. We can only first use CreateProcessAsUser or CreateProcessWithLogonW to spawn a non-elevated process into the user session(interactive desktop). Then in the non-elevated process, it may use ShellExecute(Ex) to spawn an elevated process for the real task.
To do this from .net/powershell code, it looks like I'd have to do some elaborate P/Invoke stuff to call CreateProcessAsUser or CreateProcessWithLogonW since the .Net System.Diagnostics.ProcessStartInfo doesn't have an equivalent of lpDesktop that I could set to "winsta0\default". And I'm not clear on if LocalSystem even has the rights to call CreateProcessAsUser or CreateProcessWithLogonW.
I also looked at http://blogs.msdn.com/alejacma/archive/2007/12/20/how-to-call-createprocesswithlogonw-createprocessasuser-in-net.aspx and http://stackoverflow.com/questions/2313553/process-start-with-different-credentials-with-uac-on
Based on all that, I'm reaching the conclusion that there's no straightforward way to do this. Am I missing something? This really doesn't seem like it should be so hard. It feels like UAC was just never designed to handle non-interactive use cases.
And if any Microsoft people end up reading this, I noticed that the way ShellExecute internally handles elevation is by calling out to Application Information Service (AIS). Why isn't that same call to AIS available through some Win32 or .NET API? http://msdn.microsoft.com/en-us/library/bb756945.aspx
Sorry that ran a bit long. Thanks for any ideas.


The "official" way to break session zero isolation is to use a combination of the terminal services API and CreateProcessAsUser() to launch a process within a user's session. At my old job, we did just that, as we needed to display a dialog to the user from a service prior to installing a downloaded update So, I know it works, on WinXP, Win2K3, Vista, and Win7 at least, but I don't expect that Win 2K8 would be too different. Basically, the process goes as follows:
  1. Call WTSGetActiveConsoleSessionId() to get the active console session id (VERY important, as the interactive session is NOT always session 1, even on client systems). This API will also return a -1 if there is no active user logged into the interactive session (that is, logged in locally to the physical machine, as opposed to using RDP).
  2. Pass the session id from the previous API call to WTSQueryUserToken() to get an open token that reprents the user logged into the console.
  3. Call DuplicateTokenEx() to convert the impersonation token (from WTSQueryUserToken) into a primary token.
  4. Call CreateEnvironmentBlock() to create a new environment for the process (optional, but if you don't, the process won't have one).
  5. Pass the primary token from step #3 into a call to CreateProccessAsUser(), along with the command line for the executable. If you created an environment block from step #4, you must pass the CREATE_UNICODE_ENVIRONMENT flag as well (always). This may seem silly, but the API fails horribly if you don't (with ERROR_INVALID_PARAMTER).
  6. If you created an environment block, then you need to call DestroyEnvironmentBlock, otherwise you will generate a memory leak. The process is given a separate copy of the environment block when it launches, so you are only destroying local data.
And voila! Windows does some internal magic, and you see the application launch. However, although this will launch and interactive process from a service, I am not sure if it will bypass UAC (but don't quote me on that). In other words, it may not launch as an elevated process unless the registry or internal manifest says to do so, and even then, you will might still get a UAC prompt. If the token you get from step #3 is a restricted token, you may be able to use AdjustTokenPrivileges() to restore the elevated (full) token, but don't quote me on that, either. However, as stated in the MSDN docs, please note that it is not possible to "add" privileges on a token that did not already have them (e.g. you can't turn a restricted user token into an administrator by using AdjustTokenPrivileges; the underlying user would have to be an admin to start with).
It is technically possible to do all this from Win2K forward. However, it is really only feasible starting with WinXP, as Win2K lacks the WTSGetActiveConsoleSessionId() and WTSQueryUserToken() API's (along with WTSEnumerateProcesses() for Win2K Pro). You can hard code 0 as the session id (since that is always the case in Win2K), and I suppose you might be able to get the user token by enumerating the running processes and duplicating one of their tokens (it should be one that has the interactive SID present). Regardless, the CreateProcessAsUser() will behave the same way when passed an interactive user token, even if you don't select "interact with the desktop" from the service settings. It is also more secure than launching directly from the service anyway, as the process will not inherit the godly LocalSystem access token.
Now, I don't know if your third party app does any of this when it runs the script/process, but if you want to do it from a service, that is how (and with Vista or Win7, it's the only way to overcome session 0 isolation).

Window Stations 和桌面

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

在 Windows NT 4.0中,只有一个window station 能被看得到,就是WinSta0。可见的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, WinSta0,和缺省桌面都与这个用户的Shell进程相关联。这样用户就能看到shell了,如果不是这样,用户是什么也看不到的。而且在这之后,由shell启动的所有进程也会和WinSta0 及缺省桌面相关联。

你还可以通过STARTUPINFO 数据结构的lpDesktop 成员指定你的进程同哪个window station 和桌面相关联。(如图8所示)这个数据结构传递给CreateProcess 和 CreateProcessAsUser两个函数。你可以将lpDesktop 初始化为NULL,意思是让CreateProcess函数使用和调用进程相同的window station 和桌面。你可以将你自己的window station 和桌面组合定义成"WinSta0\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 cbReserved2;
LPBYTE lpReserved2;
HANDLE hStdInput;
HANDLE hStdOutput;
HANDLE hStdError;
} STARTUPINFO, *LPSTARTUPINFO;

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

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

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

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

WinSta0\Default


Service-0x0-3e7$\Default


Service Account's Logon SID\Default

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

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

为什么知道哪个window station和桌面与服务相关联是如此的重要呢?第一,交互性。如果你的服务没有和WinSta0\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 桌面现在是活动桌面。当第二个用户登录系统时,系统将会切换回缺省桌面。用户显示的任何事物都可以被交互式用户看到。

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

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

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

注册表"蜂箱"

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

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

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

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

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

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

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

结尾

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

Understanding Windows at a deeper level - Sessions, Window Stations, and Desktops

This post will answer some very simple questions about how Windows works. This post is meant to be read by people with a technical background, and at parts it will help if you have a little knowledge about programming in Windows.
If you don't fully know the answer to any of the questions below, then you should read this post:
  • Ever wonder what happens when you lock your computer? What happens to all of the open programs? How about your task bar?
  • What is so special about UAC anyway? How do they lock and dim the whole screen? Does it really protect me?
  • Why don't software key loggers work anymore to capture a locked computer's password?
  • What's so special about screen savers? How do they work?
  • How can there be more than one user at a time logged onto the same computer at the same time?
  • How does Terminal Services or Remote Desktop work?
  • Why does your remote computer control software probably suck?
  • What does the "Allow services to interact with desktop" checkbox do on the NT services property page?
  • Why was Vista perceived to be so bad and Windows 7 so good?
To understand how all of the above works, you need to understand the concept of something called Sessions, Window Stations, and Desktops.
Some of the below may be a little heavy, but it's worth learning it to see how Windows really works.
A gentle introduction to Sessions:
Each program you have on your computer, when run is considered a process. A process is a program which is being executed. Each process is the program code, a collection of threads, and other resources relating to the program.
Each process in Windows belongs to a single user who started that process, and each process also belongs to something called a Session. Each Session in Windows hosts a collection of processes, windows, Window Stations, Desktops, and several other resources. Window Stations and Desktops will be covered later in this post.
You can see a list of all of the processes on your computer by going into Task Manager (taskmgr.exe) and clicking on the "Processes" tab. In this list you can see the Username of the user who started the process and also the Session that the process belongs to. By default Windows will not show you the Session each process belongs to but you can easily see it by clicking on the View menu item and then "Select Columns..." Turn on the option "Session ID".
Each process belongs to exactly 1 Session and each Session has a Session ID which identifies it. You cannot change a process' Session after the process is started. In Task Manager you will see at least 1 Session if you are using an operating system below Windows Vista and you will see at least 2 Sessions if you are using an operating system of Vista or above.
In Windows you are not limited to that initial number of Sessions though. There can be many different Sessions, there is a limit that can be reached but we'll say for the sake of conversation that you can potentially have infinite Sessions.
If you're using Vista or above, the first Session, Session 0 is where all of the NT services are started. The second Session above 0 is where the first logged on user's programs will be started.
More Sessions than what I mentioned will occur anytime you have multiple users logged into the same machine. You can have multiple users logged into the same machine via Terminal Services, Remote Desktop, or multi user logins onto the same machine via switch-user. For each additional login operation that you make, a new Session is made.
You can use CreateProcessAsUser to create a process in another Session. To do this you must use a user token which will contain the associated Session. To set the Session on the user token you can use the Win32 API SetUserToken with a token information class of TokenSessionId.
So to recap, so far we understand that inside your Windows operating system (OS) you have the following:
  • Session 0
    • Process 0.1
    • Process 0.2
    • Process 0.3
    • ...
    • Process 0.N
  • Session 1
    • Process 1.1
    • Process 1.2
    • Process 1.3
    • ...
    • Process 1.N
  • ...
  • Session M
    • Process M.1
    • Process M.2
    • Process M.3
    • ...
    • Process M.N
How Vista changes how Sessions work:
Before Windows Vista, the first logged in user and the NT services shared the first Session which was Session 0. This Session was also allowed to be interactive.
Windows Vista and above started to put user Sessions separate from NT service Sessions. It also made sure that Session 0 was not interactive.
These changes with Vista were made for security reasons. The security reason the change was made, was to ensure that services would be safe from application code. Why do services need to be protected? Because services run at an elevated privilege when run as the System account and hence have access to do things a user program shouldn't be able to control. More on this later in the section: "How to circumvent all security in Windows".
How Sessions worked Pre-Vista with 3 logged on users:
Pre-Vista how Sessions work
How Sessions worked Post-Vista with 3 logged on users:
Pre-Vista how Sessions work
The difference of the 2 diagrams being that the first logged on user has his own Session in the Post-Vista diagram.
Window Stations:
Each Session contains a collection of Window Stations, a clipboard, and more. Each Window station has a name unique to the Session it belongs to. Meaning within a Session each Window Station is unique. But across Sessions two Window Stations can share a name but they are completely distinct.
You can think of a Window Station as a security boundary. Once a Window Station is created, you cannot change the Session that it belongs to.
Each process belongs to a single Window Station but unlike Sessions vs. Processes, a single process can change its Window Station after startup time.
The following Win32 API can be used to deal with Window Stations: GetProcessWindowStation, SetProcessWindowStation, CreateWindowStation, and OpenWindowStation.
There is one special Window Station called Winsta0 for every station. WinSta0 is the only Window Station that can display a user interface and receive user input, it uses the keyboard, mouse and display. Other Window stations cannot display graphical user interfaces nor receive user input.
A process can set a Window Station, to associate itself with, by calling the Win32 API SetProcessWindowStation.
Once a process sets its Window Station it can then access things inside that Window Station such as Desktops, and the clipboard. Desktops will be discussed later.
Each process actually has a parent process. When your process gets started, if you aren't dealing with Window Station code directly it will put you in the same Window Station as your parent process. A process can create new Window stations with the Win32 API CreateWindowStation
So to recap , so far we understand that inside your Windows OS you have the following:
  • Session 0
    • Winsta0
      • Some Processes
    • Winsta1
      • Some Processes
    • ...
    • WinstaN
      • Some Processes
  • Session 1
    • Winsta0
      • Some Processes
    • Winsta1
      • Some Processes
    • ...
    • WinstaN
    • Some Processes
  • ...
  • Session M
    • Winsta0
      • Some Processes
    • Winsta1
      • Some Processes
    • ...
    • WinstaN
      • Some Processes
Windows Desktops
Each Window Station contains a collection of Desktops. A Desktop is loaded into kernel memory space and is a logical display surface. Any GUI object is allocated here.
Each Windows Desktop belongs to a single Session and also a single Window Station.
Only one Desktop at a time can be active (displayed) per Session. And by definition it must belong to WinSta0. The active Desktop is called the input Desktop. One can always get a handle to the active Desktop within ones own Session by calling OpenInputDesktop
WinSta0 has 3 Desktops loaded:
  1. Winlogon (the logon screen)
  2. Default (the user Desktop)
  3. ScreenSaver
There is a 4th Desktop on Vista and higher called the "Secure Desktop" which is used by default in UAC prompts.
When you lock your workstation you perform a Desktop swtich from the Default Desktop to the WinLogon Desktop.
As far as NT services go, each NT service that has credentials specified will create its own Window Station and Desktop.
The following Win32 API can be used to deal with Desktops:
  • To set a Desktop for a thread you can call SetThreadDesktop
  • A process can create a new Desktop by calling CreateDesktopEx, when a new Desktop is created it will be assigned into the Window Station associated with the calling process.
When starting a process you can specify which Window Station and Desktop to start it in. You can do this with the STARTUP info structure and the lpDesktop member. Typically this is called from a function like CreateProcessAsUser or CreateProcess.
So to recap , so far we understand that inside your Windows OS you have the following:
  • Session 0
    • Station Winsta0
      • Desktop Winlogon
        • Some processes
      • Desktop Default
        • Some processes
      • Desktop Screensaver
        • Some processes
      • Desktop UAC
        • Some processes
      • Some other Desktops
        • Some processes
    • Station Winsta1
      • Some other Desktops
        • Some processes
    • ...
    • Station WinstaN
      • Some other Desktops
        • Some processes
  • Session 1
    • Station Winsta0
      • Desktop Winlogon
        • Some processes
      • Desktop Default
        • Some processes
      • Desktop Screensaver
        • Some processes
      • Desktop UAC
        • Some processes
      • Some other Desktops
        • Some processes
    • Station Winsta1
      • Some other Desktops
        • Some processes
    • ...
    • Station WinstaN
      • Some other Desktops
        • Some processes
  • ...
  • Session M
    • Station Winsta0
      • Desktop Winlogon
        • Some processes
      • Desktop Default
        • Some processes
      • Desktop Screensaver
        • Some processes
      • Desktop UAC
        • Some processes
      • Some other Desktops
        • Some processes
    • Station Winsta1
      • Some other Desktops
        • Some processes
    • ...
    • Station WinstaN
      • Some other Desktops
        • Some processes
Mysterious checkbox in the services tab (Optional Read)
There is a little mysterious checkbox that appears in the property page of each of your services called "Allow services to interact with Desktop".
This checkbox decides that your service will run under the Window Station Winsta0 or under a different Window Station that doesn't allow user interaction. This checkbox is not guaranteed to be supported forever, and will probably eventually disapear, but it is supported up to Windows 7 so far.
This checkbox can be turned on for any service via the registry, so this by itself may be a security risk. So I would think the checkbox will probably be removed.
If this checkbox is ON, then a new Session is created and a new Window Station called Winsta0. If the service tries to display a GUI, then active user Sessions in front of a GUI will get a notification that there is a GUI on another Desktop trying to be displayed. You can then click on it to view that GUI. A user can also chose to be reminded again in 5 minutes time about the GUI notification. When you view this new Desktop it will usually look like a blank screen except for the service GUI itself.
If this checkbox is OFF, and the service tries to display a GUI, nothing will happen to any visible Desktop. The service gets started in Session 0. The GUI calls will succeed but no GUI will ever be shown.
Windows Handles
Windows inside the Windows OS are children of Desktop objects.
A Window is any GUI element and is usually identified by a Windows handle (HWND). It is important to understand where Windows Handles fit into the whole picture because then you can understand what you can do across Desktops and what you cannot do across Desktops.
Communication across Sessions
Depending on the type of communication, inter-Session communication is possible.
Things like pipes, global events, and sockets allow you to communicate across Sessions.
Things like Windows Messages, and local events do not allow you to communicate across Sessions.
As I mentioned earlier Windows Vista made a gigantic change to how Windows works by starting all of the services inside Session 0. This meant that a ton of programs which were built as Windows services and used to display a GUI no longer could display that GUI.
The proper way to display a GUI for service code now is to do some kind of inter-Session communication such as a pipe and have the GUI program be a separate program which communicates with your service.
A second way to display a GUI from a service is to simply launch the process within another user's Session inside Winsta0 and the Default Desktop.
Communication across Desktops
Windows messages are not possible across Desktops. Windows messages are only possible within the same Desktop. As confirmed here: Inter-Desktop communication via message passing is not possible.
This means that Windows Hooks which allow you to monitor and get notifications for any message from another process can only be installed at a Desktop Level.
So a key logger for example wouldn't be allowed to have access to what is typed when a computer is locked in a different Desktop.
After enumerating the Desktops you can enumerate the windows inside each Desktop.
You can use the Win323 API EnumDesktopWindows to enumerate these Desktop windows. The purpose of me telling you this is that this function takes in a handle to a Desktop and it returns to you a handle to the Windows inside that Desktop. This reinforces what I've been saying about Windows being children of Desktops.
How to circumvent all security in Windows
It is actually possible to do anything you want in Windows in any Session, Window Station, or Desktop. The solution is to build a service running on your computer running as the Local System Account.
As long as this service is running elevated via a manifest file, it can obtain the token and linked token of any process in any Session, and start a helper program within that same token to do anything it wishes to. This is probably exactly how Windows Task Manager works.
//UAC creates 2 tokens.  The second one is the restricted token and the first one is the one returned by LogonUser
//Vista and above links the elevated token to the Logonuser token though :))))
TOKEN_LINKED_TOKEN tlt;
DWORD len;
if(GetTokenInformation(hToken
    , (TOKEN_INFORMATION_CLASS)TokenLinkedToken
    , &tlt, sizeof(TOKEN_LINKED_TOKEN)
    , &len))
{
    hLinkedToken = tlt.LinkedToken;
    //From here you can start elevated processes
}
Tying it all together
Now is the fun part when we can now answer each of the questions in turn:
Ever wonder what happens when you lock your computer? What happens to all of the open programs? How about your task bar?
When you lock your computer you are doing a Desktop switch from the Default Desktop to the Winlogon Desktop which are both within the same Window Station WinSta0. Both Desktops are also within the same Session of course.
This also means that there are many such login screens across Sessions and many different users logged in could each be at their own version of this screen at the same time on the same computer.
What is so special about UAC anyway? How do they lock and dim the whole screen? Does it really protect me?
When you get a UAC prompt, by default what happens is you switch from the Default Desktop to the Secure Desktop. UAC takes a screenshot of your Default Desktop, applies a dim to that image and then displays it behind the UAC window. The UAC window is part of the Secure Desktop The user can actually set if UAC prompts should run under the current Desktop (less secure) or the Secure Desktop.
Why don't software key loggers work anymore to capture a locked computer's password?
I remember as a kid writing a key logger and using it at school. I was able to see everyone's login password, then later I could login as them and see all of their files. Since then multi Session operating systems have been introduced though.
Because Software Keyloggers are based off of Windows Hooks which work with Windows messages. They get notified for every keystroke that occurs because each keystroke has its own set of Windows Messages (key down, key up, key pressed). Since the key logger is started on a different Desktop it cannot log a password.
I think it would be possible to build such a KeyLogger which would work across Sessions but I'm not aware of any that exist. To learn how see the section: "How to circumvent all security in Windows"
What's so special about screen savers? How do they work?
There's nothing special about a screensaver. It doesn't hide any of your GUI elements nor draw on top of them. It simply does a Desktop switch to the Screensaver Desktop. Remember, a Desktop is a logical graphical device.
How can there be more than one user at a time logged onto the same computer at the same time?
Easy, each user has its own Session, and each Session contains everything else. Each person using a Session sees their own Desktop which is part of that Session's WinSta0 Window Station.
How does Terminal Services or Remote Desktop work?
Terminal Services and Remote Desktop work by either giving you access to an already open Session, or creation a new Session. Each Session can be in a connected or disconnected state.
Why does your remote computer control software probably suck?
Some remote computer control software (not Terminal Services / Remote Desktop) are not Session aware and they only work with the first Session. This includes most VNC servers including FogCreek Copilot.
If you have a multi-Session computer you can't control each Session.
Can a process communicate across different Sessions?
Yes but you need to use the correct communication means.
Can a process communicate across Desktops with Windows messages?
No.
Why was Vista perceived to be so bad and Windows 7 so good?
Because Windows Vista was the first to implement these changes. Windows Vista therefore was the Operating System to break all of the existing changes. Many software development companies and their products took a lot of time to implement the changes needed to support Session 0 isolation. Most probably still don't fully understand it.
Since some of the changes weren't made in time, Windows Vista took the hit for looking bad. But of course it was Vista's fault in the first place for breaking compatibility.
I'm not claiming Vista was perfect, it was far from it; however, Vista took more of a hit than it deserved.
Further reading

Launching an interactive process from Windows Service in Windows Vista and later

The first thing you should do about it is that, don't do it. There are many limitations, bad implications and restrictions involved into it.
In case, you are in a state that you cannot avoid launching an interactive process from Windows Service then you might want to read this article.
Launching an interactive application from a Windows service is different in Windows Vista
Introduction
To maintain isolation and provide rich user experience Windows uses sessions. When a user logs in a Terminal Services enabled computer a new session is created with each logon. Each session is associated with Logon LUID.
The interactive window station, Winsta0, is the only window station that can display a user interface or receive user input.
In Windows Vista and later Windows service run in a separate session.
In all the interactive sessions Winsta0 is created by default. A Window station acts as container to the desktop. Each session has at least one Window Station (default windows station "winsta0") and a Window station have three desktops
  • Default desktop
  • Screensaver desktop
  • Winlogon desktop
Name of the window station is case in-sensitive however name of desktops are case sensitive.
Windows station and desktop is provided to secure UI and GDI objects.  UI and GDI objects are associated with Windows station and desktops and Window station and desktops have DACLs associated with them. They are considered as securable objects in Windows.
Note:
  1. Windows station and desktop are secured. A user or a process running under that user's context can access them only if the user has got appropriate access rights to access it.
  2. Every session has its own isolated Window station and desktops. You cannot obtain handle of a Window station and subsequently of desktops of a session other than your session. Therefore you cannot modify the DACLs or security of the window station or desktops across session.
Effect of session on launching a process
A process is associated with the access token of a user. This access token is compared against securable objects DACLs when the process tries to access it.  There are 14 type of securable objects present in Windows for example Files, folders and registry keys. For a process it is must to have an access token which is derived from a user under whose context the process is launched.
There are three most effective ways to launch a process using Windows APIs.
1)      CreateProcess
a.       Access token is derived from the parent. The process is launched in the session of the parent and access the window station desktop of the parent by default.
b.      You can change the desktop accessed by this process by populating lpDesktop member of STARTUPINFO structure. Make sure that parent process has appropriate access to that window station and desktop and they are in the same session of the parent process.
2)      CreateProcessAsUser
a.       You need a primary access token to pass as the first parameter of this API.
b.      LogonUser API can be used to get the primary access token of a user. LogonUser expects credentials of the user in plain text.
a.       The CreateProcessWithLogonW and CreateProcessWithTokenW functions are similar to the CreateProcessAsUser function, except that the caller does not need to call the LogonUser function to authenticate the user and get a token. CreateProcessWithLogonW cannot be called from Local System context. Therefore if you have a service running in Local System context you cannot use  CreateProcessWithLogonW.
Session information is kept in User's access token. A process gets a session depending on the token used to launch it. The session information of an access token can be changed.
Note: A process cannot change the session id of its own token.
Both 2nd and 3rd method requires a user to login in the local system. The newly created process's access token will be checked against the DACLs of Windows Station and desktop while it will try to attach itself to them.  Also, as mentioned above Windows create a session on each logon. While launching the process by using method 2 and 3 either you would need to create and specify windows station and desktop for lunching process or Windows by default will create a Window station and desktop
For 1st method, process is launched in the same session of parent process with same access token. By default this child process will have access to the Window station and desktop of the parent. If you are changing these two you would need to make sure that parent process have access to the new window station and desktop.
Process to launch the interactive process
Until Windows Server 2003 the services and the first logged on user used to run in the same session, Session 0. Therefore, the Window station and desktop of the first logged on user was shared by services running in the same session. Therefore services were able to obtain the handle of the user's desktop and modify its DACL.
Up to and including Windows 2003 you check the check box saying "Interact with desktop" in Properties dialog box of the Service and you can launch the process interactively.
With security enhancement in Windows Vista and later versions of Windows, services run in a separate session called Session 0. Now, in Session 0 a process can only get handle of Window station and desktop associated with session 0.
Let's categorize the process in two by two cases; we will discuss each of them. These cases are listed in decreasing order of feasibility.
1)       Your service is running in LocalSystem account context.
A.      You are willing to launch the application as currently interactively logged on user.
B.      You are willing to launch the application as some user other that interactively logged on user.
2)       Your service is running in any account other than LocalSystem account.
A.      You are willing to launch the application as currently interactively logged on user.
You are willing to launch the application as some user other that interactively logged on user.         
1.A. ) Your service is running in LocalSystem account and you are willing to launch the application as currently interactively logged on user.
This is how you do it:
Get the user token by calling
Use this token in CreateProcessAsUser.
This token will have the right session ID of the interactive user. You will not have to change anything you are good to go.
1.B. ) Your service is running in LocalSystem account and you are willing to launch the application as some user other than interactively logged on user.
For an interactive application we need two essential things
1)      An access token with right session ID
a.       You need an access token of that user. You would need to call LogonUser to get the primary token. This call will create a new session and associate the session ID with token. Now, your token does not have the right Session ID. You can change it by calling SetTokenInformation. After this call you have the right session ID in the token.
2)      You need a Window station and desktop to associate with your application.
Newly created application will not have access right to the interactive session’s window station and desktop. This application cannot associate itself with interactive desktop.
Therefore, even though we have good token the launched process will not survive. It will be killed by Windows desktop manager because the desktop it is trying to access is not accessible to it.
You can change the permissions of the Window station and desktop. The only way to change the permission is to get the handle of the desktop. The process cannot obtain the handle of the desktop of session other than their session. This implies that service which is running in session 0 cannot obtain the handle of the desktop in the interactive session.
To workaround this you would need to perform following steps
a)      Launch a hidden temporary process as explained in step 1.A.
                                I.            Get the user token by calling WTSQueryUserToken (WTSGetActiveConsoleSessionId (), &hToken) ;
                              II.            Use this token in CreateProcessAsUser to launch the temporary process.
Call  CreateProcessWithLogonW from the temporary process to create the target application. CreateProcessWithLogonW saves you from explicitly modifying the DACL of the interactive desktop. However, you need the credentials of the target user to provide in this API.
2.A.) Your service is running in any other account and you are willing to launch the application as currently interactively logged on user.
You cannot call WTSQueryUserToken. This API can only be called from services running under LocalSystem account.
There is no definite way to do this because there is no legitimate way to obtain the logged on user's token with right Session ID. Here is what you should never, never and never do.
You would need to steal the access token of a process running on interactive user’s desktop. The best process to steal the access token would be Explorer provided only one interactive user is logged in. 
a)      Enumerate the process to get the handle of the Explorer.
b)      OpenProcessToken should give you the access token.
Note : The account under which your service is running must have appropriate privileges to call this API and get process token.
c)       Once you have the token call CreateProcessAsUser with this token. This token already have the right session Id.
2.B.) Your service is running in any account other than LocalSystem and you are willing to launch the application as some user other that interactively logged on user.
Same text goes again:: Here is what you should never, never and never do.
You would again need follow the technique we followed in 1.B . Launch a hidden application by performing
2.A.a, 2.A.b and 2.A.c
Once hidden application is launched follow 1.B.a- 1.B.f to launch the target application.
When interactive user log off
I have seen people saying that my application is running in a user's context which is not same as interactively logged in user, should my application be running when the interactive user logs off ?
When the user logs off, the system destroys the session associated with that user. Therefore the Windows stations and the desktops associated with that user will be destroyed. Even if your application is running in a different user's context it is attached to the interactive user's desktop.
All the applications running on the interactive desktop will be terminated. So is yours.
 However, you can expect a WM_QUERYENDSESSION and WM_ENDSISSION for your application.
Where is the code example ??
You got to write your own!!!
-Prateek

Sessions, Desktops and Windows Stations

Today we are going to briefly go over the basics of some concepts that can be pretty confusing even at the best of times - Sessions, Desktops and Windows Stations.  So let's dive right in ...
A session consists of all of the processes and other system objects that represent a single user’s logon session.  These objects include all windows, desktops and windows stations.  A desktop is a session-specific paged pool area and loads in the kernel memory space.  This area is where session-private GUI objects are allocated from.  A windows station is basically a security boundary to contain desktops and processes.  So, a session may contain more than one Windows Station and each windows station can have multiple desktops.
Only one windows station is permitted to interact with the user at the console; this is called Winsta0. Under Winsta0 there are three desktops loaded: Winlogon (the logon screen), Default (the user desktop) and Disconnect.  All three of these have separate logical displays, which is why your main desktop disappears if you lock the workstation.  When you lock the workstation, the display switches from Default to Winlogon and there is no user interaction between the two.  In Windows Vista this is even a bit more extreme.  When you get a UAC prompt for instance, it takes a screenshot of your Default desktop and then displays it dimmed out behind the UAC window in the foreground.  The UAC window is part of the Secure Desktop (new for Vista and similar to the logon desktop) and will not allow you to interact with the Default desktop until you provide input.
Other windows stations exist that do not interact with the user.  For example, services load under the ‘Service-0x0-3e7$’ non-interactive windows station. The exceptions to this are services that need to interact with the console user, so these load into Winsta0 instead.
All pages mapped to a specific user use the same memory pages, but each user has their own session space mapped in virtual memory. Session space is divided into four different areas:
  • Session Structure – Memory management control structures including session Working Set List.
  • Session Image Space – holds a private copy of Win32k.sys modified data, a single copy of Win32k.sys code and unmodified data and various session drivers.
  • Session View Space – session mapped views including desktop heap
  • Session Paged Pool – paged pool memory used for this session
As mentioned above, a desktop is an object under which a logical display surface loads.  This contains windows, menus and hooks. Session 0 is the base session where services run and is typically also the console session.  In Windows Vista this has been changed to exclusively run services, and the console session is typically Session 1.  The diagrams below show the relationships between sessions, windows stations, desktops and services in Windows Vista as compared to earlier operating systems (this is from our earlier post on Session 0 Application Compatibility Issues)
image
Session 0 in Windows XP / Windows Server 2003

image
Session 0 / Session 1 in Windows Vista
So now let's dig a little deeper using an example.  In the diagram below, we are looking at Session 0 with a user logged in named Bob.  As you can see, Winsta0 contains both processes from the user console session as well as any service that is marked as Interactive.  In this case, that includes Winlogon.exe, Explorer.exe and others that need to interact with the user.  The Service-0x0-3e7$ windows station owns any service that loads under Local System and is non-interactive.  In this case I have shown Services.exe.  As you can see by the connecting bars, it is possible for processes from different virtual sessions to load into a single windows station.  The SQL process loads under its own windows station and credentials, so it is not included in either of the other windows stations.
image
So, to reiterate what is going on in the diagram above:
  1. The whole diagram is Session 0.
  2. Processes that start under the Bob account all load in Winsta0.
  3. Interactive processes that start under Local System load in Winsta0
  4. Non-interactive processes that start under Local System load in the Service-0x0-3e7% windows station
  5. Processes that start under their own credentials start in their own windows station (like SQL)
A single desktop object will have a single desktop heap set aside for it.  This heap stores various user interface objects, such as the windows, menus and hooks.  When an application needs to draw a user interface object, it calls User32.dll to allocate this object. As I am sure you can guess, each of these interface elements requires resources out of desktop heap.  If the desktop heap becomes depleted, you will get symptoms such as a corrupt display or other anomalies.  Also, if the Session View Space becomes depleted, it will not be able to create more desktop heaps. Either of these of course is very bad. This is partially why you can still get Out Of Memory errors even on a machine with a lot of free RAM.
When this occurs, you may get initialization errors in addition to visual corruption.  A typical error you may see is 0xc0000142, which means STATUS_DLL_INIT_FAILED.  You can tell if the problem is with a single desktop heap or the entire session based on the symptom; if the desktop heap is depleted, you will only see the problem with processes that are out of heap.  If the Session View Space is depleted, you will have a problem with that entire session.
Win32k.sys has a fixed kernel address space of 48MB set aside for desktop heaps.  With Terminal Services, this space is shared with per-session storage, so that leaves 20MB for desktop heaps.  So, as you can see, it is possible to run out of desktop heap resources easier on a terminal server than a standard machine.  This is true for pre-Vista operating systems.  In Windows Vista and Windows Server 2008 desktop heap is allocated dynamically and the 48MB constraint is not there.
There is a registry setting that allows limited modification of how this memory is handled: In the HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\SubSystems key, look for the
Windows value.  There's a long string for this value that will look similar to this: %SystemRoot%system32csrss.exe ObjectDirectory=Windows SharedSection=1024,3072,512 Windows
The portion of interest is "SharedSection=1024,3072,512".  The three values under Shared Section determines how much memory in kilobytes (KB) is allocated to each component of the desktop heap.  At this point, there is a major caveat here: Please do not modify these values on a whim. Changing the second or third value too high can put you in a no-boot situation due to the kernel not being able to allocate memory properly to even get Session 0 set up.  Values up to about 8mb are generally safe, but may be pushing it, and frankly settings that high are not usually needed unless an installed program is misbehaving in the first place.  We recommend raising these values in 512kb increments and only enough to alleviate whatever problem you are experiencing.
The first value is the shared heap size, common to all desktops. It's used to store the global handle table and shared system settings. By default, it's set to 1024KB. You generally do not need to modify this value.  The second value is the desktop heap size for each desktop associated with the "interactive" window station.  It is used to store user objects like hooks, menus, strings and windows.  By default, it's set to 3072KB.  The more users that log into the system, the more desktops are created.  Consequently, the total "interactive" desktop heap size will increase to reflect the number of desktops created.  However, each desktop will only have an "interactive" desktop heap of 3072KB.  The third value is the desktop heap size for each desktop associated with the "non-interactive" window station.  By default, it's set to 512KB. But if this value is not present, the size of the "non-interactive" window station will be the same as that of the "interactive" window station.
Every service process created under a user account will be given a new desktop in a "non-interactive" window station created by the Service Control Manager (SCM).  Therefore, each of these services will consume the amount of desktop heap, as specified in the third SharedSection value.  The total desktop heap used in both interactive and non-interactive window stations must fit into the 48MB system-wide buffer.  Consequently, decreasing the second or third SharedSection values will increase the number of desktops that can be created. However, it will reduce the number of hooks, menus, strings and windows that can be created within each desktop.  Conversely, increasing the second of third SharedSection values will reduce the number of desktops that can be created and increases the number of hooks, menus, strings and windows that can be created within each desktop.  Also, increasing the third SharedSection value will reduce the number of user account services that can run successfully on the system.
So, hopefully this has shed at least a little light on how Sessions, Windows Stations and Desktops interact with each other.  The following MS articles all talk about various aspects of this subject.  There's also an in-depth blog post on the NT Debugging blog on Desktop Heap.
Additional Resources:

Thursday, April 14, 2011

Window Stations 窗口站

Window stations 和桌面可能是Windows NT服务中最与众不同的了。绝大多数编程者都不会直接接触这两种对象,尽管用户每时每刻都要碰到它们。window station 和桌面对象就象其它Windows NT对象,如事件、互斥量和信号量一样,是安全(securable)的。一个window station 对象包括一个剪贴板、一个全局原子集和更多的桌面对象。一个window station或者是可见的或者是不可见的。一个可见的window station接收用户来自于鼠标或键盘的输入。一个显示设备也与之相连这样信息可以显示给交互式用户。
在 Windows NT 4.0中,只有一个window station 能被看得到,就是WinSta0。可见的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, WinSta0,和缺省桌面都与这个用户的Shell进程相关联。这样用户就能看到shell了,如果不是这样,用户是什么也看不到的。而且在这之后,由shell启动的所有进程也会和WinSta0 及缺省桌面相关联。
你还可以通过STARTUPINFO 数据结构的lpDesktop 成员指定你的进程同哪个window station 和桌面相关联。这个数据结构传递给CreateProcess 和 CreateProcessAsUser两个函数。你可以将lpDesktop 初始化为NULL,意思是让CreateProcess函数使用和调用进程相同的window station 和桌面。你可以将你自己的window station 和桌面组合定义成"WinSta0\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 cbReserved2;
LPBYTE lpReserved2;
HANDLE hStdInput;
HANDLE hStdOutput;
HANDLE hStdError;
} STARTUPINFO, *LPSTARTUPINFO;
Window stations和桌面是具有安全性的对象。与window station 和桌面将关联的进程必须由对这些对象的合适的访问权限。如果进程没有访问权,你会看到这两个消息之一"User32.dll initialization failure(User32.dll初始化失败)" 或 "Kernel32.dll initialization failure.(Kernel32.dll初始化失败)"。由进程返回的退出码为128 或 ERROR_NO_WAIT_CHILDREN。那么我所指的合适的访问权是什么意思呢?假如你有一个文件这样的对象。你可以为这个文件创建一个DACL以使用户具有对这个文件的读访问权。Window station 和 桌面是以相同的方式工作的。
对于一个桌面对象的一个访问权限叫做DESKTOP_CREATEWINDOW。如果用户没有被授予这个访问权限,任何由这个用户启动的进程都不能创建窗口。不幸的是, 象CreateWindow 这样的USER APIs 在发生同CreateFile 或 CreateMutex API类似的安全问题时,不会返回 "Access Denied(访问被拒绝)" 消息。User32.dll 中的Windows 应用程序将会被终止,导致DLL初始化错误的消息。Kernel32.dll 初始化过程是在创建一个控制板窗口时发生的。一个例程在没有对window station和桌面的合适的访问权限的时候启动cmd.exe。然而不幸的是, CreateProcess 没有任何机制来检查这个错误。当用户不具有对window station和桌面的合适访问权限时,它并不返回一个错误信息。CreateProcess将会启动这个应用程序,然后这个应用程序本身在DLL失败后终止。
编程人员还可以有一种方法越过"User32.dll initialization failure(User32.dll初始化失败"消息。系统有一个堆用来为window station分配内存。内存是有限的。缺省设置允许创建七个或八个window station对象。如果你用光了所有的内存,你就会看到这个消息。不过,值得庆幸的是,有一个注册表关键字可以用来增加这个设置。 (参见Knowledge Base article Q142676)
如果你没在开发服务而只是普通的应用程序,Window stations 和桌面就不是真正的问题。你的应用程序只同交互式桌面WinSta0\Default 相关联。如果你是在开发一个服务,那么它可能就会同下面的window station 及桌面组合关联:
WinSta0\Default
Service-0x0-3e7$\Default
Service Account's Logon SID\Default
WinSta0\Default 同运行在LocalSystem帐户的,并且与桌面交互的服务关联 。 (在ServiceType必须指明SERVICE_INTERACTIVE_PROCESS标志)。如果服务不同桌面交互,那么它是与Service-0x0-3e7$\Default相关联的。这是个不可见的window station。你一定很疑惑这乱七八糟的0x0-3e7$ 是什么,它是服务的登录SID 。登录SID是独一无二的。它指的是用户所属的组。系统中的所有用户都会有一个登录SID。

windows station和desktop

window station 和desktop。它们主要是用于服务程序,一般的应用程序不必使用它们。它们可以用来对服务进行保护。运行在不同的window station 或不同的desktop下的应用程序,不能互相发送消息、挂钩、使用同一个clipboard等等。主要是出于安全的目的。


服务程序一般使用的是LocalSystem帐户,拥有自己的window station,和Default桌面,这个window station是不能于用户交互的,也就是说,你不能在上面显示窗口,它也不接受用户的鼠标、键盘等输入。

当使用用户帐户登录以后,看到的桌面,是WinSta0(window station)下的Default(desktop).
WinSta0下有3个桌面:
WinLogon :以Logon对话框的形式出现.当用户登录以后,WinLogon.exe切换到Default desktop.
Default :这是Explorer.exe和所有用户程序窗口出现的地方,也就是我们通常使用windows看见的地方.应用程序就运行在这个桌面上
Screen saver :系统空闲的时候,运行屏保的桌面.

可以用下面的方法解决你的问题,使你的服务运行在WinSta0(window station)下的Default(desktop):
桌面-->我的电脑-->右键,在弹出的菜单中选择"管理"-->在左面的一栏中找到"服务和应用程序"-->点击"服务"-->到右面找到你的服务,双击,在弹出的对话框中修改属性,选择“登录”标签页的“允许服务与桌面交互”,那么该服务就使用的是 WinSta0(window station)下的Default(desktop). 你也就可以与你的服务进行交互操作了。

但是,做这些之前要考虑清楚。因为,这样你的服务就有安全问题了,任何其他的程序都可以向它发送消息.比如普通程序因为跟你的服务在同一个desktop 下,就可以给你发送窗口的关闭消息,迫使退出消息循环,结束服务.或者对你的服务进行键盘、鼠标挂钩(hook)。

Why Do Certain Win32 Technologies Misbehave in Windows NT Services?

Interacting with the User in a Service

For a noninteractive service application to interact with the user, it must open the user's window station ("WinSta0") and desktop ("Default"). By default, only the logged-on user and service applications running in the LocalSystem account are granted access to the user's window station and desktop. This means that services running in other accounts must either impersonate the user when opening the interactive window station and desktop, or have access granted to those accounts by the user.


For compatibility with versions of Windows that support multiple users and/or multiple desktops per user, the user's window station and desktop names should not be hard-coded. Client applications should pass the names of their window station and desktop to the service as part of the request to the service.

This code sample displays a message on the user's desktop in response to an RPC request from one of the user's applications. Note that a global variable dwGuiThreadId in this example is also used in the logoff handling example. If the thread that displays the message box terminates after it is done with the message box, it is not necessary to save and restore the thread's desktop. It is necessary to restore the process's window station.

DWORD dwGuiThreadId = 0;

int
UserMessageBox(
RPC_BINDING_HANDLE h,
LPSTR lpszWindowStation,
LPSTR lpszDesktop,
LPSTR lpszText,
LPSTR lpszTitle,
UINT fuStyle)
{
DWORD dwThreadId;
HWINSTA hwinstaSave;
HDESK hdeskSave;
HWINSTA hwinstaUser;
HDESK hdeskUser;
int result;

// Ensure connection to service window station and desktop, and
// save their handles.

GetDesktopWindow();
hwinstaSave = GetProcessWindowStation();
dwThreadId = GetCurrentThreadId();
hdeskSave = GetThreadDesktop(dwThreadId);

// Impersonate the client and connect to the User's
// window station and desktop.

RpcImpersonateClient(h);
hwinstaUser = OpenWindowStation(lpszWindowStation, FALSE, MAXIMUM_ALLOWED);
if (hwinstaUser == NULL)
{
RpcRevertToSelf();
return 0;
}
SetProcessWindowStation(hwinstaUser);
hdeskUser = OpenDesktop(lpszDesktop, 0, FALSE, MAXIMUM_ALLOWED);
RpcRevertToSelf();
if (hdeskUser == NULL)
{
SetProcessWindowStation(hwinstaSave);
CloseWindowStation(hwinstaUser);
return 0;
}
SetThreadDesktop(hdeskUser);

// Display message box.

dwGuiThreadId = dwThreadId;
result = MessageBox(NULL, lpszText, lpszTitle, fuStyle);
dwGuiThreadId = 0;

// Restore window station and desktop.

SetThreadDesktop(hdeskSave);
SetProcessWindowStation(hwinstaSave);
CloseDesktop(hdeskUser);
CloseWindowStation(hwinstaUser);

return result;
}

什么是window station

Window station 是一个不为人所熟知但是却是非常重要的安全特性,它被设计成为一个限制操作系统中窗口环境的“沙箱”。为了避免将访问控制列表(ACL)放置到每个窗口中和对每个窗口消息进行权限检查而导致的性能下降,我们简单地让窗口互相通知而不执行任何安全检查。然而,我们是在一个私有环境中这么做的,这就是 Window station。

Window station 是一个安全核心对象,它包括了剪贴板,一个私有的原子表格,一套桌面以及一套窗口。每一个进程都依赖于一个window station,而且这个关联通常平行于登录会话的分配。也就是说,对于每一个登录会话来说,都有一个相应的window station,就像下图所描述的那样。实际上,window station的名称得自登录会话标识符。例如,用于网络服务的window station 名称是Service-0x0-0x3e4$。偶尔一个internet后台服务程序(例如IIS)会自己管理window station,但这只是一个例外。还有一个“交互式”的window station, 它会一直存在,甚至没有交互式用户存在时也如此。这个特殊的交互式window station 的名字硬编码为WinSta0, 需要我们对它更对关注。

WinSta0是唯一的一个实际绑定到硬件上的window station.也就是说,你可以在这里看到窗口,并且它们可以接收到鼠标和键盘的输入。WinSta0也是高度安全的,它通过一个能够限制登录会话运行权限的ACL来达到此目的,尽管在上图中你也可以看到一些运行在SYSTEM登录会话中的高度受信的服务程序可能运行在这里,比如说使 winlogon.exe。WinSta0上的ACL在一个非常详细的等级上控制你的GUI可以做些什么,但是实际说来,这个一个要么全有,要么全无的授权,并且只有在限制哪个进程允许关联到window station时才真正需要。(也可以调用函数SetProcessWindowStation来改变进程所属的window station,或通过在调用CreatProcess时设置STARTUPINFO参数将新启动的程序附着到特定的window station).使用这种方法,windos station上的ACL就可以阻止其他登录会话中的程序入侵到你的窗口中。

拥有window station的一个实际用处就是避免来自相对于交互式用户的internet后台服务程序的攻击。如果进程运行在一个低特权账号中同时台帐号可以发送窗口消息到一个高特权级别的程序,那么它就控制了这个程序(这称为lurring attact)。因为window station边界通常是与登录会话边界是平行的,这就有助于阻止这类的攻击。

当一个进程又创建了另外一个,那么新的进程就被放置到与旧进程相同的window station中,除非你指明不这么做(但这种情况很少)。所以,就像是令牌和登录会话一样,一个新的进程很自然的继承了它的父进程的窗口环境。注意启动一个服务不同于简单的创建一个新的进程。当一个服务启动时,它就会由操作系统分配一个合适的登录会话何window station.

一段获取当前用户Sid的小代码

#include "stdafx.h"
#include
#include

#define MAX_NAME 256

BOOL GetCurrentUserForSID (VOID)
{
DWORD dwSize = 0, dwResult = 0;
HANDLE hToken;
PTOKEN_USER pUserInfo;

LPSTR StringSid;

if (!OpenProcessToken( GetCurrentProcess(), TOKEN_QUERY, &hToken ))
return FALSE;

if(!GetTokenInformation(hToken, TokenUser, NULL, dwSize, &dwSize))
{
dwResult = GetLastError();
if( dwResult != ERROR_INSUFFICIENT_BUFFER )
return FALSE;
}

pUserInfo = (PTOKEN_USER) GlobalAlloc( GPTR, dwSize );

if(! GetTokenInformation(hToken, TokenUser, pUserInfo,
dwSize, &dwSize ) )
{
return FALSE;
}

ConvertSidToStringSid(pUserInfo->User.Sid, &StringSid);
printf("Sid: %s\n", StringSid);


if ( pUserInfo )
GlobalFree( pUserInfo );
return TRUE;
}

int _tmain(int argc, _TCHAR* argv[])
{
GetCurrentUserForSID();
return 0;
}


驱动里用SeQueryInformationToken或ZwQueryInformationToken

Progman on Win7

HWND hWnd = ::FindWindow(_T("progman"), _T("Program Manager"));
It could be Microsoft left it in to stop apps breaking on Win 7.

Wednesday, April 13, 2011

Windows下获取Logon Session信息

什么是LSA?什么是session?MSDN中的描述如下:

Local Security Authority

(LSA) A protected subsystem that authenticates and logs users onto the local system. LSA also maintains information about all aspects of local security on a system, collectively known as the Local Security Policy of the system.
(LSA) 一个判断用户登陆本系统时候的保护子系统。LSA也包含有关本系统中安全的所有方面,统称为本地安全策略的系统。
logon session

A logon session begins whenever a user logs on to a computer. All processes in a logon session have the same primary access token. The access token contains information about the security context of the logon session, including the user's SID, the logon identifier, and the logon SID.

当用户登陆计算机时,一个对应的logon session就开始了。一个logon session可以拥有多个进程,计算机上运行着的所有进程都属于一个唯一的session。怎么获取这些session和进程的相关信息呢?

LsaEnumerateLogonSessions函数可以获取已经存在的logon session identifiers (LUIDs) 和session的总数。

NTSTATUS NTAPI LsaEnumerateLogonSessions(
PULONG LogonSessionCount,
PLUID* LogonSessionList
);

当LogonSessionList不再需要时,需要调用LSAFreeReturnBuffer函数来释放所占用的内存。

看看刚才获取的LogonSessionList,数据类型为LUID。

typedef struct _LUID {
DWORD LowPart;
LONG HighPart;
} LUID, *PLUID;

为了通过LUID来获取详细的logon session信息,需要调用函数LsaGetLogonSessionData,调用者必须是拥有该session或者是本地的系统管理员。

NTSTATUS NTAPI LsaGetLogonSessionData(
PLUID LogonId,
PSECURITY_LOGON_SESSION_DATA* ppLogonSessionData
);

LsaGetLogonSessionData函数返回一个PSECURITY_LOGON_SESSION_DATA结构体。

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;

其中包含了登陆标识(LogonId)、登陆的账号(UserName)、域(LogonDomain)、认证方式 (AuthenticationPackage)、登陆类型(LogonType)、会话ID(Session)、用户的Sid(Sid)、用户登陆时间 (LogonTime)等信息。

登陆类型(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.
CachedRemoteInteractive, // Same as RemoteInteractive, this is used internally for auditing purpose
CachedUnlock // Cached Unlock workstation
} SECURITY_LOGON_TYPE, *PSECURITY_LOGON_TYPE;

用户的Sid(Sid)可以用ConvertSidToStringSid来转换成常见的SID格式字符串。

BOOL ConvertSidToStringSid(
PSID Sid,
LPTSTR* StringSid
);

这样,所有logon session的信息就获取到了。

更进一步的,用EnumProcesses函数枚举进程ID,OpenProcess获取每一个进程的句柄。在分别通过OpenProcessToken 和GetTokenInformation打开并获取进程的访问令牌信息。

BOOL OpenProcessToken(
HANDLE ProcessHandle,
DWORD DesiredAccess,
PHANDLE TokenHandle
);

BOOL GetTokenInformation(
HANDLE TokenHandle,
TOKEN_INFORMATION_CLASS TokenInformationClass,
LPVOID TokenInformation,
DWORD TokenInformationLength,
PDWORD ReturnLength
);

TokenInformationClass是个枚举类型,用来指明要获取的信息类型,这里用TokenStatistics即可。获取的信息在 TokenInformation中,数据类型为TOKEN_STATISTICS的结构体。

typedef struct _TOKEN_STATISTICS {
LUID TokenId;
LUID AuthenticationId;
LARGE_INTEGER ExpirationTime;
TOKEN_TYPE TokenType;
SECURITY_IMPERSONATION_LEVEL ImpersonationLevel;
DWORD DynamicCharged;
DWORD DynamicAvailable;
DWORD GroupCount;
DWORD PrivilegeCount;
LUID ModifiedId;
} TOKEN_STATISTICS, *PTOKEN_STATISTICS;

其中LUID AuthenticationId如果和前面logon session的LUID一致,说明该进程的拥有者是相应的logon session。

通过类似的方法,能获取很多有用的信息。下面的程序是用这些API写的一个windows下获取logon session信息,并列举属于该session的进程。