AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Programmierung allgemein Win32/Win64 API (native code) Delphi Digitale Signaturen (Authenticode) von Dateien auslesen
Thema durchsuchen
Ansicht
Themen-Optionen

Digitale Signaturen (Authenticode) von Dateien auslesen

Ein Thema von Dalai · begonnen am 12. Feb 2023 · letzter Beitrag vom 20. Feb 2023
Antwort Antwort
Benutzerbild von Dalai
Dalai

Registriert seit: 9. Apr 2006
1.680 Beiträge
 
Delphi 5 Professional
 
#1

Digitale Signaturen (Authenticode) von Dateien auslesen

  Alt 12. Feb 2023, 15:43
Hallo Leute, ich brauch mal wieder eure Hilfe und euren Sachverstand .

Aktuell bin ich damit beschäftigt, digitale Signaturen von Dateien auszulesen (EXE, CAT, SYS, MSI, MSU usw.). Erst konnte ich nur die erste Signatur lesen, fand dann aber bei StackOverflow den entscheidenden Hinweis:
Zitat:
Authenticode stores secondary signatures in the UnauthenticatedAttributes of primary signer (index 0), instead of additional PKCS 7 signer.

From the primary signature, search the UnauthenticatedAttribue for below:
[...]
szOID_NESTED_SIGNATURE
Mithilfe dieser Informationen konnte ich den folgenden Code zusammenschreiben:
Delphi-Quellcode:
procedure TSignatureContainer.FindAllEmbeddedSignatures(const ASignerInfo: PCMSG_SIGNER_INFO; const ACertStore: HCERTSTORE);
var i: integer;
    { Nested signatures }
    LUnauthAttr: PCRYPT_ATTRIBUTE;
    LNestedMsg: HCRYPTMSG;
    LNestedSignerInfo: PCMSG_SIGNER_INFO;
    LNestedSignerInfoSize: DWORD;
    LNestedMsgCertStore: HCERTSTORE;
begin
    LUnauthAttr:= ASignerInfo.UnauthAttrs.rgAttr;

    { Geht ASignerInfo.AuthAttr durch }
    AddSignatureFromSignerInfo(ASignerInfo, ACertStore);

    for i:= 1 to ASignerInfo.UnauthAttrs.cAttr do begin
        if AnsiString(LUnauthAttr.pszObjId) = szOID_NESTED_SIGNATURE then begin
            FNestedSignature:= True;
            LNestedMsg:= CryptMsgOpenToDecode(X509_OR_PKCS7_ENCODING, 0, 0, 0, nil, nil);
            if NOT Assigned(LNestedMsg) then
                Exit;
            try
                if NOT CryptMsgUpdate(LNestedMsg, LUnauthAttr.rgValue.pbData, LUnauthAttr.rgValue.cbData, True) then
                    Exit;
                if NOT CryptMsgGetParam(LNestedMsg, CMSG_SIGNER_INFO_PARAM, 0, nil, LNestedSignerInfoSize) then
                    Exit;
                GetMem(LNestedSignerInfo, LNestedSignerInfoSize);
                try
                    if NOT CryptMsgGetParam(LNestedMsg, CMSG_SIGNER_INFO_PARAM, 0, LNestedSignerInfo, LNestedSignerInfoSize) then
                        Exit;
                    LNestedMsgCertStore:= CertOpenStore(CERT_STORE_PROV_MSG, X509_OR_PKCS7_ENCODING, 0, 0, LNestedMsg);
                    try
                        FindAllEmbeddedSignatures(LNestedSignerInfo, LNestedMsgCertStore);
                    finally
                        CertCloseStore(LNestedMsgCertStore, 0);
                    end;
                finally
                    FreeMem(LNestedSignerInfo);
                end;
            finally
                CryptMsgClose(LNestedMsg);
            end;
        end;
        Inc(LUnauthAttr);
    end;
end;
Wie man im Code sehen kann, werden die Attribute sowohl rekursiv als auch iterativ durchsucht. Dennoch sind so nicht mehr als zwei Signaturen auszulesen. Also hab ich weitergesucht und bin auf diesen C++-Code gestoßen, von dem der Autor behauptet, der Code könne alle geschachtelten Signaturen (Nested Signatures) auslesen.

Da sind wir beim eigentlichen Punkt: Kann mir jemand erklären, warum das so umständlich gemacht wird, warum diese Kopfstände mit Umwandlung Big Endian in Little Endian (Zeile 198), Vergleich einzelner Bytes (Zeile 175), Byte Alignment (Zeile 201) und einigen anderen Dingen gemacht werden? Gibt's dafür nicht fertige Funktionen in der Windows API, die das Ganze "in schön" tun? Wie macht man es richtig? Oder geht das tatsächlich nur so umständlich? Und wenn ja, wie würde man diesen C++-Code nach Delphi übersetzen, vor allem die Makros?


Wer eine Datei mit mehr als zwei Signaturen braucht, kann einen Link zu einer solchen z.B. in diesem Issue bei Github finden.

Grüße
Dalai
  Mit Zitat antworten Zitat
Benutzerbild von Dalai
Dalai

Registriert seit: 9. Apr 2006
1.680 Beiträge
 
Delphi 5 Professional
 
#2

AW: Digitale Signaturen (Authenticode) von Dateien auslesen

  Alt 15. Feb 2023, 14:26
Nach etwas Zögern hab ich den C++-Code mit Visual Studio selbst kompiliert und anschließende Tests bestätigen die Vermutung, dass damit mehr als zwei Signaturen auslesbar sind.

Nun wollte ich den Code nach Delphi übersetzen, aber ich komme auf keinen grünen Zweig. Aktuell ist meine Übersetzung instabil, d.h. mal wird aus derselben Datei eine Signatur gelesen, dann doch wieder zwei (und mehr als zwei hab ich noch nicht erreichen können).

Nachfolgend einige konkrete Code-Beispiele, in der Hoffnung, dass darin mein (Denk)Fehler zu finden ist.

Code:
CONST UCHAR SG_ProtoCoded[] = {
    0x30, 0x82,
};

CONST UCHAR SG_SignedData[] = {
    0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x02,
};
habe ich übersetzt mit
Delphi-Quellcode:
const
  SG_ProtoCoded: array[0..1] of Byte = ($30, $82);
  SG_SignedData: array[0..8] of Byte = ($2a, $86, $48, $86, $f7, $0d, $01, $07, $02);
Ist das korrekt? Was bedeuten die abschließenden Kommas im C++?

Die Makros und deren Benutzung hab ich so übersetzt:
Code:
#define XCH_WORD_LITEND(num) \
    (WORD)(((((WORD)num) & 0xFF00) >> 8) | ((((WORD)num) & 0x00FF) << 8))

#define _8BYTE_ALIGN(offset, base) \
    (((offset + base + 7) & 0xFFFFFFF8L) - (base & 0xFFFFFFF8L))


cbCurrData = XCH_WORD_LITEND(*(WORD *)(pbCurrData + 2)) + 4;
pbNextData += _8BYTE_ALIGN(cbCurrData, (ULONG_PTR)pbCurrData);
Delphi-Quellcode:
var
  LpbCurrData, LpbNextData: PByte;
  LcbCurrData: DWORD;

    function BigEndianToLittleEndian(const A: Word): Word;
    begin
        Result:= Word(((A AND $FF00) shr 8) OR ((A AND $00FF) shl 8));
    end;

    function _8ByteAlign(const offset, base: NativeUInt): NativeUInt;
    begin
        Result:= ((offset + base + 7) AND $FFFFFFF8) - (base AND $FFFFFFF8);
    end;


//LcbCurrData:= Swap(Word(LpbCurrData)+2) + 4;
LcbCurrData:= BigEndianToLittleEndian(Word(LpbCurrData)+2) + 4;
Inc(LpbNextData, _8ByteAlign(LcbCurrData, ULONG_PTR(LpbCurrData)));
Was bedeutet der erste Stern in diesem Aufruf
Code:
cbCurrData = XCH_WORD_LITEND(*(WORD *)(pbCurrData + 2)) + 4;
Was genau wird da übergeben? Ein Pointer auf einen Pointer auf ein Word?

Oder liegt das Problem in der Abbruchbedingung?
Code:
    if (memcmp(pbCurrData + 0, SG_ProtoCoded, sizeof(SG_ProtoCoded)) ||
        memcmp(pbCurrData + 6, SG_SignedData, sizeof(SG_SignedData)))
    {
        break;
    }
Delphi-Quellcode:
    if ((NOT CompareMem(PByte(NativeUInt(LpbCurrData) + 0), @SG_ProtoCoded, SizeOf(SG_ProtoCoded))) OR
       ((NOT CompareMem(PByte(NativeUInt(LpbCurrData) + 6), @SG_SignedData, SizeOf(SG_SignedData))))) then
           Break;

Grüße
Dalai
  Mit Zitat antworten Zitat
TiGü

Registriert seit: 6. Apr 2011
Ort: Berlin
3.060 Beiträge
 
Delphi 10.4 Sydney
 
#3

AW: Digitale Signaturen (Authenticode) von Dateien auslesen

  Alt 15. Feb 2023, 16:00
Was bedeutet der erste Stern in diesem Aufruf
Code:
cbCurrData = XCH_WORD_LITEND(*(WORD *)(pbCurrData + 2)) + 4;
Was genau wird da übergeben? Ein Pointer auf einen Pointer auf ein Word?
ChatGPT meint dazu:
Zitat:
Was macht der folgende Code?
Code:
#define XCH_WORD_LITEND(num) \
    (WORD)(((((WORD)num) & 0xFF00) >> 8) | ((((WORD)num) & 0x00FF) << 8))

cbCurrData = XCH_WORD_LITEND(*(WORD *)(pbCurrData + 2)) + 4;

Die Code-Zeile liest ein 16-Bit-Wort aus dem Speicher, das an der Adresse pbCurrData+2 liegt. Dabei wird angenommen, dass die Byte-Reihenfolge des Worts in umgekehrter Reihenfolge (Little Endian) vorliegt. Das ausgelesene Wort wird dann in ein neues 16-Bit-Wort geschrieben, dessen Byte-Reihenfolge umgekehrt ist (Big Endian). Das Ergebnis wird in die Variable cbCurrData geschrieben, nachdem 4 addiert wurde.

Die Funktion XCH_WORD_LITEND dient dazu, die Byte-Reihenfolge von 16-Bit-Wörtern in umgekehrter Reihenfolge (Little Endian) in die in der Regel verwendete Reihenfolge (Big Endian) umzuwandeln, damit die Wörter auf Systemen mit unterschiedlichen Byte-Reihenfolgen korrekt interpretiert werden können.
  Mit Zitat antworten Zitat
Benutzerbild von Dalai
Dalai

Registriert seit: 9. Apr 2006
1.680 Beiträge
 
Delphi 5 Professional
 
#4

AW: Digitale Signaturen (Authenticode) von Dateien auslesen

  Alt 16. Feb 2023, 14:52
Danke, nach längerm Rumprobieren konnte ich mithilfe dieser Beschreibung meinen Code funktionierend bekommen. Es können nun alle Signaturen gelesen werden.

Leider ist das Ergebnis weiterhin inkonsistent, d.h. ich erhalte nicht immer dasselbe Ergebnis nach Neuaufbau der TSignatureContainerList (ist eine TObjectList von TSignatureContainer). Beeinflusst wird das Ergebnis auch durch Aufrufe von OutputDebugString - vor allem solche, die FFileName ausgeben. Aber auch nur mit dem Standard-Speichermanager von Delphi 5. Nutze ich stattdessen FastMM, ist das Ergebnis konsistent und die Calls von OutputDebugString spielen keine Rolle.

Durch die Debug-Ausgaben konnte ich ermitteln, dass das erste CompareMem fehlschlägt. Daher habe ich den Verdacht, dass die Zeigerschieberei irgendetwas kaputtmacht. Zugriffsverletzungen habe ich aber keine, soweit ich sehen kann.

Nachfolgend die komplette Funktion:
Delphi-Quellcode:
procedure TSignatureContainer.FindAllEmbeddedSignatures(const ASignerInfo: PCMSG_SIGNER_INFO; const ACertStore: HCERTSTORE; const AMaxSize: DWORD);

    function BigEndianToLittleEndian(const A: Word): Word;
    begin
        Result:= Word(((A AND $FF00) shr 8) OR ((A AND $00FF) shl 8));
    end;

    function _8ByteAlign(const offset, base: NativeUInt): NativeUInt;
    begin
        Result:= ((offset + base + 7) AND $FFFFFFF8) - (base AND $FFFFFFF8);
    end;


const
  SG_ProtoCoded: array[0..1] of Byte = ($30, $82);
  SG_SignedData: array[0..8] of Byte = ($2a, $86, $48, $86, $f7, $0d, $01, $07, $02);
var i: integer;

    { Nested signatures }
    LUnauthAttr: PCRYPT_ATTRIBUTE;
    LNestedMsg: HCRYPTMSG;
    LNestedSignerInfo: PCMSG_SIGNER_INFO;
    LNestedSignerInfoSize: DWORD;
    LNestedMsgCertStore: HCERTSTORE;

    LpbCurrData, LpbNextData: PByte;
    LcbCurrData: DWORD;
    Lret: BOOL;
    LMaxAddress: PByte;
begin
    LUnauthAttr:= ASignerInfo.UnauthAttrs.rgAttr;
    AddSignatureFromSignerInfo(ASignerInfo, ACertStore);

    LMaxAddress:= PByte(ASignerInfo);
    Inc(LMaxAddress, AMaxSize);

    // FFileName: WideString;
    OutputDebugString(PChar(String(FFileName)));

    for i:= 1 to ASignerInfo.UnauthAttrs.cAttr do begin
        if AnsiString(LUnauthAttr.pszObjId) = szOID_NESTED_SIGNATURE then begin
            FNestedSignature:= True;

            LpbCurrData:= LUnauthAttr.rgValue.pbData;

            while (NativeUInt(LpbCurrData) > NativeUInt(ASignerInfo)) AND
                  (NativeUInt(LpbCurrData) < NativeUInt(LMaxAddress)) do begin
// OutputDebugString('Loop');
                if NOT CompareMem(Pointer(NativeUInt(LpbCurrData) + 0), @SG_ProtoCoded, SizeOf(SG_ProtoCoded)) then
                    Break;
// OutputDebugString('Loop2');
                if NOT CompareMem(Pointer(NativeUInt(LpbCurrData) + 6), @SG_SignedData, SizeOf(SG_SignedData)) then
                    Break;

                LNestedMsg:= CryptMsgOpenToDecode(X509_OR_PKCS7_ENCODING, 0, 0, 0, nil, nil);
                if NOT Assigned(LNestedMsg) then
                    Exit;
                try
// LcbCurrData:= Swap(PWord(NativeUInt(LpbCurrData)+2)^) + 4;
                    LcbCurrData:= BigEndianToLittleEndian(PWord(NativeUInt(LpbCurrData)+2)^) + 4;
                    LpbNextData:= LpbCurrData;
                    Inc(LpbNextData, _8ByteAlign(LcbCurrData, ULONG_PTR(LpbCurrData)));

                    Lret:= CryptMsgUpdate(LNestedMsg, LpbCurrData, LcbCurrData, True);
                    LpbCurrData:= LpbNextData;
                    if NOT Lret then
                        Continue;

                    if NOT CryptMsgGetParam(LNestedMsg, CMSG_SIGNER_INFO_PARAM, 0, nil, LNestedSignerInfoSize) then
                        Exit;
                    GetMem(LNestedSignerInfo, LNestedSignerInfoSize);
                    try
                        if NOT CryptMsgGetParam(LNestedMsg, CMSG_SIGNER_INFO_PARAM, 0, LNestedSignerInfo, LNestedSignerInfoSize) then
                            Exit;
                        LNestedMsgCertStore:= CertOpenStore(CERT_STORE_PROV_MSG, X509_OR_PKCS7_ENCODING, 0, 0, LNestedMsg);
                        try
                            AddSignatureFromSignerInfo(LNestedSignerInfo, LNestedMsgCertStore);
                        finally
                            CertCloseStore(LNestedMsgCertStore, 0);
                        end;
                    finally
                        FreeMem(LNestedSignerInfo);
                    end;
                finally
                    CryptMsgClose(LNestedMsg);
                end;
            end;
            Break;
        end;
        Inc(LUnauthAttr);
    end;
end;
Grüße
Dalai
  Mit Zitat antworten Zitat
Benutzerbild von Dalai
Dalai

Registriert seit: 9. Apr 2006
1.680 Beiträge
 
Delphi 5 Professional
 
#5

AW: Digitale Signaturen (Authenticode) von Dateien auslesen

  Alt 20. Feb 2023, 16:17
Hallo nochmal.

Die Sache hat mir keine Ruhe gelassen und deshalb hab ich die vergangenen Tage genauer untersucht, was da eigentlich passiert. Ich liebe nicht zuverlässig reproduzierbare Fehler ...

Nach einiger Zeit des Debugging hab ich die Funktion _8ByteAlign als Problemverursacher ausgemacht. Diese gibt bei bestimmten Eingabedaten einen um 8 Byte höheren Wert zurück als sie sollte. Die Folge ist, dass die while-Schleife vorzeitig verlassen wird - es werden deshalb nicht alle Signaturen gefunden.

Basierend auf dem Debugging des Produktivcodes hab ich ein kleines Testprogramm erstellt, das die Problematik verdeutlicht. Die hier benutzten Adressen und Offsets sind von den Debug-Sessions übernommen.
Delphi-Quellcode:
program Project1;
{$APPTYPE CONSOLE}
uses
  sysutils,
  Windows;

function _8ByteAlign(const offset, base: DWORD): DWORD;
begin
    WriteLn((base + offset + 7));
    WriteLn((base + offset + 7) AND $FFFFFFF8);
    WriteLn(((base + 7) AND $FFFFFFF8));
    Result:= ((offset + base + 7) AND $FFFFFFF8) - (base AND $FFFFFFF8);
end;

var
  base, offset: DWORD;
  i: integer;

begin
  for i:= 0 to 8 do begin
      base:= $431E814 + i;
      offset:= 9293;
      WriteLn;
      WriteLn(i);
      WriteLn(Format('Base: 0x%.8x (%u), Offset: %d', [base, base, offset]));
      WriteLn(_8ByteAlign(offset, base));
  end;
  ReadLn;
end.
Ausgabe:
Code:
0
Base: 0x0431E814 (70379540), Offset: 9293
70388840
70388840
70379536
9304

1
Base: 0x0431E815 (70379541), Offset: 9293
70388841
70388840
70379536
9304

2
Base: 0x0431E816 (70379542), Offset: 9293
70388842
70388840
70379536
9304

3
Base: 0x0431E817 (70379543), Offset: 9293
70388843
70388840
70379536
9304

4
Base: 0x0431E818 (70379544), Offset: 9293
70388844
70388840
70379544
9296

5
Base: 0x0431E819 (70379545), Offset: 9293
70388845
70388840
70379544
9296

6
Base: 0x0431E81A (70379546), Offset: 9293
70388846
70388840
70379544
9296

7
Base: 0x0431E81B (70379547), Offset: 9293
70388847
70388840
70379544
9296

8
Base: 0x0431E81C (70379548), Offset: 9293
70388848
70388848
70379544
9304
Das nächstgrößere Vielfache von 8 ist 9296, nicht 9304. Mit anderen Adressen und Offsets ist die Rückgabe der Funktion unter Umständen nicht so häufig falsch. Eindeutiges Anzeichen für unzuverlässigen Code.

Nach dieser Entdeckung hab ich mich gefragt, was eigentlich die Basisadresse in der Funktion verloren hat, wozu die dienen soll. Es fiel mir keine sinnvolle Antwort ein. Ich habe die Vermutung, dass die Funktion bzw. das Makro im C++ ursprünglich die absolute Adresse berechnet hat. Hat jemand eine (andere) Idee, wozu die Basisadresse dienen könnte?

Sobald ich die Funktion wie folgt ändere, funktioniert der Code überall zuverlässig, egal mit welchem Delphi-Speichermanager.
Delphi-Quellcode:
function _8ByteAlign(const offset: DWORD): DWORD;
begin
    Result:= (offset + 7) AND $FFFFFFF8;
end;
Die entsprechende Änderung hab ich auch im C++-Code gemacht und getestet und das Ergebnis ist wie vorher (Code funktioniert).


Fazit: Viel Detektivarbeit wegen weniger kleiner Fehler. Aber immerhin bin ich nun ziemlich sicher, dass der Code jetzt zuverlässig und (hoffentlich) auch stabil ist .

Grüße
Dalai

Geändert von Dalai (21. Feb 2023 um 15:17 Uhr)
  Mit Zitat antworten Zitat
Antwort Antwort


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 12:30 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