Part1

Let’s write our own Credential Server implementation.

At first, we need to create a named pipe with a unique name. Let’s construct the pipe name using a GUID – this should be unique, but we can do it in a cycle to be absolutely sure:

function CreateGinaPipe(out PipeName : String) : THandle;
var
  Guid : TGUID;
  Res : HResult;
begin
  repeat
    Res := CreateGUID(Guid);
    if Res <> S_OK then
    begin
      Writeln(Format(‘Faild to get GUID (%d)’, [Res]));
      Abort;
    end;
    PipeName := Format(‘\\.\pipe\OurLogonCreds%s’, [GUIDToString(Guid)]);
    Result := CreateNamedPipe(PChar(PipeName), PIPE_ACCESS_OUTBOUND or FILE_FLAG_WRITE_THROUGH,
      0,  1, 0, 0, NMPWAIT_USE_DEFAULT_WAIT, nil);
  until (Result <> INVALID_HANDLE_VALUE) or (GetLastError <> ERROR_PIPE_BUSY);
  if Result = INVALID_HANDLE_VALUE then
    RaiseLastOSError;
end;

We need to save the pipe name in a registry key Name under the hive HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Credentials. By default, only system has access to this key. But if you want to make FUS available to some user(s) and/or group(s), you can modify this registry key permission (but don’t forget about possible security flaws).

const
  GinaKey : String = ‘SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Credentials’;
  GinaKeyName : String = ‘Name’;

procedure CreateGinaRegistryKey(Const Name:String);
begin
  with TRegistry.Create(GetRegistryMode) do
  try
    RootKey := HKEY_LOCAL_MACHINE;
    if not OpenKey(GinaKey, true) then
    begin
      Writeln(Format(‘Failed to open KEY %s’, [GinaKey]));
      Abort;
    end;
    WriteString(GinaKeyName, Name);
  finally
    Free;
  end;
end;

Ok, now the pipe and the registry key have been created. The data format of the pipe is:

  PGinaPipeRecord = ^TGinaPipeRecord;
  TGinaPipeRecord =  record
    TotalSize : DWORD;
    Seed : DWORD; // only low byte is used
    UserName_U : UNICODE_STRING;
    Domain_U : UNICODE_STRING;
    Password_U : UNICODE_STRING;
    DataLabel : record end; // dynamic part
  end;

Note that all the UNICODE_STRING structures are normalized: their Buffer members should be point to memory inside the block, and it should be not a pointer, but an offset from the beginning of the record. What is the Seed field? The Lower byte of it is used to encrypt the Password_U using the undocumented RtlRunEncodeUnicodeString function (check http://www.d–b.webpark.pl/reverse01_en.htm for description). The original msgina.dll uses the lower byte of GetTickCount for it, let’s do this as well. Now we can write the function which will create the memory block for the pipe:

procedure CopyAndSkipString(var P : Pointer; var Str : PWideChar);
var
  Len : Cardinal;
begin
  Len := (Length(Str) + 1) * SizeOf(Str[0]);
  CopyMemory(P, Str, Len);
  Str := P;
  Inc(Cardinal(P), Len);
end;

procedure NormalizeUnicodeString(Str : PUNICODE_STRING; Base : Pointer);
begin
  Dec(Cardinal(Str^.Buffer), Cardinal(Base));
end;

function CreateGinaPipeRecord32(UserName : PWideChar;
           Domain : PWideChar;
           Password : PWideChar) : PGinaPipeRecord;
var
  BufferSize : Cardinal;
  P : Pointer;
begin
  BufferSize := SizeOf(TGinaPipeRecord) +
    (Length(UserName) + Length(Domain) + Length(Password) + 3) * SizeOf(WideChar);

  Result := GetMemory(BufferSize);
  with Result^ do
  try
    ZeroMemory(Result, BufferSize);

    TotalSize := BufferSize;
    Seed := Byte(GetTickCount);
    P := @DataLabel;

    CopyAndSkipString(P, UserName);
    CopyAndSkipString(P, Domain);
    CopyAndSkipString(P, Password);

    RtlInitUnicodeString(@UserName_U, UserName);
    RtlInitUnicodeString(@Domain_U, Domain);
    RtlInitUnicodeString(@Password_U, Password);

    RtlRunEncodeUnicodeString(@Seed, @Password_U);

    NormalizeUnicodeString(@UserName_U, Result);
    NormalizeUnicodeString(@Domain_U, Result);
    NormalizeUnicodeString(@Password_U, Result);
  except
    FreeMemory(Result);
    Raise;
  end;
end;

Ok, now we can write the data to the pipe. We need to do it in another thread since we are using FILE_FLAG_WRITE_THROUGH while accessing the pipe (which means that write function does NOT return until the client has read our data). So first the total number of structures is written; then the whole structure is transmitted (yes, with total size again!).

function SendCredentialThreadProc(Parameter: PThreadDataRec): Integer;
var
  NumberOfBytesWritten : Cardinal;
begin
  Result := 0;
  with Parameter^, Data^ do
  begin
    if  not ConnectNamedPipe(PipeHandle, nil) then
      Result := GetLastError
    else
     if not WriteFile(PipeHandle, Data, SizeOf(TotalSize), @NumberOfBytesWritten, nil) or
      (NumberOfBytesWritten <> SizeOf(TotalSize)) then
         Result := GetLastError
      else
        if not WriteFile(PipeHandle, Data, TotalSize, @NumberOfBytesWritten, nil) or
         (NumberOfBytesWritten <> TotalSize) then
          Result := GetLastError
        else
          if not DisconnectNamedPipe(PipeHandle) then
            Result := GetLastError;
    EndThread(Result);
  end;
end;

Now, while our data is being written, we disconnect the console and wait for the thread to finish writing to the pipe:

function MyShellStartCredentialServer(UserName : PWideChar;
           Domain : PWideChar;
           Password : PWideChar;
           Timeout : DWORD) : DWORD; stdcall;
var
  ThreadRec : TThreadDataRec;
  PipeName : String;
  ThreadHandle : THandle;
begin
  Result := 0;
  ThreadRec.PipeHandle := CreateGinaPipe(PipeName);
  try
    CreateGinaRegistryKey(PipeName);
    try
      ThreadRec.Data := CreateGinaPipeRecord(UserName, Domain, Password);
      try
        ThreadHandle := SendCredentialAsync(@ThreadRec);
        try
          if not WinStationDisconnect(0, WTSGetActiveConsoleSessionId, True) then
          begin
            Writeln(‘Failed to disconnect console session’);
            Abort;
          end;
          WaitForCredentials(ThreadHandle, Timeout);
        finally
          CloseHandle(ThreadHandle);
        end;
      finally
        FreeMemory(ThreadRec.Data);
      end;
    finally
      DestroyGinaRegistryKey;
    end;
  finally
    CloseHandle(ThreadRec.PipeHandle);
  end;
end;

Hurray, we’ve done it! So, now, if you’ll adjust the permission of the gina key (HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Credentials) to give everyone write access (read is not needed), we can use this function from any account!

P.S.1. x64 part

The x64 version of Windows XP does the FUS in exactly the same way; however, there are 2 factors which will prevent this program for working:

1) This program is 32 bit. 32 bit programs are accessing the HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node registry key instead of HKEY_LOCAL_MACHINE\SOFTWARE; So we can add KEY_WOW64_64KEY flag to force native registry key access.

2) UNICODE_STRING is 16 bytes long instead of 8. So we need to write some small procedure which will adjust the created record for x64:

type
  PUNICODE_STRING_64 = ^UNICODE_STRING_64;
  UNICODE_STRING_64 = record
    Length: USHORT;
    MaximumLength: USHORT;
    Alignment1 : DWORD;
    Buffer: PWSTR;
    Alignment2 : DWORD;
  end;

procedure MoveUnicodeString(var TargetPointer : Pointer; TargetBase : Pointer; TargetStr : PUNICODE_STRING_64;
  SourceBase : Pointer; SourceStr : PUnicodeString);
begin
  TargetStr^.Length := SourceStr^.Length;
  TargetStr^.MaximumLength := SourceStr^.MaximumLength;
  Cardinal(TargetStr^.Buffer) := Cardinal(TargetPointer)Cardinal(TargetBase);// normalize the offset
  CopyMemory(TargetPointer, Pointer(Cardinal(SourceBase) + Cardinal(SourceStr^.Buffer)), SourceStr^.MaximumLength);
  Inc(Cardinal(TargetPointer), SourceStr^.MaximumLength);
end;

function GetGinaRecordFor64(ExistingRecord : PGinaPipeRecord) : PGinaPipeRecord_64;
var
  Size : DWORD;
  P : Pointer;
begin
  Size := SizeOf(TGinaPipeRecord_64) + ExistingRecord^.TotalSizeSizeOf(ExistingRecord^);
  Result := GetMemory(Size);
  try
    ZeroMemory(Result, Size);
    Result^.TotalSize := Size;
    Result^.Seed := ExistingRecord^.Seed;

    P := @Result^.DataLabel;

    MoveUnicodeString(P, Result, @Result^.UserName_U, ExistingRecord, @ExistingRecord^.UserName_U);
    MoveUnicodeString(P, Result, @Result^.Domain_U, ExistingRecord, @ExistingRecord^.Domain_U);
    MoveUnicodeString(P, Result, @Result^.Password_U, ExistingRecord, @ExistingRecord^.Password_U);

    FreeMemory(ExistingRecord);
  except
    FreeMemory(Result);
    Raise;
  end;
end;

Now this program is working for x64 Windows XP as well :-)

P.S.2. – TODO list

1) Adjust the pipe security to protect it from read by anyone except SYSTEM.

2) Change the pipe write function for using overlapped structure – terminating a thread is not really a good way!

3) Validate passed credentials before writing them to the pipe

4) Refactor the code as class

SwitchUser.zip (247)
VN:F [1.9.3_1094]
Rating: 10.0/10 (1 vote cast)
VN:F [1.9.3_1094]
Rating: +1 (from 1 vote)
Executing a Fast User Switch programmatically - part 2, 10.0 out of 10 based on 1 rating convert this post to pdf.