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 Win
Depending on your use case, you could do what I do. I hunt down the winlogon process for the active session and steal its token. If there's no active session (API returned -1), use 1 if WINVER >= 6 otherwise 0. This results in SYSTEM on the active session.
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:
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).WTSQueryUserToken()
to get an open token that reprents the user logged into the console.DuplicateTokenEx()
to convert the impersonation token (from WTSQueryUserToken
) into a primary token.CreateEnvironmentBlock()
to create a new environment for the process (optional, but if you don't, the process won't have one).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
).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).