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:

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:

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:

  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:

  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:

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.

  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 (99) convert this post to pdf.