$theTitle=wp_title(" - ", false); if($theTitle != "") { ?>
About Virtualization, VDI, SBC, Application Compatibility and anything else I feel like
Hello, my name is Danila Galimov and i will write here sometimes 🙂
My first post is about communications between Terminal Server sessions and Terminal Server service process (termsrv.exe/dll). Terminal Server service needs to communicate with each session for many tasks, such as sending window message, getting message reply and so on. So, on init, Terminal Server creates a SmSsWinStationApiPort port in global namespace and runs a few WinStationLpcThread threads, which are listening on port and are used to process port messages. When csrss.exe is started, it parses its command line, which usually looks like:
%SystemRoot%\system32\csrss.exe ObjectDirectory=\Windows SharedSection=4096,4096,1024 Windows=On SubSystemType=Windows ServerDll=basesrv,1 ServerDll=winsrv:UserServerDllInitialization,3 ServerDll=winsrv:ConServerDllInitialization,2 ProfileControl=Off MaxRequestThreads=16
and loads the required dlls (winsrv.dll in our case). Initialization of winsrv.dll creates a thread, which connects to SmSsWinStationApiPort port and does the loop for processing Terminal Server messages until it receives WinStationTerminate message.
We’ll try to fool Terminal Server and send some messages to the port directly. At first, we need to connect to the port. A structure of 3 DWORDS is used to for connection:
1 2 3 4 5 6 | type TConnecttionStructure = record dwVersion : DWORD; // must be VERSION_1 DWORD2 : DWORD; // unknown ErrorCode : NTSTATUS; // returned error code (if any) end; |
Let’s do the connection:
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 | procedure DoWork(); var PortHandle : THandle; PortName : UNICODE_STRING; SequrityQOS : TSecurityQualityOfService; ConnectionDataLength : DWORD; ConnectionStructure : TConnecttionStructure; begin RtlInitUnicodeString(@PortName, '\SmSsWinStationApiPort'); ConnectionDataLength := SizeOf(ConnectionStructure); with ConnectionStructure do begin dwVersion := VERSION_1; DWORD2 := 0; // is it a parameter, does it mean something? end; ZeroMemory(@SequrityQOS, SizeOf(SequrityQOS)); NtCheck('Connecting to port \SmSsWinStationApiPort', NtConnectPort(@PortHandle, @PortName, @SequrityQOS, nil, nil, nil, @ConnectionStructure, @ConnectionDataLength)); try if ConnectionDataLength = SizeOf(ConnectionStructure) then NTCheck('Connecting to port \SmSsWinStationApiPort error code', ConnectionStructure.ErrorCode); finally CloseHandle(PortHandle); end; end; |
Now we’re connected. We need to prepare and send a message. Each message (after standard PORT_MESSAGE header) has common header:
1 2 3 4 5 6 7 | TWinstationMessageHeader = record Unused1 : DWORD; ApiNumber : DWORD; // see API_NUMBER_XXX constants DoRequireReply : BOOL;// if true when NtRequestWaitReplyPort must be used to request reply, // otherwise, NtRequestPort must be used. ProcResult : NTSTATUS; end; |
For creating idle winstation, 2 fields are added to this message: WinstationName, which is optional, and OutSessionId, which returns, on success, id of recently created winstation. So the whole structure will look like this:
1 2 3 4 5 6 7 | TCreateIdleWinstationMessage = record Base : TBaseWinstationMessage; Data : record WinstationName : array [0..32] of WideChar; OutSessionId : DWORD; end; end; |
So now we can send this message and check for the results:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | procedure CreateIdleWinstation(const PortHandle : THandle); var CreateIdleWinstationMessage : TCreateIdleWinstationMessage; begin ZeroMemory(@CreateIdleWinstationMessage, SizeOf(CreateIdleWinstationMessage)); with CreateIdleWinstationMessage, Base, Header, Data, WinstationHeader do begin MessageSize := SizeOf(CreateIdleWinstationMessage); DataSize := MessageSize - SizeOf(Header); ApiNumber := API_NUMBER_WinStationInternalCreate; DoRequireReply := True; CallAndCheckResult(PortHandle, CreateIdleWinstationMessage.Base); Writeln(Format('Session id %d has been created', [OutSessionId])); end; end; |
Disconnect and Reset (logoff messages) are very simple: just target session id is added to the structure.
1 2 3 4 5 6 | TResetWinstationMessage = record Base : TBaseWinstationMessage; Data : record SessionId : DWORD; end; end; |
That’s all! Now we can just add command line parameters parsing features, and it’s ready for usage!
As only system is allowed to access SmSsWinStationApiPort port, we need to run our program as system. You can use RunAsSys program for that propose.
Unfortunately, in windows vista/2008 the Terminal Server internal functions have been changed, so this program (IdleWinsta) does not work on vista.
IdleWinsta.zip (2667 downloads )
One Response for "Terminal Server Internals"
[…] Yes, I was told that more is to come. So dig into the first article Terminal Service Internals. […]
Leave a reply