$theTitle=wp_title(" - ", false); if($theTitle != "") { ?>
About Virtualization, VDI, SBC, Application Compatibility and anything else I feel like
If you want to obtain a user’s token in a Terminal Server or Citrix session (eg to launch a process in a session) you can call the WTSQueryUserToken function.
On the x64 versions of Windows XP and Server 2003 this function fails however and returns ERROR_INSUFFICIENT_BUFFER (“The data area passed to a system call is too small.”) when called from a 32 bit process.
Internally WTSQueryUserToken calls the undocumented function WinstationQueryInformationW with the WinStationUserToken class (14) and passing a WINSTATIONUSERTOKEN struct, filled with caller ProcessId and ThreadId.
But on x64 Windows the size of this structure is 24 bytes, while on 32 bit Windows the size of the structure is 12 bytes!
Ok, let’s try to call WinstationQueryInformationW function directly:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | type THANDLE_64 = record Handle : THandle; Alignment : DWORD; end; function GetUserToken(SessionId : DWORD; out TokenHandle : THandle) : BOOL; var WINSTATIONUSERTOKEN64 : record ProcessId : THANDLE_64; ThreadId : THANDLE_64; TokenHandle : THANDLE_64; end; Res : DWORD; RetLength : DWORD; begin if (IsWow64) then begin with WINSTATIONUSERTOKEN64 do begin ProcessId.Handle := GetCurrentProcessId; ProcessId.Alignment := 0; ThreadId.Handle := GetCurrentThreadId; ThreadId.Alignment := 0; TokenHandle.Handle := 0; TokenHandle.Alignment := 0; end; Result := WinstationQueryinformationW(WTS_CURRENT_SERVER, SessionId, WinstationUserToken, @WINSTATIONUSERTOKEN64, SizeOf(WINSTATIONUSERTOKEN64), RetLength); if (Result) then TokenHandle := WINSTATIONUSERTOKEN64.TokenHandle.Handle; end else Result := WTSQueryUserToken(SessionId, TokenHandle); end; |
But this call returns ERROR_INSUFFICIENT_BUFFER again! Why?
The reason is that WinstationQueryInformationW does some buffer checks/adjustments, before calling the real function, RpcWinStationQueryInformation.
Unfortunately, in some cases the buffer size is (incorrectly) hardcoded while the receiver (termsrv.dll) in many cases allows any value greater then required.
The buffer check is done in an internal function called ValidUserBuffer:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | bool __stdcall ValidUserBuffer(unsigned int BufferSize, int WinStationInformationClass) { switch ( WinStationInformationClass ) { case 25: return BufferSize >= 0x48; case 0: return BufferSize == 8; case 2: return BufferSize == 568; case 5: return BufferSize == 264; case 7: case 11: case 12: case 13: case 16: case 17: case 18: case 19: case 20: return true; case 9: return BufferSize == 652; case 10: return BufferSize == 4; case 26: return BufferSize == 16; case 27: return BufferSize >= 0xCC; case 29: return BufferSize >= 0x20; case 34: return BufferSize >= 0x60; case 33: return BufferSize >= 0x600; case 32: case 35: case 36: return BufferSize >= 1; case 24: case 28: case 30: case 31: case 37: return BufferSize >= 4; case 8: return BufferSize == 1216; case 6: return BufferSize == 2296; case 14: return BufferSize == 12; case 21: return BufferSize >= 9; default: return false; case 1: return BufferSize == 2664; case 3: return BufferSize == 300; case 4: return BufferSize == 736; } } |
So, as you can see, there is no way to succeed using the original winsta.dll!
The only workaround is to patch it (and possibly distribute it with your app) or use RpcWinStationQueryInformation directly.
The code below works fine:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | function GetUserToken(SessionId : DWORD; out TokenHandle : THandle) : BOOL; var WINSTATIONUSERTOKEN64 : record ProcessId : THANDLE_64; ThreadId : THANDLE_64; TokenHandle : THANDLE_64; end; Res : DWORD; RetLength : DWORD; begin if (IsWow64) then begin with WINSTATIONUSERTOKEN64 do begin ProcessId.Handle := GetCurrentProcessId; ProcessId.Alignment := 0; ThreadId.Handle := GetCurrentThreadId; ThreadId.Alignment := 0; TokenHandle.Handle := 0; TokenHandle.Alignment := 0; end; Result := RpcWinStationQueryInformation(WinStationGetLocalServerHandle, Res, SessionId, WinStationUserToken, @WINSTATIONUSERTOKEN64, SizeOf(WINSTATIONUSERTOKEN64), RetLength); if (Result) then TokenHandle := WINSTATIONUSERTOKEN64.TokenHandle.Handle else SetLastError(RtlNtStatusToDosError(Res)); end else Result := WTSQueryUserToken(SessionId, TokenHandle); end; |
However to use the RpcWinStationQueryInformation you need the MIDL compiler, which supports only C/C++, to generate the code.
Of course it can be done using Delphi but that is out of scope for this article.
15 Responses for "Querying a user token under 64 bit version of 2003/XP"
Good job!
But you should always call the original function and check for this specific error. Then you can call the workaround. Otherwise you will not use a bugfix in newer windows versions.
“However to use the RpcWinStationQueryInformation you need the MIDL compiler, which supports only C/C++, to generate the code.” Sorry, I don’t get it. There is your code with RpcWinStationQueryInformation. So does it compile or doesn’t it?
[…] 9) In case session 0 has a logged-on user, Winlogon also ignores the WLX_SAS_TYPE_AUTHENTICATED message. I use WTSQueryUserToken to get the token of the logged on user. If the function succeeds, then a user is logged on. There are some problems calling this function on 64 bit systems, and they are described in Querying a user token under 64 bit version of 2003/XP […]
Hi daNIL,
Thanks for the post, this is exactly what I need to achieve, however I am having trouble, well, getting anywhere really!
Obviously RpcWinStationQueryInformation and WinStationGetLocalServerHandle are what I’m missing in order to fully compile your code fragment.
I understand that somehow I am meant to use MIDL to generate the stubs so that I am somehow able to compile them into my application, however I have no idea where RpcWinStationQueryInformation is stored, how I build some kind of stub reference to it, compile that stub using MIDL and how that would then be compiled into Delphi.
Sorry daNIL, basically I assumed I could link directly to the procedure but assuming it simple isnt exported from wherever it lives.
Is there anyway you could point me in the right direction without taking up too much of your time, or if this is something you’ve already done let me have that code in return for a €100 donation or something?
Anyway either way this is great work, unfortunately I’m not as advanced as to be able to work through it! (sorry!?)
Ross
Hello Ross,
The sample code is available free of charge as a part of AutoLogonXP sample (https://www.remkoweijnen.nl/blog/2011/03/03/autologon-user-on-windows-xp2003-using-autoreconnect-pipe-part-3-implementation-details/)
RpcWinsta unit in included there, and *some* of the RpcXXX functions are implemented there. Please take a look on it, you should be able to compile your code and run it (actually, AutoLogonXP uses the GetUserToken function mentioned in the original article).
P.S.
Of course, any donations are welcome 🙂
Crikey, I know Harry Potter is out in a couple of weeks but this is crazy as it seems to be magic, the RPC stuff looks like it doesn’t even reference the supplied parameters in the RPCxxxxx calls in RpcWinsta yet it works flawlessly!
Funny as I had the Jedi stuff including that unit already, that is annoying as I googled the RPC function name I was missing and found practically nothing, had I searched my hard disk I would have got a hit!
Anyway guys this is excellent work, keep your magic wands safe, donation is on it’s way……
Ross
Hi daNIL,
This seems to all work perfectly for the first time a user logs in, but when that same user logs out then back in again the CreateProcessAsUser() when passed with the token returns zero but the process doesnt start.
I have a service app which monitors WTS logins and logouts, when a user logs in it merely spawns itself with a command line parameter. If I restart the service it first enumerates the active sessions and spawns the process into all of them successfully. If a different user logs in it still works OK, if one of the existing users logs out (confirmed they’re completely logged out) then they log back in again the process doesnt spawn at all.
I think if the spawning bit (where it gets the token and spawns the process) is in a different process (a further spawened command line parameter function) then it will work (as it does when the service is restarted). I’ll give this a try but thought you should know.
Thanks again!
Ross
Hello Ross,
This is a well-known problem of Windows XP (there are many articles in the Internet, i’ve just googled this one (http://www.ureader.com/msg/16591696.aspx)) and it has nothing to do with QueryUserToken function. If your sollution allows installation and restart, you can do the following:
1) Register Winlogon notification package (http://msdn.microsoft.com/en-us/library/aa374783(v=vs.85).aspx). As every session has it’s own copy of Winlogon, your package will be loaded to each of it.
2) Communicate with your notification package via pipes/shared memory/events some other interprocess communication mechanis, so it(winlogon notificaion package) will create process in the session you want with the token you want. Btw, this is the real way the system does CreateProcessAsUser in the other sessions, so you’ll just copy this mechanism.
I’m not sure if this can be fixed in the other way, more investigation required, but the problem, i think, is in the original wlnotify.dll problems.
Hi daNIL,
Christian Wimmer seems to think the Jedi Winlogin Notification example (http://blog.delphi-jedi.net/2008/05/27/winlogon-notification-package/), and thus the proposed solution, wont work on 64-bit Windows Server 2003. Although I have to say the solution was really elegant, I have built my DLL and was ready to test (I cant restart the test server just yet) – might still give it a go though just in case, although if the winlogin process is natively 64-bit then it wont.
Ultimately what I am doing is simply recording the visual desktop activity in a TS session for the logged on users, I chose to go down the spawning route as I understand this is essential from Vista and above to get out of the isolated service session 0, however I guess I could implement two mechanisms – 1) threads within the service capturing Server 2003 x64 desktop images and 2) spawning for Vista\7\Server 2008\R2.
I dont suppose you know off the top of your head the method to extract the desktop image from a TS session?
Thanks for all your help daNIL, it’s really appreciated.
Ross
Hello Ross,
I think it will work if you build a 64 bit all for 64 bit system, and 32 bit dll for 32 bit system 🙂 But you’ll have to have 2 different dll’s and register them in registry depending on current OS.
The other option you can try to execute a CreateProcessAsUser by sending the data in the pipe directly (look at this sample http://www.ms-news.net/f3603/createprocessasuser-fails-with-233-no-process-is-on-the-other-end-of-the-pipe-xp-2111459.html, while it’s in c it will give you the idea)
As another option, you can include your app into HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server\Wds\rdpwd\StartupPrograms list. It can contain more than one program in the list, delimeted by commas (‘,’). So your app will get started automaticlaly whenever someonce will connect to your session from RDP protocol (or a new RDP session will be started). You can use full program name there, but be careful with full path and params, since a comma is a program delimeter there (don’t know why Microsoft guys have chosen it).
I do not know the other way of capturing a session image except creating a process in that session and read the screen.
Btw, what is your problem? You cannot create a user in session 0? Or in the otehr session? Can you please describe the failing scenario?
Hi daNil,
Yeah sure, no problem, it goes like this…
32-bit Delphi service monitors WTS events, when it finds a new login event (i.e. new TS session) then it starts a program in that new session that records the screen activity. The problem is when running this screen recorder service on x64 Server 2003.
Initially the WTSQueryUserToken was giving me an insufficient buffer (as per this post) which with your help we were able to solve. We start the program by performing your implementation of GetUserToken() on the appropriate session ID (i.e. the one that’s just logged in), then pass this token to CreateProcessAsUser() starting our application (happens to be the same EXE) with a command line switch to start the recording in the users’ session.
I am testing for “if not CreateProcessAsUser()” as to whether there is a problem, this CreateProcessAsUser() returns false (i.e. zero) on the second time “User n” logs in (i.e. when the process doesnt open).
So, basically the first time any given user logs in it works fine, then when they log out and log back in CreateProcessAsUser() appears to do nothing.
Incidentally this same code works OK x86 Server 2003, which is why I thought it might be something to do with GetUserToken().
I’ll have a look at those other solutions, thanks daNIL!
Ross
One other weird thing is when I restart the service it resets it so that users can be logged the first time again. I thought if I ripped the GetUserToken() and CreateProcessAsUser() stuff into a separate EXE that was called then closed I’d be OK, it’s as though the separate EXE which is called from the service using CreateProcess() is somehome linked to the parent process and as such still keeps hold of the pipe or whatever it is which is connected to that user.
Strange!
Ross
Hi daNIL,
Not to worry, I took you up on your StartupPrograms registry method for spawning my recorder process. This is effective and is more reliable that what I had already so this is fine!!
Thanks for all your help.
Ross
Hello Ross
I’m glad to hear that you’ve solved your problems. However, i do not fully understand the login as different users problem.
If you logoff and logon an user, it will have a different session id (except session 0). For example, if you have got an user logged on in session 1, then you do a logoff, session 1 is logged off, then disconnected , then session 2 is created and connected, then session 2 is getting logged on, so technically it’s a new session. The only problem can be with recycling of session 0 – it’s never destroyed (except shutdown). So can you please clarify the session id where the GetUserToken/CreateProcessAsUser is not working? I still do not think that there is significant difference between x86 and x64 in session/user token handling
Also be careful with this, as you should always preserve original value(s) (by default, it’s rdpclip) when adding or removing values to this registry key. And if you need to have the same behaviour for Citrix ICA connections, you’ll need to modify ICA registry key as well.
[…] […]
Hi daNIL
i met the same problem, and i want to check the solution as below, but i can’t visit the address, can you post it on the board or mail to me? i really…really need your help, thanks, Pitt
“The other option you can try to execute a CreateProcessAsUser by sending the data in the pipe directly (look at this sample http://www.ms-news.net/f3603/createprocessasuser-fails-with-233-no-process-is-on-the-other-end-of-the-pipe-xp-2111459.html, while it’s in c it will give you the idea)”
Leave a reply