This article contains the description of how to start a process in the current active session on session logon notification event with permissions of system process running in the current session.
This article describes how to use
CreateProcessAsUser with the token duplicated from the system process in the current logged session. The common concept is to start process when a new session appears and to terminate it when the session connection status is changed to “disconnected” or the session is closed. The process, which our sample service starts, is the same executable file that the service itself, but started with the parameter “app”. The singularity of the process consists in the permission restriction for the users without administrator privileges.
This article is a logical continuing of another one – Monitoringof Logon/Logout in Terminal and Client Sessions.
Waiting for the system process in the session
Let’s describe the situation when we get the notification about session log in event. We want to start a process at that moment as the user that is the owner of our service. It is
NT AUTHORITY\SYSTEM. To call the
CreateProcessAsUser function we need to have the handle to the primary token that represents a user with
TOKEN_DUPLICATE access rights. We could duplicate the token of the current module (our service), it is running as NT AUTHORITY\SYSTEM. But it is running in the console session with session ID 0. What if we need the handle to the token of the process that is running in the same session, which we want to run a process in? There is a small nuance – in the moment when we get the session connecting notification, there can be no process in the session, from which we could duplicate the token. So there has to be a thread for waiting process run by the user that we need, and with the session ID of the process that matches the logged session ID, so that we will be able to duplicate the token of this process. As in the previous article project sample,
boost::thread is used. Here is the class-wrapper for work with the thread:
This class contains the pointer to the thread to stop it in destructor and the pointer to the class, which implements the function executing in the thread - class
CThreadForSession, to set stop event in the destructor of the
ThreadHolder class. Here is the
CThreadForSession class with the function running in the separate thread:
WinNotificationEvent class encapsulates work with events. It creates an event in the constructor calling the
CreateEvent() function and implements such important methods as
Set using the
SetEvent() WinApi functions correspondingly.
ISObserver is intended to provide callback from the thread of waiting for the system process in the session to the main thread that started it .
CSObserver class implements this interface. There is the map of the started threads and the session IDs corresponding them in the class members.
We extend the
CSessionManager class and add to its members the pointer to the
ISObserver interface. So in the
CSessionManager::UpdateSessionsState() method, the
ISObserver::GotLogon() method is called for every new session and
ISObserver::GotLogoff() for every ended session. The last removes the thread by sid from the thread pool thus stoping the thread for the session if it is running at the moment, and then termninates the process in this session, in case it has not been terminated by the system yet.
GotLogon() method illustrates the work with the threads map. It uses
boost::bind for calling the function - member of the
CThreadForSession class that is intended to wait for any system process in the logged session. Boost synchronization method is used in it to synchronize the access to the threads pool.
CThreadForSession::Execute() function implementation:
CreateProcessInSessionId() function returns an error code - the last error retrieved if the
CreateProcessAsUser function fails. The
ERROR_PIPE_NOT_CONNECTED error means that Logon complete notification has not been sent by winlogon yet. The root cause is that in certain environments, depending on the system performance, it is possible that the
WTS_SESSION_LOGON notification is sent for a non-zero session before it is available for process creation.
CreateProcessAsUser() fails if you pass 'winsta0\\default' as a parameter to it, because the desktop, which you are targeting at, is not created yet.
So finally, we’ve got to the part of the article, where we will obtain the handle to the token.
Obtaining the handle to the user token
GetProcessToken() function returns a handle to the user token as an out parameter. The
CHandleGuard class is a class-wrapper that has a handle as its member and calls
CloseHandle in destructor. Let’s take a look at the token obtaining function:
The function enumerates processes and searches for the process that runs under the "NT AUTHORITY\" user and has the session ID matching with the given SID, which is the function parameter. Where the
GetTokenSessionId() function is:
And function retrieving user token is:
GetProcessToken() function is being called from the
CThreadForSession::Execute() function in the separate thread until the handle to the token satisfying conditions will be retrieved. Now we’ve got to the final part of the article – process creation.
Running process using the CreateProcessAsUser() function
The final purpose of the article is to run process with the given token. First, it needs to be duplicated with the access rights of the token
TOKEN_ALL_ACCESS and the impersonation level of the new token
SecurityImpersonation value. Then the
SetTokenInformation() function has to be called. Then we can call the
CreateProcessAsUser() WinApi function. In the
STARTUPINFOW structure, we have to set
lpDesktop field to the "Winsta0\\default" value if we want the process to have access to GDI, and leave it blank otherwise. Here is the code that starts the process:
The result of this article is the service that monitors session logon/logout, connect/disconnect state and starts a process in every logged session. It will create the log file with the one entry notifying that the process is started and will be running until session is logged out.
The project supports both x86 and x64 platforms. To build the solution boost environment variable has to be set, and it has to be built for both platforms. In this article boost 1.40.0 version is used.
- MSDN Authorization documentation (http://msdn.microsoft.com/en-us/library/aa446608%28v=VS.85%29.aspx)
- GNT win32 programmer forum (http://us.generation-nt.com/answer/createprocessasuser-fails-error-pipe-not-connected-help-40987232.html#r)