|
Antwort |
Registriert seit: 9. Apr 2006 1.682 Beiträge Delphi 5 Professional |
#1
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
Delphi-Quellcode:
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.
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; 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 |
Zitat |
Registriert seit: 9. Apr 2006 1.682 Beiträge Delphi 5 Professional |
#2
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:
habe ich übersetzt mit
CONST UCHAR SG_ProtoCoded[] = {
0x30, 0x82, }; CONST UCHAR SG_SignedData[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x07, 0x02, };
Delphi-Quellcode:
Ist das korrekt? Was bedeuten die abschließenden Kommas im C++?
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); 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:
Was bedeutet der erste Stern in diesem Aufruf
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)));
Code:
Was genau wird da übergeben? Ein Pointer auf einen Pointer auf ein Word?
cbCurrData = XCH_WORD_LITEND(*(WORD *)(pbCurrData + 2)) + 4;
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 |
Zitat |
Registriert seit: 6. Apr 2011 Ort: Berlin 3.070 Beiträge Delphi 10.4 Sydney |
#3
Was bedeutet der erste Stern in diesem Aufruf
Code:
Was genau wird da übergeben? Ein Pointer auf einen Pointer auf ein Word?
cbCurrData = XCH_WORD_LITEND(*(WORD *)(pbCurrData + 2)) + 4;
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. |
Zitat |
Registriert seit: 9. Apr 2006 1.682 Beiträge Delphi 5 Professional |
#4
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:
Grüße
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; Dalai |
Zitat |
Registriert seit: 9. Apr 2006 1.682 Beiträge Delphi 5 Professional |
#5
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:
Ausgabe:
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.
Code:
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.
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 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:
Die entsprechende Änderung hab ich auch im C++-Code gemacht und getestet und das Ergebnis ist wie vorher (Code funktioniert).
function _8ByteAlign(const offset: DWORD): DWORD;
begin Result:= (offset + 7) AND $FFFFFFF8; end; 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 16:17 Uhr) |
Zitat |
Ansicht |
Linear-Darstellung |
Zur Hybrid-Darstellung wechseln |
Zur Baum-Darstellung wechseln |
ForumregelnEs 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
|
|
Nützliche Links |
Heutige Beiträge |
Sitemap |
Suchen |
Code-Library |
Wer ist online |
Alle Foren als gelesen markieren |
Gehe zu... |
LinkBack |
LinkBack URL |
About LinkBacks |