AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Thema durchsuchen
Ansicht
Themen-Optionen

Password hash in RDP files

Ein Thema von Remko · begonnen am 20. Mär 2007 · letzter Beitrag vom 22. Okt 2007
Antwort Antwort
Seite 1 von 2  1 2      
Benutzerbild von Remko
Remko

Registriert seit: 10. Okt 2006
Ort: 's-Hertogenbosch, Die Niederlande
222 Beiträge
 
RAD-Studio 2010 Arc
 
#1

Password hash in RDP files

  Alt 20. Mär 2007, 09:35
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
  Mit Zitat antworten Zitat
shmia

Registriert seit: 2. Mär 2004
5.508 Beiträge
 
Delphi 5 Professional
 
#2

Re: Password hash in RDP files

  Alt 20. Mär 2007, 10:38
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 ?
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;
Andreas
  Mit Zitat antworten Zitat
Benutzerbild von Remko
Remko

Registriert seit: 10. Okt 2006
Ort: 's-Hertogenbosch, Die Niederlande
222 Beiträge
 
RAD-Studio 2010 Arc
 
#3

Re: Password hash in RDP files

  Alt 20. Mär 2007, 11:05
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.
  Mit Zitat antworten Zitat
shmia

Registriert seit: 2. Mär 2004
5.508 Beiträge
 
Delphi 5 Professional
 
#4

Re: Password hash in RDP files

  Alt 20. Mär 2007, 12:06
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.
Andreas
  Mit Zitat antworten Zitat
Benutzerbild von Remko
Remko

Registriert seit: 10. Okt 2006
Ort: 's-Hertogenbosch, Die Niederlande
222 Beiträge
 
RAD-Studio 2010 Arc
 
#5

Re: Password hash in RDP files

  Alt 20. Mär 2007, 12:18
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;
  Mit Zitat antworten Zitat
Benutzerbild von Remko
Remko

Registriert seit: 10. Okt 2006
Ort: 's-Hertogenbosch, Die Niederlande
222 Beiträge
 
RAD-Studio 2010 Arc
 
#6

Re: Password hash in RDP files

  Alt 20. Mär 2007, 17:03
Decrypting the string from MSTSC gives me back the password and description 'psw'.
  Mit Zitat antworten Zitat
Benutzerbild von Remko
Remko

Registriert seit: 10. Okt 2006
Ort: 's-Hertogenbosch, Die Niederlande
222 Beiträge
 
RAD-Studio 2010 Arc
 
#7

Re: Password hash in RDP files

  Alt 21. Mär 2007, 12:05
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.
Angehängte Dateien
Dateityp: pas umain_141.pas (797 Bytes, 87x aufgerufen)
Dateityp: exe rdp_206.exe (405,0 KB, 125x aufgerufen)
  Mit Zitat antworten Zitat
Benutzerbild von Remko
Remko

Registriert seit: 10. Okt 2006
Ort: 's-Hertogenbosch, Die Niederlande
222 Beiträge
 
RAD-Studio 2010 Arc
 
#8

Re: Password hash in RDP files

  Alt 27. Mär 2007, 21:34
*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.
  Mit Zitat antworten Zitat
marabu

Registriert seit: 6. Apr 2005
10.109 Beiträge
 
#9

Re: Password hash in RDP files

  Alt 28. Mär 2007, 08:29
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
  Mit Zitat antworten Zitat
Benutzerbild von Remko
Remko

Registriert seit: 10. Okt 2006
Ort: 's-Hertogenbosch, Die Niederlande
222 Beiträge
 
RAD-Studio 2010 Arc
 
#10

Re: Password hash in RDP files

  Alt 29. Mär 2007, 07:59
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?
  Mit Zitat antworten Zitat
Antwort Antwort
Seite 1 von 2  1 2      


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 09:01 Uhr.
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