Hallo!
Aus ursprünglich einer
Frage von mir im Netzwerke-Board ist ein kleines Projekt entstanden.
Wenn man mittels
Indy mit externen Diensten Daten austauscht, hat man es heutzutage oftmals mit SSL/TLS-gesicherten Verbindungen zu tun. Gerade beim Debugging kann das ziemlich hinderlich sein, wenn der Server eine Anfrage abweist und man nur sehr umständlich heraus findet, woran es scheitert.
Die meisten modernen Webbrowser unterstützen
das Schreiben einer NSS-Datei. Dank dessen konnte ich verschlüsselte Verbindungen mittels
Wireshark einsehen. Beispielsweise das Projekt
FMC wurde dadurch überhaupt erst möglich.
Nun wollte ich gerne mit eigenen HTTP-Clienten ebenso verfahren und eine solche
NSS-Datei erstellen. Das erwies sich als erstaunlich schwierig, weil es in der von mir verwendeten
Indy-10-Bibliothek
einen Bug gab.
Nachdem das aus der Welt geschafft war, ist das Erstellen einer NSS-Datei mit
Indy eigentlich ganz einfach. Auf das Grundgerüst eines HTTPS-Clients gehe ich hier nicht weiter ein, dafür gibt es bereits
Tutorials. Letzten Endes hat man eine TIdSSLIOHandlerSocketOpenSSL-Instanz. Diese verfügt über ein Ereignis namens OnStatusInfoEx. Dieses Ereignis verwenden wir für unser SSL-Keylog.
Delphi-Quellcode:
uses
IdSSLOpenSSL, IdSSLOpenSSLHeaders;
const
FNSSFilePath = '
C:\Log\SSLKEYLOG.LOG';
var
FLastNSSLine: AnsiString;
procedure TForm1.DoSSLStatusInfoEx(ASender: TObject;
const AsslSocket: PSSL;
const AWhere, Aret: Integer;
const AType,
AMsg:
string);
var
LMasterByte, LRandomByte: Byte;
LRandomChar: AnsiChar;
LMasterSecret, LClientRandom, LLogLine: AnsiString;
LMode: Word;
LFileStream: TFileStream;
LS3: Pssl3_state;
begin
if AWhere = SSL_CB_HANDSHAKE_DONE
then begin
LS3 := AsslSocket^.s3;
Dec(PByte(LS3));
// <-- Bug in IdSSLOpenSSLHeaders.pas Deklaration write_mac_secret : array [0..EVP_MAX_MD_SIZE] of TIdAnsiChar; // muss [0..EVP_MAX_MD_SIZE - 1] lauten
for LRandomChar
in LS3.client_random
do begin
LRandomByte := Ord(LRandomChar);
LClientRandom := LClientRandom + AnsiString(Format('
%2.2x', [LRandomByte]));
end;
for LMasterByte
in AsslSocket^.session^.master_key
do begin
LMasterSecret := LMasterSecret + AnsiString(Format('
%2.2x', [LMasterByte]));
end;
LLogLine := '
CLIENT_RANDOM ' + LClientRandom + '
' +
LMasterSecret + sLineBreak;
if LLogLine <> FLastNSSLine
then begin
LMode := (fmOpenReadWrite
or fmShareDenyNone);
if DirectoryExists(ExtractFilePath(FNSSFilePath))
then begin
if not FileExists(FNSSFilePath)
then begin
LMode := fmCreate
or LMode;
end;
LFileStream := TFileStream.Create(FNSSFilePath, LMode);
try
LFileStream.Position := LFileStream.Size;
LFileStream.
Write(LLogLine[1], Length(LLogLine));
Exit;
finally
FLastNSSLine := LLogLine;
FreeAndNil(LFileStream);
end;
end;
end;
end;
end;
OnStatusInfoEx wird während einer Verbindung mehrfach aufgerufen. Deshalb prüfen wir den Verbindungsstatus mit
if AWhere = SSL_CB_HANDSHAKE_DONE
auf den richtigen Moment. Anschließend erzeugen wir mittels zweier Schleifen aus den beiden Arrays
AsslSocket^.s3.client_random
und
AsslSocket^.session^.master_key
zwei Strings und bauen daraus die neue Zeile für die NSS-Datei zusammen. Diese schaut dann in etwa so aus:
Code:
CLIENT_RANDOM 0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF 0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
Die SSL-Schlüssel werden jedoch nicht bei jeder weiteren HTTPS-Anfrage, die wir mit unserem
Indy-Client absetzen, neu ausgehandelt sondern teilweise wiederverwendet. Damit wir also nicht mehrfach die selben NSS-Einträge schreiben, benutze ich hier einfach eine globale AnsiString-Variable
FLastNSSLine
und prüfe ob diese mit der neuen Zeile identisch ist. Es ist übrigens durchaus wichtig, AnsiStrings zu verwenden und nicht etwa Strings. Dies würde der Compiler auch mit mehreren Implizite-Umwandlung-Warnungen quittieren.
Die NSS-Datei wird mittels Pfadangabe (hier die globale Konstante
FNSSFilePath
an einem bestimmten Platz gespeichert. Anstelle der Konstante sollte man sinnigerweise die Environment-Variable %SSLKEYLOGFILE% auswerten. Diese wird z.B. auch vom Firefox-Browser verwendet. Aber Vorsicht: Firefox und den eigenen Client nicht parallel laufen lassen, weil der Browser die Datei Write-Exclusive öffnet. Wir öffnen sie jedoch Shared-Read-Write, sodass wir zumindest keine anderen Anwendungen blockieren.
Schließlich muss
Wireshark noch
konfiguriert werden, die soeben erstellte NSS-Datei zur Entschlüsselung von SSL/TLS-Verbindungen zu nutzen.
Der oben schon erwähnte Bug in Indy 10 befand sich in der IdSSLOpenSSLHeaders.pas und wurde am 3. Dezember 2018 in den Indy-Quellen behoben. Daher ist es erforderlich, die Zeile Dec(PByte(LS3));
zu löschen, wenn man den Indy-10-Quellenstand vom 3.12.2018 oder später verwendet. Delphi 10.3 Rio wurde jedoch noch mit dem fehlerhaften Stand ausgeliefert.
Unterstützt wird aktuell SSLv1 bis SSLv3 sowie TLS 1.0 bis TLS 1.2. Zum jetzigen Zeitpunkt ist TLS 1.3 noch kein Thema, weil dafür größere Umbauten an
Indy notwendig sind.
So, das wars dann auch schon. Happy SSL/TLS-Hacking!
Euer Cody