Delphi-PRAXiS
Seite 1 von 2  1 2      

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Win32/Win64 API (native code) (https://www.delphipraxis.net/17-win32-win64-api-native-code/)
-   -   Delphi Password hash in RDP files (https://www.delphipraxis.net/88732-password-hash-rdp-files.html)

Remko 20. Mär 2007 08:35


Password hash in RDP files
 
Note: this topic is followup off a previous topic: Base64 encoding
With MSTSC.EXE you can save connection settings in an RDP file. This file will then also contain an hashed or encrypted password which is valid only for the user who created the RDP file. Analysis of MSTSC learns that the hash is created using MSDN-Library durchsuchenCryptProtectData API. The encrypted password is a fixed size (always 1329 bytes) and contains only HEX chars. And might look like this:
Code:
password 51:b:01000000D08C9DDF0115D1118C7A00C04FC297EB0100000062B
(removed the rest, you get the picture)

With the code below I'm able to produce a working RDP file with some remarks: The produced hash is smaller in size (about 350 bytes) and seems to work only during the same logon session.
So the question is, how to produce the same hash as MSTSC?
In the previous topic Marabu suggested the MSDN-Library durchsuchenCryptStringToBinary API. I also saw on MSDN there's a MSDN-Library durchsuchenCryptBinaryToString API. I've added Delphi translations for these functions to JwaCrypt CVS version.

Delphi-Quellcode:
Uses JwaCrypt;

var DataIn: DATA_BLOB;
    DataOut: DATA_BLOB;
    Description: PWideChar;
    Hash2: String;
    I: Integer;
    P: PByte;
    sRDPFileName: string;
    RDPFile: TStringList;
    LocalAppData: PAnsiChar;
    sFolder: string;
    si: _STARTUPINFOW;
    pi: PROCESS_INFORMATION;
    pwCmdLine: PWideChar;
    pchString: DWord;
    Res: Boolean;
    pszString: PWideChar;

begin
  GetMem(LocalAppData, MAX_PATH);
  if ShGetFolderPath(THandle(nil),
                     CSIDL_LOCAL_APPDATA,
                     THandle(nil),
                     SHGFP_TYPE_CURRENT,
                     LocalAppData) = S_OK then
  begin
    sRDPFileName := String(LocalAppData + '\GPRMC');

    if not DirectoryExists(sRDPFileName) then
    begin
      MkDir(sRDPFileName);
    end;

    sRDPFileName := sRDPFileName + '\GPRMC.rdp';
    Memo1.Lines.Add(sRDPFileName);
  end;
  FreeMem(LocalAppData);

  DataOut.cbData := 0;
  DataOut.pbData := nil;
  DataIn.pbData := Pointer(WideString(Edit1.Text));
  DataIn.cbData := Length(Edit1.Text) * SizeOf(WChar);
  Description := WideString('psw');
  if CryptProtectData(@DataIn,
                      Description,
                      nil,
                      nil,
                      nil,
                      CRYPTPROTECT_UI_FORBIDDEN,
                      @DataOut) then
  begin
    P := DataOut.pbData;
    I := DataOut.cbData;

    Hash2 := '';
    while (I > 0) do begin
      Dec(I);
      Hash2 := Hash2 + IntToHex(P^, 2);
      Inc(P);
    end;

    RDPFile := TStringList.Create;
    RDPFile.Add('screen mode id:i:1');
    RDPFile.Add(Format('desktopwidth:i:%d', [Screen.Width]));
    RDPFile.Add(Format('desktopheight:i:%d', [Screen.Height - 50]));
    RDPFile.Add('session bpp:i:16');
    RDPFile.Add('winposstr:s:2,3,247,0,1055,627');
    RDPFile.Add('full address:s:SERVERNAME');
    RDPFile.Add('compression:i:1');
    RDPFile.Add('keyboardhook:i:2');
    RDPFile.Add('audiomode:i:2');
    RDPFile.Add('redirectdrives:i:0');
    RDPFile.Add('redirectprinters:i:0');
    RDPFile.Add('redirectcomports:i:0');
    RDPFile.Add('redirectsmartcards:i:0');
    RDPFile.Add('displayconnectionbar:i:1');
    RDPFile.Add('autoreconnection enabled:i:1');
    RDPFile.Add('username:s:USERNAME');
    RDPFile.Add('domain:s:DOMAIN');
    RDPFile.Add('alternate shell:s:');
    RDPFile.Add('shell working directory:s:');
    RDPFile.Add('password 51:b:' + Hash2);
    RDPFile.Add('disable wallpaper:i:1');
    RDPFile.Add('disable full window drag:i:1');
    RDPFile.Add('disable menu anims:i:1');
    RDPFile.Add('disable themes:i:1');
    RDPFile.Add('disable cursor setting:i:0');
    RDPFile.Add('bitmapcachepersistenable:i:1');
    RDPFile.SaveToFile(sRDPFileName);
    RDPFile.Free;

    ZeroMemory(@si, SizeOf(si));
    si.cb := SizeOf(si);
    si.lpDesktop := nil;
    pwCmdLine := PWideChar(WideString('mstsc.exe ' + '"' + sRDPFileName + '"'));
    if CreateProcessW(nil,
                      pwCmdLine,
                      nil,
                      nil,
                      False,
                      0,
                      nil,
                      nil,
                      si,
                      pi) then
    begin
      // Succes
    end
    else begin
      // Failure
    end;
  end;
end;
Debugging MSTSC.EXE while saving an RDP shows this sequence:

CryptProtectData - CRYPT32.dll
CryptUnprotectData - CRYPT32.dll
CryptUnprotectData - CRYPT32.dll
CryptProtectData - CRYPT32.dll

It seems like the first CryptProtectData crypts the username (I passed Username as user)
http://web.inter.nl.net/users/weijnen/dp/Info1.jpg
And the 2nd CryptProtectData the Password (I passed Password as the password string)
http://web.inter.nl.net/users/weijnen/dp/Info2.jpg

shmia 20. Mär 2007 09:38

Re: Password hash in RDP files
 
The *.RDP file is stored using UNICODE.
The file starts with the BOM (Byte Order Mark): $FF$FE to indicate that the file is using UNICODE.
You could easily view and edit the content the file with the Wordpad application; just draw & drop the file on Wordpad.

Sorry, but your code looks a bit like spaghetti.
Why don't you use a function to calculate the hash for the password ?
Delphi-Quellcode:
function CalcPasswordHash(const PlainPW:string):string;
Then compare the output of this function with the content of a RDP file to make shure that your
hash function is fine.

To save your stringlist as UNICODE:
Delphi-Quellcode:
procedure SaveWideStringToFile(const filename:string; const ws:WideString);
const
   BOM_UTF16 = $FEFF;  // BOM = Byte Order Mark
var
   fs : TFileStream;
   BOM : WideString;
begin
   BOM := Widechar(BOM_UTF16);
   fs := TFileStream.Create(filename, fmCreate);
   try
      fs.WriteBuffer(BOM[1], Length(BOM)*sizeof(Widechar));
      fs.WriteBuffer(ws[1], Length(ws)*sizeof(Widechar));
   finally
      fs.Free;
   end;
end;

....
    RDPFile.Add('bitmapcachepersistenable:i:1');
//    RDPFile.SaveToFile(sRDPFileName);  // wrong
    SaveWideStringToFile(RDPFile.Text);  // correct
    RDPFile.Free;

Remko 20. Mär 2007 10:05

Re: Password hash in RDP files
 
I agree the code is Spaghetti, I just made it quickly to test it. I will tidy it up and make it more readable.
I checked and rdp file with HEX viewer and indeed it starts with FF FE (although mstsc doesn't bother absence of it).

I already made the plaintext password a WideString but I keep ending with a hash about 350 bytes long while MSTSC always produces 1329 bytes. Also each time I save the RDP file the hash is different.

shmia 20. Mär 2007 11:06

Re: Password hash in RDP files
 
Zitat:

Zitat von Remko
I already made the plaintext password a WideString but I keep ending with a hash about 350 bytes long while MSTSC always produces 1329 bytes. Also each time I save the RDP file the hash is different.

On my machine the hash is always 1329 chars long, too.
This is a little bit strange, because it should be a even number.

I've found some C code to decode the hashtext:
http://archives.neohapsis.com/archiv...2-12/0045.html
Maybe MSTSC uses a different "Description" every time it calls CryptProtectData.
You could try to decrypt a hash created by MSTSC to see the description.
I think the description contains date & time.

Remko 20. Mär 2007 11:18

Re: Password hash in RDP files
 
I watched what MSTSC does with a debugger, the description is always psw.

Here's the cleaned up function:
Delphi-Quellcode:
uses JwaWinCrypt;

function CryptRDPPassword(sPassword: string): string;
var DataIn: DATA_BLOB;
    DataOut: DATA_BLOB;
    pwDescription: PWideChar;
    P: PByte;
    I: Integer;
    PwdHash: string;
begin
  PwdHash := '';

  DataOut.cbData := 0;
  DataOut.pbData := nil;

  // RDP uses UniCode
  DataIn.pbData := Pointer(WideString(sPassword));
  DataIn.cbData := Length(sPassword) * SizeOf(WChar);

  // RDP always sets description to psw
  pwDescription := WideString('psw');

  if CryptProtectData(@DataIn,
                      pwDescription,
                      nil,
                      nil,
                      nil,
                      CRYPTPROTECT_UI_FORBIDDEN, // Never show interface
                      @DataOut) then
  begin
    // Convert the DataBlob to Hex String
    P := DataOut.pbData;
    I := DataOut.cbData;

    PwdHash := '';
    while (I > 0) do begin
      Dec(I);
      PwdHash := PwdHash + IntToHex(P^, 2);
      Inc(P);
    end;
  end;

  Result := PwdHash;

  // Cleanup
  pwDescription := nil;
  LocalFree(Cardinal(DataOut.pbData));
  LocalFree(Cardinal(DataIn.pbData));

end;

Remko 20. Mär 2007 16:03

Re: Password hash in RDP files
 
Decrypting the string from MSTSC gives me back the password and description 'psw'.

Remko 21. Mär 2007 11:05

Re: Password hash in RDP files
 
Liste der Anhänge anzeigen (Anzahl: 2)
So I've cleaned up the code and put it in a seperate unit. It contains Encrypting and Decrypting RDP password hashes.
I don't really like the function PasswordHashToBlobData though. I could use some tips so make this part better.

Edit: Attached demo program
Delphi-Quellcode:
{******************************************************************}
{ Author: Remko Weijnen (r dot weijnen at gmail dot com)          }
{ Version: 0.1                                                     }
{ Date: 21-03-2007                                                 }
{                                                                  }
{ The contents of this file are subject to                        }
{ the Mozilla Public License Version 1.1 (the "License"); you may }
{ not use this file except in compliance with the License. You may }
{ obtain a copy of the License at                                 }
{ [url]http://www.mozilla.org/MPL/MPL-1.1.html[/url]                         }
{                                                                  }
{ Software distributed under the License is distributed on an     }
{ "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or  }
{ implied. See the License for the specific language governing    }
{ rights and limitations under the License.                       }
{******************************************************************}

unit uRDPHash;

interface

uses Windows, Sysutils, JwaWinCrypt;

function CryptRDPPassword(sPassword: string): string;
function DecryptRDPPassword(sPasswordHash: string): string;
function BlobDataToHexStr(P: PByte; I: Integer): string;
function PasswordHashToBlobData(sPasswordHash: string): DATA_BLOB;

implementation

{***********************************************************}
{ HexToByte: Converts Hex value to Byte                    }
{ Found this somewhere on the internet                     }
{***********************************************************}
function HexToByte(s : String) : Byte;
const
  cs = '0123456789ABCDEF';
begin
  result := 0;
  if (length(s) = 2) and
     (s[1] in ['0'..'9','A'..'F']) and
     (s[2] in ['0'..'9','A'..'F']) then
    result := ((pos(s[1],cs)-1) *16) + (pos(s[2],cs)-1)
  else raise EConvertError.CreateFmt('%s is not a Hexformatstring',[s]);
end;

{***********************************************************}
{ PasswordHashToBlobData: Converts a RDP password Hash to  }
{                         a DATA_BLOB structure            }
{ sPasswordHash : RDP Password Hash (HEX String            }
{***********************************************************}
function PasswordHashToBlobData(sPasswordHash: string): DATA_BLOB;
var Buf: array of Byte;
  dwBufSize: Cardinal;
  i: Cardinal;
  j: Cardinal;
  dwHashSize: Cardinal;
begin
  dwBufSize := Length(sPassWordHash) DIV 2;
  dwHashSize := Length(sPasswordHash);
  SetLength(Buf, dwBufSize);

  i := 1;
  j := 0;
  while i < dwHashSize do begin
    Buf[j] := HexToByte(sPassWordHash[i] + sPassWordHash[i+1]);
    Inc(i, 2);
    Inc(j);
  end;

  GetMem(Result.pbData, dwBufSize);
  Result.cbData := dwBufSize;
  Result.pbData := PByte(Buf);
end;

{***********************************************************}
{ BlobDataToHexStr: Converts a PByte from a DATA_BLOB      }
{                   to a Hex String so it can be saved in  }
{                   an RDP file                            }
{ P : PByte (pbData) from DATA_BLOB                        }
{ I : Integer (cbData) from DATA_BLOB                      }
{***********************************************************}
function BlobDataToHexStr(P: PByte; I: Integer): string;
var HexStr: string;
begin
  HexStr := '';
  while (I > 0) do begin
    Dec(I);
    HexStr := HexStr + IntToHex(P^, 2);
    Inc(P);
  end;
  Result := HexStr;
end;

{***********************************************************}
{ CryptRDPPassword: Converts a plaintext password to       }
{                   encrypted password hash                }
{                   an RDP file                            }
{ sPassword: plaintext password                            }
{***********************************************************}
function CryptRDPPassword(sPassword: string): string;
var DataIn: DATA_BLOB;
    DataOut: DATA_BLOB;
    pwDescription: PWideChar;
    PwdHash: string;
begin
  PwdHash := '';

  DataOut.cbData := 0;
  DataOut.pbData := nil;

  // RDP uses UniCode
  DataIn.pbData := Pointer(WideString(sPassword));
  DataIn.cbData := Length(sPassword) * SizeOf(WChar);

  // RDP always sets description to psw
  pwDescription := WideString('psw');

  if CryptProtectData(@DataIn,
                      pwDescription,
                      nil,
                      nil,
                      nil,
                      CRYPTPROTECT_UI_FORBIDDEN, // Never show interface
                      @DataOut) then
  begin
    PwdHash := BlobDataToHexStr(DataOut.pbData, DataOut.cbData);
  end;
  Result := PwdHash;

  // Cleanup
  LocalFree(Cardinal(DataOut.pbData));
  LocalFree(Cardinal(DataIn.pbData));

end;

{***********************************************************}
{ DecryptRDPPassword: Converts an RDP Password Hash back   }
{                     to it's original password.           }
{                     Note that this only works for the user}
{                     who encrypted the password (or on the }
{                     same computer in case it was encrypted}
{                     with the computerkey                 }
{ sPasswordHash: Password hash (string)                    }
{***********************************************************}
function DecryptRDPPassword(sPasswordHash: string): string;
var DataIn: DATA_BLOB;
    DataOut: DATA_BLOB;
    sPassword: string;
    pwDecrypted: PWideChar;
    pwDescription: PWideChar;
begin

  DataIn := PasswordHashToBlobData(sPasswordHash);

  DataOut.cbData := 0;
  DataOut.pbData := nil;

  if CryptUnprotectData(@DataIn,
                        @pwDescription,
                        nil,
                        nil,
                        nil,
                        CRYPTPROTECT_UI_FORBIDDEN, // Never show interface
                        @DataOut) then
  begin
    Getmem(pwDecrypted, DataOut.cbData);
    lstrcpynW(pwDecrypted, PWideChar(DataOut.pbData), (DataOut.cbData DIV 2) + 1);
    sPassword := pwDecrypted;
    FreeMem(pwDecrypted);
  end
  else
  begin
    raise EConvertError.CreateFmt('Error decrypting: %s',[SysErrorMessage(GetLastError)]);
  end;

  Result := sPassword;

  // Cleanup
  if DataOut.cbData > 0 then
  begin
    LocalFree(Cardinal(DataOut.pbData));
  end;
end;


end.

Remko 27. Mär 2007 20:34

Re: Password hash in RDP files
 
*push*
Any thoughts on improving this part:
Delphi-Quellcode:
{***********************************************************} 
{ PasswordHashToBlobData: Converts a RDP password Hash to  } 
{                         a DATA_BLOB structure            } 
{ sPasswordHash : RDP Password Hash (HEX String            } 
{***********************************************************} 
function PasswordHashToBlobData(sPasswordHash: string): DATA_BLOB;
var Buf: array of Byte;
  dwBufSize: Cardinal;
  i: Cardinal;
  j: Cardinal;
  dwHashSize: Cardinal;
begin
  dwBufSize := Length(sPassWordHash) DIV 2;
  dwHashSize := Length(sPasswordHash);
  SetLength(Buf, dwBufSize);

  i := 1;
  j := 0;
  while i < dwHashSize do begin
    Buf[j] := HexToByte(sPassWordHash[i] + sPassWordHash[i+1]);
    Inc(i, 2);
    Inc(j);
  end;

  GetMem(Result.pbData, dwBufSize);
  Result.cbData := dwBufSize;
  Result.pbData := PByte(Buf);
end;
BTW Functionally everything is working now, the hashed password also works after logout/login.

marabu 28. Mär 2007 07:29

Re: Password hash in RDP files
 
Hi Remko,

are you aware of the support functions HexToBin() and BinToHex()?

Delphi-Quellcode:
uses
  Classes;

var
  s: string;
  data: array of Byte;

begin
  s := 'CafeCafe';
  SetLength(data, Length(s) shr 1);
  HexToBin(@s[1], @data[0], Length(data));
  BinToHex(@data[0], @s[1], Length(data));
  ShowMessage('"' + s + '"');
end;
Regards

Remko 29. Mär 2007 06:59

Re: Password hash in RDP files
 
Thanks Maribu. I did see the functions in the Help but decided not to use them because of this note in the help:
Note:
The hexadecimal number must use lower-case characters; HexToBind does not recognize upper-case characters.

Using shr brings back an old discussion:
Warning Only use Shr when a bit operation is required - do not use instead of a multiplication or division. First because it is unclear as to what is happening. Secondly, bits may be lost in the operation.

Warning The compiler will reject hardcoded shift right values that exceed 32, unless the data type is Int64. The same is not true for Shl.

Still your code looks efficient, so I'm going to test.

Is it bad practice to have the function return a structure (DATA_BLOB)? Would it be better to use is as a var param?


Alle Zeitangaben in WEZ +1. Es ist jetzt 07:56 Uhr.
Seite 1 von 2  1 2      

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz