GUI-Based RunAsEx

Environment: Win2K+ ONLY, VC6+, MS SDK (Platform SDK), DDK for Win2K+, Process Explorer, Local Administrator Identity

Prerequisite Knowledge: Win2K Security stuff (SID, Token, ACL, Privilege, WinStation/Desktop, and so on), NT Service, SEH

Note:

  1. To compile and link successfully, half of the functionality provided by this tool (the more interesting half) requires that you have DDK for Win2K+ installed for using Native API exported by ntdll.dll, though the executable file of this tool does not have any dependency on it (DDK).
  2. The user MUST be a local administrator member; he/she can be a common domain user meanwhile. If a certain privilege is not GRANTED, the user needs to LOGOFF (NOT REBOOT) ONCE to use this tool. This is a “Do-It-Once” job if no more domain policy is involved.
  3. The VC++6 Project File is here for this RunAsEx with all source code and final executable file. VS. NET users can easily upgrade to Solution file.
  4. Go to the kitchen and prepare a cup of coffee or tea for yourself before reading this lengthy article. You should be familiar with Token inside-out when you finish it.

Important Note: Before the time of this writing (November 2003), I planed to include several sample programs from the book Programming Server-Side Applications for Microsoft Windows 2000, ISBN 0-7356-0753-2, Microsoft Press, 1999 to save my re-creation of a similar helper program. But, I can NOT get permission from the author, so I included my own programs I wrote years ago when I read this book. The point is that I rewrote these original samples’ GUI framework with MFC and extended the user interaction (e.g. I list all Process ID in a combo to choose with clicking instead of edit box you need to type with a number found in Task Manager).
WARNING: I only expect that these helper programs will help you understand what I will be talking about here concerning this RunAsEx tool. You may find a few menus/button handlers empty there, while most do give you massive information. Again, by no means are they serious programs; they are only my coding practice when reading a cool book as a habit. And, I have no intention to polish them later. Oh, btw, at the time of writing these program, Visual Studio 6.0 Japanese Enterprise Version is my standard IDE, so the wizard generated all comments in Japanese and they look weird in the English version of Windows, but I have re-opened them and make sure they can be compiled on an English OS. Just ignore all Japanese (unreadable) comments.

1. What’s Unique in This RunAsEx and What’s It For?

So, many of you must have heard of a tool called “RunAs” that originated from the WinNT4 Resource Kit; and from Win2K. Its functionality was even integrated to the OS itself. It definitely gives the user a fast way to RunAs somebody. For example, when you want to run some software that you can not trust fully, having it running on a separate machine may be a way for the rich, ordering a Virtual Machine Utility such as VMWare also costs you dozens of bucks, and the zero-price way is that you carefully design ACLs on a directory and the Registry and let the un-trusted program run under a limited user context. One of the most serious, if not deadly, drawbacks of MS version RunAs provided by Windows OS is that it can only RunAs somebody and not the SYSTEM (LocalSystem) which is important to developers.

There come some programs for your rescue, two of which that are open source code are Mr. Keith Brown‘s tool CmdRunAs in Feb 2000, MSJ or Mr. Martyn ‘Ginner’ Brown‘s tool Start a Command As Any User in www.codeguru.com 2001. Both are based on same idea—To overcome the privilege requirement, launch (after installing) the second instance of self as a NT Service, from there call LogonUser and CreateProcessAsUser to launch the target program, and finally uninstall the NT Service. In the midst of these operations, they will modify the destination WinStation/Desktop’s DACL (it is a hard coded WinStat0\Default) and grant the target user access to the target WinStation/Desktop. Besides that, they handle the chores of loading/unloading the target user’s profile gracefully. In fact, I highly recommended you read Mr. Brown’s article before continuing unless you are quite familiar with Win2K Security already.

There are still some shortcomings of these two programs:

  1. They have no support for WTS (Windows Terminal Service) and, by default, NT Service is running in Session0 even you install one from Session1. The net result: The program you RunAs-ed from these tools always starts from the Session0 desktop. This is especially inconvenient, if not useless, for a WinXP+ FUS (Fast User Switch) user for the program RunAs-ed is always put in Session0 by these two tools.
  2. They always shoot the program to WinStat0’s Default Desktop. If you want to RunAs on a Logon Desktop, they are not usable. Go to my previous articles, “COM Interface Hooking” and “Super PasswordSpy++”, and you will find the application of RunAs on the Winlogon Desktop.
  3. They are console applications; you have to type anyway.
  4. They need the target user’s password, even you are a Administrator member who can set passwords for anybody.
  5. The final token used to CreateProcessAsUser is obtained from the LogonUser API; you have no control over it (Advanced User Only).

There are, naturally, other non-open-source tools that can handle part of the above problems and I list several of them in the ending reference list. But, none of them, it seems, can handle all and that’s why I present here a full open source RunAsEx as the the solution.

2. GUI Overview of RunAsEx

Figure 1. GUI of RunAsEx

As a whole, RunAsEx is a dialog-based Windows application written with plain C and Win32 SDK (the previous version that uses MFC is too meaty in size). It also accepts a command line so even a batch file can use it. If you do not have a command line with the target program or do not want to create a new desktop, using the mouse can satisfy all your needs without typing. It prepares a list of most used program to RunAsEx, saving your time searching them. You can even drag-and-drop the target program (including its Ink) onto it.

If you’re running on Win2K (including Pro, Svr, Adv Svr, and a WTS Remote Desktop connecting to Win2K Svr), it can show you the plain text password cached in the winlogon process. If you’re running in a Terminal Service environment, it permits you to RunAs a program in any existing session. It can make fake Tokens with any privileges, any user group (needing a slight modification in code, which I will explain later) tucked into it and use it to RunAs a program (domain users have certain limitation due to domain policy).

It will generate a command line for itself and copy it to the Clipboard so you save quite a lot typing (the CmdText button) when you want to start it from a batch file or a Command Window. It has four choices on how to RunAs, giving you enough control on how to RunAs. With a “(Password) Test” button, you can test the usr-pwd match before RunAs. A user list dialog lets you have an overview on the properties of the users on any machine/domain that you have rights to do so. A domain list dialog will show all shared net resources (it is an overkill for now, I have to admit). A privilege dialog is there to help enable some necessary privileges to save you opening MMC and clicking items one by one. A combo box containing all existing desktop on machine permits you RunAs on them and generate a new desktop on your request. And, finally, it will log the internal error to disk file or Event Log for your troubleshooting task.

The user will find both WinStation and Desktop are listed in Desktop Combo Box; only choose Desktop when RunAs. The red colored icons are WinStation-inaccessible from your program, usually due to security reasons (ERROR_ACCESS_DENIED 5). Some empty icons in the Most Used Program ListView control mean that program is not installed on the machine or not installed on the default position (e.g. you installed VS .Net on %Program Files%\MyVSnet instead of %Program Files%\Microsoft Visual Studio .NET (2003), I will not take care of this behavior by scanning your hard disk).

The check boxes give the user more control on RunAs when they use a Zw-type RunAs (the two bottom right-most buttons). Direct Launcher’s Session check box will be enabled only when in WTS mode, including a Win2K server+ client terminal and WinXP/2003 FUS. When this box is checked, the target program’s session ID will NOT be adjusted. We will talk about it in detail later. The next combo box, when in WTS, lists all session IDs available on this machine. It is recommended that this check box is unchecked. The Load User Profile check box is just as its name. You may think it has no sense when RunAs SYSTEM, but it is not true. Token From Caller is meaningful only when the user uses the Zw type; we will defer its explanation until later. By default, it is not recommended to check it. Keep User Privilege, when checked, queries the actual privilege held by the user; when unchecked, the created Token will have all privileges granted and enabled. By default, it is not checked. The “SetPrivilege” button will pop up a dialog to let the user grant/enable more privileges to themselves. Domain User, please note here, your domain group policy may affect this action; for example, reboot will deprive you of some privileges. So, please check the privileges even after you are re-logged.

3. Before You RunAsEx

Because there is no SetProcessToken API (well, a SetThreadToken does exist, which accepts only a Impersonate Token), sooner or later we need to call CreateProcessAsUser and pass it a primary token. So, where can we turn to get one? According to the documented API, it is LogonUser (LogonUserEx is basically the same as LogonUser except that it returns more information that can be obtained elsewhere). By undocumented API, it is ZwCreateToken exported by NTDLL.DLL. The most interesting thing about this ZwCreateToken is that no password is needed directly or indirectly. If you go to MSDN and check GetTokenInformation, you will see that there is quite a lot of information inside a token. And, that’s almost all you need to call ZwCreateToken. To give you an direct and intuitive image what’s inside a token, I suggest you spend some time on the ZTokenMan program I enclosed with the RunAsEx program; its GUI is like following:

Figure 2. ZTokenMan Program GUI

Push the button marked with the number in order (in Step 2, choose a process yourself) and if you experience no access problem, the token information of the process you chose in the combo box will be shown in the bottom right panel. By the way, you’d better enable four privileges: TCB, ChangeNotify, IncreaseQuota, and AssignPrimaryToken for ZTokenMan to try and launch the second instance of itself under the context of SYSTEM (which will help you peek some system process’s token). Try to peek several processes and find their common points and difference; this will be very useful when you forge your own token with ZwCreateToken. If you are really reluctant to avoid enabling these privileges, use RunAsEx to launch ZTokenMan as SYSTEM. By the way, CPUs are really getting faster and faster. Can you see there is a rainbow bar in the rear position of the status bar? I put it there because dumping a token is a time-consuming job. I remembered those days this program running on my PII 350M machine with Win2k Server, the bar will rotate several steps (the longer query token information it takes, the more the bar rotates). Now, on my WinXP running on PIV 2G CPU, it rotates one step at the most (nSeconds vs 50 milliseconds!!!)!

Figure 3. ZAccessMan Program GUI

Another tool program I want you to spare some time to play with is ZAccessMan. I want you to clear the edit box marked with 1, push the log button marked with 2 to launch the second instance under context of SYSTEM, choose correct object type (in this figure we are interested with Desktop), and choose the specific object. Now, you choose to see the object’s DACL with standard Windows security dialog or a homemade one like this:

Figure 4. Standard Security Editor Box

Figure 5. Security Editor Box from ZAccessMan

Readers should be aware here that there is some strange implementation in the Windows Station and Desktop, plus poorly documented MSDN in this part. For example, suppose you are a local Administrator and grant AND enable the TakeOwnership Privilege to yourself, you still can NOT get the DACL of WinStat0\Winlogon Desktop. I remembered that Keith Brown also mentioned this in his book Programming Windows Security, ISBN 0-201-60442-6, 2000 from Addison-Wesley, and it is still true on my Win2k Server + SP3, WinXP, and Win2003 nowadays. According to MSDN: A process must exist in a certain Windows Station and only one, and its thread(s) must be attached to a Desktop. Besides, CreateDesktop’s first input parameter can NOT contain a backslash (\). You can make a simple program to prove they are not true like this (suppose the program is started on your WinStat0\Default Desktop):

#include <windows.h>
int _tmain(int argc, _TCHAR* argv[])
{
   HWINSTA hWinStat = ::CreateWindowStation(_T("WinStatABC"), 0,
      WINSTA_CREATEDESKTOP | WINSTA_ENUMDESKTOPS | WINSTA_ENUMERATE,
      NULL);
   if(hWinStat == 0) return 0;
   //::SetProcessWindowStation(hWinStat); //You can do this here

   //Following will give you "WinStat0\WinStatABCDeskABC" without
   //an error
   HDESK hDesk = CreateDesktop(
         _T("WinStatABC\DeskABC"), NULL, NULL, 0,
         DESKTOP_ENUMERATE | DESKTOP_CREATEWINDOW, NULL);
   if(hDesk == NULL) return 0;
   ::Sleep(50000);    //Sleep long enough to let you check with
                      //other tools
   return 0;
}

You see, first, the desktop name CAN contain a backslash; the API just ignores it. Second, when you set a process to some newly created Window Station, your thread is not attached to any desktop you specified at that moment unless it is a Default desktop associated with the new WinStat, but I cannot find any documentation stating that. One possibility is that CreateWindowStation internally calls CreateDesktop with the name “Default”. What’s more, say, if you want to RunAs the program on MyWinStat\MyDesktop, you have to do it like this: CreateWindowStation, SetProcessWindowStation to MyWinStat, CreateDesktop. You also should be aware that when moving Processes between WinStat, if any inside thread has windows, menus, or hooks attached to the old desktop, the API call will fail. That’s why you will notice that RunAsEx will flash a little on the screen when you RunAs. RunAsEx has to dismiss the GUI Dialog, doing the hard work, and re-create the dialog back to the user.

But, CreateProcessAsUser needs the desktop from you, and you have to open the desktop’s DACL first to make sure the target user has permission on that desktop. There are a bunch of permissions and you choose them by your concrete need, e.g. you need not DESKTOP_JOURNALRECORD unless you want to install such a hook. Naturally, you also need to do the same on the desktop’s parent—WinStat. Following is what you will see when these requirements are not met:

Figure 6. Error Dialog When Application is Running on a certain User Context

You can turn to ZAccessMan to reproduce this: Open WinStat0\Default desktop, pop up one of the security dialog (standard or my homemade one), (Note: this experiment need you re-log after it, so save all opened document at this point.) Delete all ACEs and press OK. Now try to start any application, and you will see this dialog. You are very likely to choose re-logging instead of editing the Default Desktop DACL.

4. Experiments on Token and Desktop

Now, use ZAccessMan to open the standard security dialog on WinStat0\Default. It may show something like this:

  1. Administrators
  2. RESTRICTED //Stands for it is a token created by CreateRestrictedToken
  3. S-1-5-5-0-XXX
  4. SYSTEM

Go to my security dialog and you will get the SID of these items, as in the following:

  1. S-1-5-32-544
  2. S-1-5-12 (from SECURITY_RESTRICTED_CODE_RID)
  3. S-1-5-5-0-XXX
  4. S-1-5-18

Start ZTokenMan now, and dump any common application (e.g. start Notepad, and dump its token), and you will find what S-1-5-5-0-XXX is; it is the Logon SID inside the token. Readers, please be aware here: Here comes an difference between using token generated by LogonUser and ZwCreateToken. The former API will provide a Logon SID for you while the latter can not. You can deem Logon SID as an instance of you. In other words, Logon SID, which is given to you from the moment you log on that machine, can be traced to identify you. You can Log on multiple times, thru programs or terminal clients; each time you get a different Logon SID. With this point in mind, you can understand why, when using LogonUser, you only grant access to that Logon SID in the target WinStat and Desktop; when using ZwCreateToken, you have to grant access to the actual user.

One thing you may find in RunAsEx is that it never reverts the DACL back. It is by design. In Keith Brown’s cmdasusr, he starts a thread to wait the target process until it exits by the WaitForSingleObject(hProcess). When the process is over, the thread reverts the DACL back. RunAsEx can do it, BUT, doing so means two things:

  1. RunAsEx has to be kept running when the target is running
  2. The target process doesn’t spawn child process.

What’s worse is that multiple processes can overlap in time span, and modifying the DACL of the Desktop will affect others. Take an example, I start program A as Alice on Desktop Default, I modify Default’s DACL by adding Alice positive ACE; and then wait until A is done. Then, I start program B as Alice too on Default; this time, I checked Default’s DACL and find nothing needs to change. After some time, program A spawns program C and exits; the tracking thread reverts the DACL back by deleting that ACE. Then, programs B and C have problems because they lose access to Default. You may say that we could solve all these problems by setting up a global lookup table, using injection DLL monitor process spawn. That’s true, you can program anything, but it makes things too complicated to say it is a RunAsEx, not a SpyRunAsEx.

So, to fire-and-forget the RunAs-ed program, the DACL will not be reverted, and in most cases it is not a problem. Anyway, you have ZAccessMan, which could help to modify the DACL of WinStat and the Desktop by mouse clicking.

For the sake of some readers who just touched the WinStat and Desktop, I want to emphasize that WinStat0 and its three Default, WinLogon, and Disconnect desktops are created even before you see the logon screen (WinLogon, which asks for a password from you to log on). After you input the usr-pwd pair, the system MODIFYs WinStat0 and WinStat0\Default’s DACL (I know there must be some people thinking the Default desktop is created when you logon and deleted when you logoff); one of the modifications is adding your Logon SID ACE to the DACL. When you log off, your Logon SID becomes obsolete and the system deletes its ACE from the DACL of WinStat0 and WinStat0\Default. Because RunAsEx does NOT revert the DACL back, if you want the desktop to recover to its original state and do not want to use another tool, rebooting the machine is your only choice.

Now, let’s turn to Token and see what’s inside. Normally, you wouldn’t worry about it when you call LononUser, for only a logon type makes sense. To RunAs a process, which means assigning the token to a process instead of a thread, it must be a primary token. The following table shows you different logon types and their effect:

Flag OS Primary Token Special Group SID Included (SID, Name, Domain) Comment
INTERACTIVE   y S-1-5-4, INTERACTIVE, NT AUTHORITY The logged-on user must have been granted/enabled SeInteractiveLogonRight NT Rights (not the caller!!!).

The most-used logon type, User will be granted a unique Logon SID. Tokens received with this logon will be cached with the system. This means that the local system can lose connection with the authenticating machine but still make future successful calls to LogonUser using cached credentials.

After ImpernateLoggedOnUser (or DuplicateTokenEx, SetThreadToken), the impersonate thread can access network resources.

BATCH   y S-1-5-3, BATCH, NT AUTHORITY The logged-on user must have been granted/enabled SeBatchLogonRight NT Rights (not the caller!!!).

A unique Logon SID will be issued

Tokens received with this logon type are not cached, increasing the performance of LogonUser and making the logon type appropriate for high-performance servers.

After ImpernateLoggedOnUser (or DuplicateTokenEx, SetThreadToken), the impersonated thread can access network resources.

SERVICE   y S-1-5-6, SERVICE, NT AUTHORITY The logged-on user must been granted/enabled SeServiceLogonRight NT Rights (not the caller!!!).

A unique Logon SID will be issued.

This token will be cached for future calls to LogonUser if the machine loses connection to the authenticating agent. LogonUser returns a primary token.

After ImpernateLoggedOnUser (or DuplicateTokenEx, SetThreadToken), the impersonate thread can access network resources.

*Note: You could (and should) deem the SERVICE and BATCH types as the same (except the cache); there is really not much difference, although it is the System SCM that takes care of Service logon and COM SCM that take cares of Batch Logon.

UNLOCK   y S-1-5-4, INTERACTIVE, NT AUTHORITY This intended for GINA DLLs only, but actual effect the the same as interactive!
NETWORK   n   The logged-on user must have been granted/enabled SeNetworkLogonRight NT Rights (not the caller!!!).

Tokens received with this logon type are not cached. In addition, this token will be an impersonation token and a “network token.”

The impersonate thread can access network resources.

NETWORK_CLEARTEXT Win2K+ n   The logged-on user must have been granted/enabled SeNetworkLogonRight NT Rights (not the caller!!!).

This logon type returns an impersonation token while preserving a copy of the trustee’s credentials so that network access is possible using the resulting token. The token has to be duplicated to a primary token before it can be used in calls to CreateProcessAsUser.

The impersonate thread can access network resources.

NEW_CREDENTIALS Win2K+ y   This logon type makes a copy of the calling thread’s process token and adds a second identity to the token. This second identity will be the token’s identity for all network access, whereas the token’s identity for the local machine will remain the same as that of the original token. This makes the LOGON32_LOGON_NEW_CREDENTIALS unique in that it uses an existing token to build a new token with extra credentials. For an example of this logon type, see the RunAs.exe utility provided with Windows 2000. The “/NetOnly” switch uses the LOGON32_LOGON_NEW_CREDENTIALS logon type to create a token for the new process. The resulting token is a primary token.

The impersonate thread can access network resource.

List of Common Group SID inside User Process (non-system Process)

  • SID: S-1-1-0
    Use: Well-Known Group SID
    Name: Everyone
    Domain Name:
  • SID: S-1-2-0
    Use: Well-Known Group SID
    Name: LOCAL
    Domain Name:
  • SID: S-1-5-11
    Use: Well-Known Group SID
    Name: Authenticated Users
    Domain Name: NT AUTHORITY
  • SID: S-1-5-18
    Use: User SID
    Name: SYSTEM
    Domain Name: NT AUTHORITY

List of Common Group SID inside System Process (lsass, winlogon, rpcss, csrss)

  • SID: S-1-1-0
    Use: Well-Known Group SID
    Name: Everyone
    Domain Name:
  • SID: S-1-5-18
    Use: User SID
    Name: SYSTEM
    Domain Name: NT AUTHORITY

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read