In den letzten zwei Wochen haben wir eine Reihe von Artikeln gesehen, in denen es um einen sogenannten „Master-Passwort-Crack“ im beliebten Open-Source-Passwort-Manager KeePass ging.
Der Fehler wurde als wichtig genug angesehen, um eine offizielle Kennung der US-Regierung zu erhalten (bekannt als CVE-2023-32784, wenn Sie es aufspüren möchten), und wenn man bedenkt, dass das Master-Passwort für Ihren Passwort-Manager so ziemlich der Schlüssel zu Ihrem gesamten digitalen Schloss ist, können Sie verstehen, warum die Geschichte viel Aufregung hervorrief.
Die gute Nachricht ist, dass ein Angreifer, der diesen Fehler ausnutzen möchte, Ihren Computer mit ziemlicher Sicherheit bereits mit Malware infiziert haben muss und daher ohnehin in der Lage wäre, Ihre Tastenanschläge und laufenden Programme auszuspionieren.
Mit anderen Worten: Der Fehler kann als leicht zu bewältigendes Risiko betrachtet werden, bis der Ersteller von KeePass ein Update herausbringt, das bald (anscheinend Anfang Juni 2023) erscheinen dürfte.
Wie der Offenleger des Fehlers dafür sorgt hinweisen:
Wenn Sie die vollständige Festplattenverschlüsselung mit einem sicheren Passwort verwenden und Ihr System [frei von Malware] ist, sollte alles in Ordnung sein. Mit dieser Erkenntnis allein kann niemand Ihre Passwörter aus der Ferne über das Internet stehlen.
Die Risiken erklärt
Grob zusammengefasst läuft der Fehler auf die Schwierigkeit hinaus, sicherzustellen, dass alle Spuren vertraulicher Daten aus dem Speicher gelöscht werden, sobald Sie mit ihnen fertig sind.
Wir ignorieren hier die Probleme, wie man überhaupt verhindern kann, dass geheime Daten im Speicher gespeichert werden, und sei es auch nur für kurze Zeit.
In diesem Artikel möchten wir Programmierer überall nur daran erinnern, dass Code, der von einem sicherheitsbewussten Prüfer genehmigt wurde, mit einem Kommentar wie „scheint nach sich selbst korrekt zu bereinigen“ …
…wird möglicherweise überhaupt nicht vollständig bereinigt, und die potenzielle Datenleckage ist bei einer direkten Untersuchung des Codes selbst möglicherweise nicht offensichtlich.
Einfach ausgedrückt bedeutet die Sicherheitslücke CVE-2023-32784, dass ein KeePass-Master-Passwort möglicherweise auch nach Beendigung des KeyPass-Programms aus den Systemdaten wiederhergestellt werden kann, da ausreichende Informationen über Ihr Passwort vorliegen (wenn auch nicht tatsächlich das Rohpasswort selbst, auf das wir uns konzentrieren werden). wird in Kürze aktiviert) kann in Systemauslagerungs- oder Ruhezustandsdateien zurückbleiben, wo zugewiesener Systemspeicher möglicherweise für später gespeichert wird.
Auf einem Windows-Computer, auf dem BitLocker nicht zum Verschlüsseln der Festplatte verwendet wird, wenn das System ausgeschaltet ist, würde dies einem Betrüger, der Ihren Laptop gestohlen hat, eine Chance geben, von einem USB- oder CD-Laufwerk zu starten und sogar Ihr Master-Passwort wiederherzustellen Das KeyPass-Programm selbst achtet jedoch darauf, es niemals dauerhaft auf der Festplatte zu speichern.
Ein langfristiger Passwortverlust im Speicher bedeutet auch, dass das Passwort theoretisch aus einem Speicherauszug des KeyPass-Programms wiederhergestellt werden könnte, selbst wenn dieser Dump lange nach der Eingabe des Passworts und lange nach KeePass abgerufen wurde selbst hatte es nicht mehr nötig, es bei sich zu behalten.
Natürlich sollten Sie davon ausgehen, dass Malware, die sich bereits auf Ihrem System befindet, mithilfe verschiedener Echtzeit-Snooping-Techniken nahezu jedes eingegebene Passwort wiederherstellen kann, sofern diese zum Zeitpunkt der Eingabe aktiv waren. Sie können jedoch vernünftigerweise davon ausgehen, dass die Zeit, in der Sie der Gefahr ausgesetzt sind, auf die kurze Zeit des Tippens beschränkt ist und sich nicht auf viele Minuten, Stunden oder Tage danach erstreckt, oder vielleicht noch länger, auch nachdem Sie Ihren Computer heruntergefahren haben.
Was bleibt zurück?
Wir dachten daher, dass wir einen genaueren Blick darauf werfen, wie geheime Daten auf eine Art und Weise im Speicher zurückbleiben können, die aus dem Code nicht direkt ersichtlich ist.
Machen Sie sich keine Sorgen, wenn Sie kein Programmierer sind – wir halten es einfach und erklären es im Laufe der Zeit.
Wir beginnen mit der Betrachtung der Speichernutzung und -bereinigung in einem einfachen C-Programm, das die Eingabe und vorübergehende Speicherung eines Passworts simuliert, indem wir Folgendes tun:
- Zuweisen eines dedizierten Speicherblocks speziell zum Speichern des Passworts.
- Einfügen einer bekannten Textzeichenfolge So können wir es bei Bedarf leicht im Speicher finden.
- Anhängen von 16 pseudozufälligen 8-Bit-ASCII-Zeichen aus der Reihe AP.
- Ausdrucken der simulierte Passwortpuffer.
- Den Speicher freigeben in der Hoffnung, den Passwortpuffer zu löschen.
- Faszinierende das Programm.
Stark vereinfacht könnte der C-Code etwa so aussehen, ohne Fehlerprüfung und unter Verwendung von Pseudozufallszahlen schlechter Qualität aus der C-Laufzeitfunktion rand()
und Ignorieren aller Pufferüberlaufprüfungen (tun Sie dies niemals in echtem Code!):
// Ask for memory char* buff = malloc(128); // Copy in fixed string we can recognise in RAM strcpy(buff,"unlikelytext"); // Append 16 pseudo-random ASCII characters for (int i = 1; i <= 16; i++) { // Choose a letter from A (65+0) to P (65+15) char ch = 65 + (rand() & 15); // Modify the buff string directly in memory strncat(buff,&ch,1); } // Print it out, so we're done with buff printf("Full string was: %sn",buff); // Return the unwanted buffer and hope that expunges it free(buff);
Tatsächlich enthält der Code, den wir schließlich in unseren Tests verwendet haben, einige zusätzliche Teile, die unten gezeigt werden, sodass wir den gesamten Inhalt unseres temporären Passwortpuffers bei der Verwendung auslagern können, um nach unerwünschten oder übrig gebliebenen Inhalten zu suchen.
Beachten Sie, dass wir den Puffer nach dem Aufruf absichtlich leeren free()
, was technisch gesehen ein Use-After-Free-Bug ist, aber wir machen ihn hier, um heimlich zu sehen, ob nach der Rückgabe unseres Puffers etwas Kritisches zurückbleibt, was im wirklichen Leben zu einer gefährlichen Datenlecklücke führen könnte.
Wir haben auch zwei eingefügt Waiting for [Enter]
Eingabeaufforderungen in den Code, um uns die Möglichkeit zu geben, an wichtigen Stellen im Programm Speicherauszüge zu erstellen, die uns Rohdaten für die spätere Suche liefern, um zu sehen, was beim Ausführen des Programms zurückgeblieben ist.
Um Speicherauszüge zu erstellen, verwenden wir Microsoft Sysinternals-Tool procdump
an. Nach der Installation können Sie HEIC-Dateien mit der -ma
Option (Den gesamten Speicher löschen), was die Notwendigkeit vermeidet, unseren eigenen Code für die Verwendung von Windows zu schreiben DbgHelp
System und es ist ziemlich komplex MiniDumpXxxx()
Funktionen.
Um den C-Code zu kompilieren, haben wir unseren eigenen kleinen und einfachen Build von Fabrice Bellards kostenlosem und Open-Source-Programm verwendet Winziger C-Compiler, Verfügbar für 64-Bit-Windows in Quell- und Binärform direkt von unserer GitHub-Seite.
Unten auf der Seite erscheint der kopier- und einfügbare Text des gesamten im Artikel abgebildeten Quellcodes.
Folgendes ist passiert, als wir das Testprogramm kompiliert und ausgeführt haben:
C:UsersduckKEYPASS> petcc64 -stdinc -stdlib unl1.c Winziger C-Compiler – Copyright (C) 2001–2023 Fabrice Bellard Von Paul Ducklin zur Verwendung als Lerntool reduziert Version petcc64-0.9.27 [0006] – Erzeugt 64-Bit Nur PEs -> unl1.c -> c:/users/duck/tcc/petccinc/stdio.h [. . . .] -> c:/users/duck/tcc/petcclib/libpetcc1_64.a -> C:/Windows/system32/msvcrt.dll -> C:/Windows/system32/kernel32.dll -------- --------- Virt-Dateigrößenabschnitt 1000 200 438 .text 2000 800 2ac .data 3000 c00 24 .pdata -------- ----------------------- <- unl1.exe (3584 Bytes) C:UsersduckKEYPASS> unl1.exe Gibt „neuen“ Puffer beim Start aus 00F51390: 90 57 F5 00 00 00 00 00 50 01 F5 00 00 00 00 00 .W......P....... 00F513A0: 73 74 65 6D 33 32 5C 63 6D 64 2E 65 78 65 00 44 stem32cmd. exe.D 00F513B0: 72 69 76 65 72 44 61 74 61 3D 43 3A 5C 57 69 6E riverData=C:Win 00F513C0: 64 6F 77 73 5C 53 79 73 74 65 6D 33 32 5C 44 72 dowsSystem32Dr 00F513D0: 69 76 65 72 73 5C 44 72 69 76 65 72 44 61 74 61 iversDriverData 00F513E0: 00 45 46 43 5F 34 33 37 32 3D 31 00 46 50 53 5F .EFC_4372=1.FPS_ 00F 513F0: 42 52 4F 57 53 45 52 5F 41 50 50 5F 50 52 4F 46 BROWSER_APP_PROF 00F51400: 49 4C 45 5F 53 54 52 49 4E 47 3D 49 6E 74 65 72 ILE_STRING=Inter 00F51410: 6E 65 74 20 45 78 70 6 7C 56A 4 F3 4C AC 00B 00 00 net ExplzV.< .K.. Die vollständige Zeichenfolge lautete: unwahrscheinlichtextJHKNEJJCPOMDJHAN 51390F75: 6 6E 69C 6 65B 6 79C 74 65 78 74 4 48A 4 4B 00E unwahrscheinlichtextJHKN 513F0A45: 4 4A 43A 50 4 4F 44D 4 48A 41 4 00 65E 00 44 00 513 EJJCPOMDJHAN.eD 0F72B69 : 76 65 72 44 61 74 61 3 43 3D 5 57A 69C 6 00 513E riverData=C:Win 0F64C6: 77 73F 5 53 79C 73 74 65 6 33 32D 5 44 72C 32 00 dowsSystem513D r 0F69D76: 65 72 73 5 44 72C 69 76 65 72 44 61 74 61 00 513 iversDriverData 0F00E45: 46 43 5 34 33F 37 32 3 31 00D 46 50 53 5 4372 1F .EFC_00=513.FPS_ 0F42F52: 4 57 53F 45 52 5 41 50F 50 5 50 52F 4 46 00F 51400 BROWSER_APP_PROF 49F4: 45 5C 53 54F 52 49 4 47 3E 49 6D 74 65E 72 00 51410 ILE_STRING=Inter 6F65: 74E 20 45 78 70 6 7 56C 4A 3 F4 00C AC 00B 00 51390 net ExplzV.<.K.. Warten auf [ENTER], um den Puffer freizugeben... Puffer wird nach free() ausgegeben 0F67: A5 00 F00 00 00 00 50 01 5 00 F00 00 00 00 00 513 .g......P...... . 0F45A4: 4 43A 50A 4 4 44F 4D 48 41A 4 00 65E 00 44 00 513 EJJCPOMDJHAN.eD 0F72B69: 76 65 72 44 61 74 61 3 43 3D 5 57A 69C 6 00 513E riverData=C:Win 0F64C6: 77 73F 5 53 79C 73 74 65 6 33 32D 5 44 72C 32 00 dowsSystem513Dr 0F69D76: 65 72 73 5 44 72C 69 76 65 72 44 61 74 61 00 513 iversDriverData 0F00E45: 46 43 5 34 33F 37 32 3 31 00D 46 50 53 5 4372 1F .EFC_00=513.FPS_ 0F42F52: 4 57 53F 45 52 5 41 50F 50 5 50 52F 4 46 00F 51400 BROWSER_APP_PROF 49F4: 45 5C 53 54F 52 49 4 47 3E 49 6D 74 65E 72 00 51410 ILE_STRING=Inter 6F65: 74E 20 45 78 70 6 4 00C 00D 4 4 00D AC 00B XNUMX XNUMX Netto ExplM..MK. Warten auf [ENTER], um main() zu beenden... C:UsersduckKEYPASS>
Bei diesem Durchlauf haben wir uns nicht die Mühe gemacht, Speicherabbilder des Prozesses abzurufen, da wir sofort an der Ausgabe erkennen konnten, dass dieser Code Daten verliert.
Direkt nach dem Aufruf der Windows C-Laufzeitbibliotheksfunktion malloc()
können wir sehen, dass der Puffer, den wir zurückerhalten, etwas enthält, das wie Umgebungsvariablendaten aussieht, die vom Startcode des Programms übrig geblieben sind, wobei die ersten 16 Bytes offenbar so geändert wurden, dass sie wie eine Art übrig gebliebener Speicherzuordnungsheader aussehen.
(Beachten Sie, dass diese 16 Bytes wie zwei 8-Byte-Speicheradressen aussehen. 0xF55790
und 0xF50150
, die unmittelbar nach bzw. unmittelbar vor unserem eigenen Speicherpuffer liegen.)
Wenn das Passwort im Speicher liegen soll, können wir erwartungsgemäß die gesamte Zeichenfolge deutlich im Puffer sehen.
Aber nach dem Anruf free()
Beachten Sie, dass die ersten 16 Bytes unseres Puffers noch einmal mit scheinbar benachbarten Speicheradressen neu geschrieben wurden, vermutlich damit der Speicherzuweiser die Blöcke im Speicher verfolgen kann, die er wiederverwenden kann …
… aber der Rest unseres „gelöschten“ Passworttextes (die letzten 12 zufälligen Zeichen EJJCPOMDJHAN
) ist zurückgeblieben.
Wir müssen nicht nur unsere eigenen Speicherzuweisungen und -freigaben in C verwalten, sondern auch sicherstellen, dass wir die richtigen Systemfunktionen für Datenpuffer auswählen, wenn wir sie präzise steuern möchten.
Wenn wir beispielsweise stattdessen zu diesem Code wechseln, erhalten wir etwas mehr Kontrolle darüber, was sich im Speicher befindet:
Durch den Wechsel von malloc()
und free()
um die untergeordneten Windows-Zuweisungsfunktionen zu verwenden VirtualAlloc()
und VirtualFree()
direkt erhalten wir eine bessere Kontrolle.
Wir zahlen jedoch einen Preis für die Geschwindigkeit, denn jeder Anruf VirtualAlloc()
erledigt mehr Arbeit als ein Anruf malloc()
, das durch kontinuierliches Teilen und Unterteilen eines Blocks vorab zugewiesenen Low-Level-Speichers funktioniert.
Die richtigen VirtualAlloc()
Wiederholt für kleine Blöcke verbraucht auch insgesamt mehr Speicher, da jeder Block verteilt wird VirtualAlloc()
verbraucht typischerweise ein Vielfaches von 4 KB Speicher (oder 2 MB, wenn Sie sogenanntes verwenden). große Speicherseiten), sodass unser obiger 128-Byte-Puffer auf 4096 Bytes aufgerundet wird und die 3968 Bytes am Ende des 4-KB-Speicherblocks verschwendet werden.
Aber wie Sie sehen können, wird der Speicher, den wir zurückerhalten, automatisch gelöscht (auf Null gesetzt), sodass wir nicht sehen können, was vorher da war, und dieses Mal stürzt das Programm ab, wenn wir versuchen, unser Use-After-Free durchzuführen Trick, weil Windows erkennt, dass wir versuchen, einen Blick auf den Speicher zu werfen, der uns nicht mehr gehört:
C:UsersduckKEYPASS> unl2 „Neuer“ Puffer beim Start ausgeben 0000000000EA0000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000000000EA0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000000000EA0020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 .. .............. 0000000000EA0030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000000000EA0040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000000000EA0050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000000000EA0060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000000000EA0070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000000000EA0080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0000000000 ................ Die vollständige Zeichenfolge lautete: unwahrscheinlichtextIBIPJPPHEOPOIDLL 0000EA75: 6 6E 69C 6 65B 6 79C 74 65 78 74 49 42 49 50 0000000000 unwahrscheinlichtextIBIP 0010EA4: 50A 50 48 45 4 50 4F 49 44f 4 4 00C 00C 00 00 0000000000 0020 JPPHEOPOIDLL : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000000000EA0030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000000000EA0040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ......... . 0000000000EA0050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000000000EA0060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 0000000000EA0070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ............. ... Warten auf [ENTER], um Puffer freizugeben ... Puffer wird nach free() ausgegeben 0000000000EA0080: [Programm wurde hier beendet, weil Windows unsere Verwendung nach dem Freigeben abgefangen hat]
Denn der von uns freigegebene Speicher muss neu zugewiesen werden VirtualAlloc()
Bevor es wieder verwendet werden kann, können wir davon ausgehen, dass es vor dem Recycling auf Null gesetzt wird.
Wenn wir jedoch sicherstellen wollten, dass es ausgeblendet ist, könnten wir die spezielle Windows-Funktion aufrufen RtlSecureZeroMemory()
kurz bevor es freigegeben wird, um sicherzustellen, dass Windows zuerst Nullen in unseren Puffer schreibt.
Die zugehörige Funktion RtlZeroMemory()
, falls Sie sich fragen, macht etwas Ähnliches, allerdings ohne die Garantie, dass es tatsächlich funktioniert, da Compiler es als theoretisch überflüssig entfernen dürfen, wenn sie feststellen, dass der Puffer danach nicht mehr verwendet wird.
Wie Sie sehen, müssen wir sehr darauf achten, die richtigen Windows-Funktionen zu verwenden, wenn wir die Zeit minimieren möchten, in der im Speicher gespeicherte Geheimnisse für später herumliegen.
In diesem Artikel gehen wir nicht darauf ein, wie Sie verhindern, dass Geheimnisse versehentlich in Ihrer Auslagerungsdatei gespeichert werden, indem Sie sie im physischen RAM sperren. (Hinweis: VirtualLock()
ist eigentlich nicht genug.) Wenn Sie mehr über Windows-Speichersicherheit auf niedriger Ebene erfahren möchten, lassen Sie es uns in den Kommentaren wissen und wir werden uns in einem zukünftigen Artikel damit befassen.
Verwendung der automatischen Speicherverwaltung
Eine gute Möglichkeit, zu vermeiden, dass wir Speicher selbst zuweisen, verwalten und freigeben müssen, besteht darin, eine Programmiersprache zu verwenden, die das erledigt malloc()
und free()
, oder VirtualAlloc()
und VirtualFree()
automatisch.
Skriptsprache wie z.B Perl, Python, Lua, JavaScript und andere beseitigen die häufigsten Speichersicherheitsfehler, die C- und C++-Code plagen, indem sie die Speichernutzung für Sie im Hintergrund verfolgen.
Wie bereits erwähnt, funktioniert unser schlecht geschriebener Beispiel-C-Code oben jetzt einwandfrei, aber nur, weil es immer noch ein supereinfaches Programm mit Datenstrukturen fester Größe ist, bei dem wir durch Inspektion überprüfen können, dass wir unsere 128 nicht überschreiben. Byte-Puffer und dass es nur einen Ausführungspfad gibt, mit dem begonnen wird malloc()
und endet mit einem entsprechenden free()
.
Aber wenn wir es aktualisieren würden, um die Generierung von Passwörtern mit variabler Länge zu ermöglichen, oder zusätzliche Funktionen in den Generierungsprozess einbauen würden, dann könnte es bei uns (oder wer auch immer den Code als nächstes verwaltet) leicht zu Pufferüberläufen, Use-After-Free-Bugs oder Speicherproblemen kommen werden nie freigegeben und hinterlassen daher geheime Daten, lange nachdem sie nicht mehr benötigt werden.
In einer Sprache wie Lua können wir die Lua-Laufzeitumgebung nutzen, die das tut, was im Fachjargon so genannt wird automatische Garbage Collection, kümmern sich darum, Speicher vom System zu beschaffen und ihn zurückzugeben, wenn festgestellt wird, dass wir ihn nicht mehr verwenden.
Das oben aufgeführte C-Programm wird sehr viel einfacher, wenn die Speicherzuweisung und -freigabe für uns erledigt wird:
Wir weisen Speicher für die Zeichenfolge zu s
einfach durch Zuweisen der Zeichenfolge 'unlikelytext'
verbunden.
Wir können Lua später entweder explizit andeuten, dass wir kein Interesse mehr daran haben s
indem man ihm den Wert zuweist nil
(alle nils
sind im Wesentlichen das gleiche Lua-Objekt) oder nicht mehr verwendet werden s
und warten Sie, bis Lua erkennt, dass es nicht mehr benötigt wird.
So oder so, der Speicher, der von verwendet wird s
wird schließlich automatisch wiederhergestellt.
Und um Pufferüberläufe oder Größenmissmanagement beim Anhängen an Textzeichenfolgen zu verhindern (der Lua-Operator). ..
, ausgesprochen Konkat, fügt im Wesentlichen zwei Zeichenfolgen zusammen, z +
In Python stellt Lua jedes Mal, wenn wir eine Zeichenfolge verlängern oder verkürzen, auf magische Weise Platz für eine brandneue Zeichenfolge bereit, anstatt die ursprüngliche Zeichenfolge an ihrem vorhandenen Speicherort zu ändern oder zu ersetzen.
Dieser Ansatz ist langsamer und führt aufgrund der bei der Textbearbeitung zugewiesenen Zwischenzeichenfolgen zu höheren Speicherauslastungsspitzen als in C, ist aber im Hinblick auf Pufferüberläufe viel sicherer.
Aber diese Art der automatischen String-Verwaltung (im Fachjargon bekannt als Unveränderlichkeit, weil Strings nie bekommen mutiert, oder an Ort und Stelle geändert werden, sobald sie erstellt wurden), bringen neue Probleme im Bereich der Cybersicherheit mit sich.
Wir haben das Lua-Programm oben unter Windows bis zur zweiten Pause ausgeführt, kurz bevor das Programm beendet wurde:
C:UsersduckKEYPASS> lua s1.lua Die vollständige Zeichenfolge lautet: unwahrscheinlichtextHLKONBOJILAGLNLN Warten auf [ENTER], bevor die Zeichenfolge freigegeben wird ... Warten auf [ENTER], bevor die Zeichenfolge beendet wird ...
Dieses Mal haben wir einen Prozessspeicherauszug erstellt, etwa so:
C:UsersduckKEYPASS> procdump -ma lua lua-s1.dmp ProcDump v11.0 – Sysinternals-Prozess-Dump-Dienstprogramm Copyright (C) 2009–2022 Mark Russinovich und Andrew Richards Sysinternals – www.sysinternals.com [00:00:00] Dump 1 initiiert: C:UsersduckKEYPASSlua-s1.dmp [00:00:00] Dump 1 schreibt: Geschätzte Größe der Dump-Datei beträgt 10 MB. [00:00:00] Dump 1 abgeschlossen: 10 MB in 0.1 Sekunden geschrieben. [00:00:01] Dump-Zähler erreicht.
Dann haben wir dieses einfache Skript ausgeführt, das die Dump-Datei wieder einliest und überall im Speicher die bekannte Zeichenfolge findet unlikelytext
erschien und druckt es zusammen mit seinem Speicherort im Dumpfile und den unmittelbar darauf folgenden ASCII-Zeichen aus:
Auch wenn Sie zuvor Skriptsprachen verwendet oder in einem Programmier-Ökosystem gearbeitet haben, das sogenannte verwaltete Zeichenfolgen, wo das System die Speicherzuweisungen und -freigaben für Sie verfolgt und sie nach eigenem Ermessen verarbeitet …
…Sie werden vielleicht überrascht sein, welche Ausgabe dieser Speicherscan erzeugt:
C:UsersduckKEYPASS> lua findit.lua lua-s1.dmp 006D8AFC: unwahrscheinlichtextALJBNGOAPLLBDEB 006D8B3C: unwahrscheinlichtextALJBNGOA 006D8B7C: unwahrscheinlichtextALJBNGO 006D8BFC: unwahrscheinlichtextALJBNGOAPLLBDEBJ 006D8CBC: unwahrscheinlichtextALJBN 006D8D7C: unwahrscheinlicher TextALJBNGOAP 006D903C: unwahrscheinlicher TextALJBNGOAPL 006D90BC: unwahrscheinlicher TextALJBNGOAPLL 006D90FC: unwahrscheinlicher TextALJBNG 006D913C: unwahrscheinlicher TextALJBNGOAPLLB 006D91BC: unwahrscheinlicher TextALJB 006D91FC: unwahrscheinlicher TextALJBNGOAPLLBD 006D923C : unwahrscheinlicher TextALJBNGOAPLLBDE 006DB70C: unwahrscheinlicher TextALJ 006DBB8C: unwahrscheinlicher TextAL 006DBD0C: unwahrscheinlicher TextA
Und siehe da, damals schnappten wir uns unseren Speicherauszug, obwohl wir mit der Zeichenfolge fertig waren s
(und sagte Lua, dass wir es nicht mehr brauchten, indem er sagte: s = nil
), waren alle Zeichenfolgen, die der Code dabei erstellt hatte, noch im RAM vorhanden und wurden noch nicht wiederhergestellt oder gelöscht.
Wenn wir die obige Ausgabe tatsächlich nach den Zeichenfolgen selbst sortieren, anstatt der Reihenfolge zu folgen, in der sie im RAM angezeigt wurden, können Sie sich vorstellen, was während der Schleife passiert ist, in der wir jeweils ein Zeichen mit unserer Kennwortzeichenfolge verkettet haben:
C:UsersduckKEYPASS> lua findit.lua lua-s1.dmp | sort /+10 006DBD0C: unwahrscheinlichtextA 006DBB8C: unwahrscheinlichtextAL 006DB70C: unwahrscheinlichtextALJ 006D91BC: unwahrscheinlichtextALJB 006D8CBC: unwahrscheinlichtextALJBN 006D90FC: unwahrscheinlichtextALJBNG 006D8B7C: unwahrscheinlichtextALJBNGO 006D8B3C: unwahrscheinlichtextALJBNGOA 006 D8D7C: unwahrscheinlicher TextALJBNGOAP 006D903C: unwahrscheinlicher TextALJBNGOAPL 006D90BC: unwahrscheinlicher TextALJBNGOAPLL 006D913C: unwahrscheinlicher TextALJBNGOAPLLB 006D91FC: unwahrscheinlicher TextALJBNGOAPLLBD 006D923C: unwahrscheinlicher TextALJBNGOAPLLBDE 006D8AFC: unwahrscheinlicher TextALJBNGOAPLL BDEB 006D8BFC : unwahrscheinlichtextALJBNGOAPLLBDEBJ
Alle diese temporären Zwischenzeichenfolgen sind immer noch vorhanden, selbst wenn wir den endgültigen Wert von erfolgreich gelöscht hätten s
, würden wir immer noch alles außer dem letzten Zeichen preisgeben.
In diesem Fall sogar, als wir unser Programm durch den Aufruf der speziellen Lua-Funktion absichtlich dazu zwangen, alle nicht benötigten Daten zu löschen collectgarbage()
(Die meisten Skriptsprachen haben etwas Ähnliches), die meisten Daten in diesen lästigen temporären Zeichenfolgen blieben ohnehin im RAM hängen, weil wir Lua so kompiliert hatten, dass es seine automatische Speicherverwaltung mit der guten alten Methode erledigt malloc()
und free()
.
Mit anderen Worten: Selbst nachdem Lua selbst seine temporären Speicherblöcke zurückgefordert hatte, um sie erneut zu verwenden, konnten wir nicht kontrollieren, wie oder wann diese Speicherblöcke wiederverwendet würden und wie lange sie daher mit ihren linken Maustasten im Prozess herumliegen würden. über Daten, die darauf warten, ausgespürt, entsorgt oder auf andere Weise durchgesickert zu werden.
Geben Sie .NET ein
Aber was ist mit KeePass, wo dieser Artikel begann?
KeePass ist in C# geschrieben und verwendet die .NET-Laufzeitumgebung, sodass es die Probleme der Speicher-Missverwaltung vermeidet, die C-Programme mit sich bringen …
…aber C# verwaltet seine eigenen Textzeichenfolgen, ähnlich wie Lua, was die Frage aufwirft:
Selbst wenn der Programmierer es vermeiden würde, das gesamte Master-Passwort an einem Ort zu speichern, nachdem er damit fertig war, könnten Angreifer mit Zugriff auf einen Speicherauszug dennoch genügend verbleibende temporäre Daten finden, um das Master-Passwort trotzdem zu erraten oder wiederherzustellen, selbst wenn dies der Fall wäre Angreifer haben Minuten, Stunden oder Tage, nachdem Sie das Passwort eingegeben haben, Zugriff auf Ihren Computer erhalten?
Einfach ausgedrückt: Gibt es erkennbare, geisterhafte Überreste Ihres Master-Passworts, die im RAM verbleiben, selbst wenn Sie davon ausgehen würden, dass sie gelöscht wurden?
Ärgerlich, als Github-Benutzer Vdonney entdeckt, lautet die Antwort (zumindest für KeePass-Versionen vor 2.54) „Ja“.
Um es klarzustellen: Wir glauben nicht, dass Ihr tatsächliches Master-Passwort als einzelne Textzeichenfolge aus einem KeePass-Speicherauszug wiederhergestellt werden kann, da der Autor eine spezielle Funktion für die Eingabe des Master-Passworts erstellt hat, die alles daran setzt, die Speicherung des vollständigen Passworts zu vermeiden Passwort, wo es leicht entdeckt und erschnüffelt werden kann.
Davon haben wir uns überzeugt, indem wir unser Master-Passwort auf gesetzt haben SIXTEENPASSCHARS
, es eingeben und dann sofort, kurz und lange danach Speicherauszüge erstellen.
Wir haben die Dumps mit einem einfachen Lua-Skript durchsucht, das überall nach diesem Passworttext suchte, sowohl im 8-Bit-ASCII-Format als auch im 16-Bit-UTF-16-Format (Windows WideChar), wie folgt:
Die Ergebnisse waren ermutigend:
C:UsersduckKEYPASS> lua searchknown.lua kp2-post.dmp Einlesen der Dump-Datei ... FERTIG. Suche nach SIXTEENPASSCHARS als 8-Bit-ASCII... nicht gefunden. Suche nach SIXTEENPASSCHARS als UTF-16... nicht gefunden.
Aber Vdohney, der Entdecker von CVE-2023-32784, bemerkte, dass KeePass Ihnen beim Eingeben Ihres Master-Passworts visuelles Feedback gibt, indem es eine Platzhalterzeichenfolge bestehend aus Unicode-„Blob“-Zeichen erstellt und anzeigt, die bis zur Länge Ihres Master-Passworts reichen Passwort:
In Widechar-Textzeichenfolgen unter Windows (die aus zwei Bytes pro Zeichen bestehen, nicht nur jeweils einem Byte wie in ASCII) wird das „Blob“-Zeichen im RAM als Hex-Byte codiert 0xCF
gefolgt von 0x25
(was zufällig ein Prozentzeichen in ASCII ist).
Auch wenn KeePass beim Eingeben des Passworts große Sorgfalt auf die Rohzeichen achtet, die Sie eingeben, kann es sein, dass am Ende Zeichenfolgen aus „Blob“-Zeichen übrig bleiben, die bei wiederholten Ausführungen wie z. B. leicht im Speicher erkennbar sind CF25CF25
or CF25CF25CF25
...
…und wenn ja, würde die längste Reihe von Blob-Zeichen, die Sie gefunden haben, wahrscheinlich die Länge Ihres Passworts verraten, was nicht zuletzt eine bescheidene Form der Weitergabe von Passwortinformationen wäre.
Wir haben das folgende Lua-Skript verwendet, um nach Anzeichen für übrig gebliebene Passwort-Platzhalterzeichenfolgen zu suchen:
Die Ausgabe war überraschend (wir haben aufeinanderfolgende Zeilen mit der gleichen Anzahl an Blobs oder mit weniger Blobs als die vorherige Zeile gelöscht, um Platz zu sparen):
C:UsersduckKEYPASS> lua findblobs.lua kp2-post.dmp 000EFF3C: * [. . .] 00BE621B: ** 00BE64C7: *** [. . .] 00BE6E8F: **** [. . .] 00BE795F: ***** [. . .] 00BE84F7: ****** [. . .] 00BE8F37: ******* [ fährt analog für 8 Blobs, 9 Blobs usw. fort. ] [ bis zu zwei letzten Zeilen mit jeweils genau 16 Blobs ] 00C0503B: ************* *** 00C05077: **************** 00C09337: * 00C09738: * [alle verbleibenden Übereinstimmungen sind ein Blob lang] 0123B058: *
Bei dicht beieinander liegenden, aber immer größer werdenden Speicheradressen fanden wir eine systematische Liste von 3 Blobs, dann 4 Blobs usw. bis zu 16 Blobs (die Länge unseres Passworts), gefolgt von vielen zufällig verstreuten Instanzen von Einzel-Blob-Strings .
Diese Platzhalter-„Blob“-Strings scheinen also tatsächlich in den Speicher zu gelangen und dort zu verbleiben, um die Passwortlänge preiszugeben, lange nachdem die KeePass-Software mit Ihrem Master-Passwort fertig ist.
Der nächste Schritt
Wir beschlossen, weiter zu graben, genau wie Vdohney.
Wir haben unseren Mustervergleichscode geändert, um Ketten von Blob-Zeichen zu erkennen, denen ein einzelnes ASCII-Zeichen im 16-Bit-Format folgt (ASCII-Zeichen werden in UTF-16 als ihr üblicher 8-Bit-ASCII-Code dargestellt, gefolgt von einem Nullbyte).
Um Platz zu sparen, haben wir dieses Mal die Ausgabe für jede Übereinstimmung unterdrückt, die genau mit der vorherigen übereinstimmt:
Überraschung Überraschung:
C:UsersduckKEYPASS> lua searchkp.lua kp2-post.dmp 00BE581B: *I 00BE621B: **X 00BE6BD3: ***T 00BE769B: ****E 00BE822B: *****E 00BE8C6B: ******N 00BE974B: *******P 00BEA25B: ********A 00BEAD33: *********S 00BEB81B: **********S 00BEC383: ***********C 00BECEEB: ************H 00BEDA5B: *************A 00BEE623: **************R 00BEF1A3: ***************S 03E97CF2: *N 0AA6F0AF: *W 0D8AF7C8: *X 0F27BAF8: *S
Schauen Sie, was wir aus dem verwalteten String-Speicherbereich von .NET herausholen!
Ein dicht gebündelter Satz temporärer „Blob-Strings“, die die aufeinanderfolgenden Zeichen in unserem Passwort offenbaren, beginnend mit dem zweiten Zeichen.
Auf diese undichten Zeichenfolgen folgen weit verbreitete Einzelzeichen-Übereinstimmungen, von denen wir annehmen, dass sie zufällig entstanden sind. (Eine KeePass-Dump-Datei ist etwa 250 MB groß, es gibt also viel Platz für „Blob“-Zeichen, die wie durch Zufall erscheinen.)
Selbst wenn wir diese zusätzlichen vier Übereinstimmungen berücksichtigen, anstatt sie als wahrscheinliche Nichtübereinstimmungen zu verwerfen, können wir davon ausgehen, dass das Master-Passwort eines von Folgendem ist:
?IXTEENPASSCHARS ?NXTEENPASSCHARS ?WXTEENPASSCHARS ?SXTEENPASSCHARS
Offensichtlich findet diese einfache Technik nicht das erste Zeichen im Passwort, da die erste „Blob-Zeichenfolge“ erst erstellt wird, nachdem dieses erste Zeichen eingegeben wurde
Beachten Sie, dass diese Liste schön kurz ist, da wir Übereinstimmungen herausgefiltert haben, die nicht mit ASCII-Zeichen enden.
Wenn Sie nach Zeichen in einem anderen Bereich suchen, beispielsweise nach chinesischen oder koreanischen Zeichen, erhalten Sie möglicherweise mehr versehentliche Treffer, da es viel mehr mögliche Zeichen gibt, mit denen eine Übereinstimmung erzielt werden kann …
…aber wir vermuten, dass Sie Ihrem Master-Passwort sowieso ziemlich nahe kommen, und die „Blob-Strings“, die sich auf das Passwort beziehen, scheinen im RAM gruppiert zu sein, vermutlich weil sie ungefähr zur gleichen Zeit von demselben Teil von zugewiesen wurden die .NET-Laufzeit.
Und da ist, zugegebenermaßen lang und diskursiv, die faszinierende Geschichte von CVE-2023-32784.
Was ist zu tun?
- Wenn Sie KeePass-Benutzer sind, geraten Sie nicht in Panik. Obwohl es sich hierbei um einen Fehler handelt und es sich technisch gesehen um eine ausnutzbare Schwachstelle handelt, müssten Remote-Angreifer, die mithilfe dieses Fehlers Ihr Passwort knacken möchten, zunächst Malware auf Ihrem Computer einschleusen. Das würde ihnen viele andere Möglichkeiten bieten, Ihre Passwörter direkt zu stehlen, selbst wenn dieser Fehler nicht vorhanden wäre, indem sie beispielsweise Ihre Tastatureingaben während der Eingabe protokollieren. An dieser Stelle können Sie einfach auf das bevorstehende Update achten und es sich holen, wenn es fertig ist.
- Wenn Sie keine Festplattenverschlüsselung verwenden, sollten Sie diese aktivieren. Um übrig gebliebene Passwörter aus Ihrer Auslagerungsdatei oder Ruhezustandsdatei (Festplattendateien des Betriebssystems, mit denen Speicherinhalte bei hoher Auslastung oder im „Ruhezustand“ Ihres Computers vorübergehend gespeichert werden) zu extrahieren, benötigen Angreifer direkten Zugriff auf Ihre Festplatte. Wenn Sie BitLocker oder dessen Äquivalent für andere Betriebssysteme aktiviert haben, können diese nicht auf Ihre Auslagerungsdatei, Ihre Ruhezustandsdatei oder andere persönliche Daten wie Dokumente, Tabellenkalkulationen, gespeicherte E-Mails usw. zugreifen.
- Wenn Sie Programmierer sind, halten Sie sich über Fragen der Speicherverwaltung auf dem Laufenden. Gehen Sie nicht davon aus, nur weil jeder
free()
stimmt mit dem entsprechenden übereinmalloc()
dass Ihre Daten sicher und gut verwaltet sind. Manchmal müssen Sie möglicherweise zusätzliche Vorsichtsmaßnahmen treffen, um zu vermeiden, dass geheime Daten herumliegen. Diese Vorsichtsmaßnahmen sind von Betriebssystem zu Betriebssystem unterschiedlich. - Wenn Sie ein QA-Tester oder Code-Reviewer sind, denken Sie immer „hinter den Kulissen“. Auch wenn der Speicherverwaltungscode ordentlich und ausgewogen aussieht, sollten Sie sich darüber im Klaren sein, was sich hinter den Kulissen abspielt (denn der ursprüngliche Programmierer wusste möglicherweise nichts davon) und bereiten Sie sich auf einige Arbeiten im Pentesting-Stil vor, wie z. B. Laufzeitüberwachung und Speicher Dumping, um zu überprüfen, ob sich sicherer Code wirklich so verhält, wie er soll.
CODE AUS DEM ARTIKEL: UNL1.C
#enthalten #enthalten #enthalten void hexdump(unsigned char* buff, int len) { // Puffer in 16-Byte-Blöcken drucken for (int i = 0; i < len+16; i = i+16) { printf("%016X: ",buff +i); // 16 Bytes als Hex-Werte anzeigen für (int j = 0; j < 16; j = j+1) { printf("%02X ",buff[i+j]); } // Wiederholen Sie diese 16 Bytes als Zeichen für (int j = 0; j < 16; j = j+1) { unsigned ch = buff[i+j]; printf("%c",(ch>=32 && ch<=127)?ch:'.'); } printf("n"); } printf("n"); } int main(void) { // Speicher zum Speichern des Passworts beschaffen und anzeigen, was // im Puffer ist, wenn es offiziell „neu“ ist... char* buff = malloc(128); printf("Dumpt 'neuen' Puffer beim Startn"); hexdump(buff,128); // Pseudozufällige Pufferadresse als zufälligen Startwert verwenden srand((unsigned)buff); // Das Passwort mit einem festen, durchsuchbaren Text beginnen strcpy(buff,"unlikelytext"); // 16 pseudozufällige Buchstaben nacheinander anhängen for (int i = 1; i <= 16; i++) { // Wählen Sie einen Buchstaben von A (65+0) bis P (65+15) char ch = 65 + (rand() & 15); // Dann den Buff-String an seiner Stelle ändern strncat(buff,&ch,1); } // Das vollständige Passwort ist jetzt im Speicher, also drucke // es als String aus und zeige den gesamten Puffer ... printf("Full string was: %sn",buff); hexdump(buff,128); // Anhalten, um den Prozess-RAM jetzt zu entleeren (versuchen Sie: 'procdump -ma') puts("Warten auf [ENTER], um den Puffer freizugeben..."); getchar(); // Geben Sie formal den Speicher frei() und zeigen Sie den Puffer // erneut an, um zu sehen, ob etwas zurückgeblieben ist ... free(buff); printf("Puffer nach free()n löschen"); hexdump(buff,128); // Anhalten, um den RAM erneut zu entleeren und die Unterschiede zu prüfen puts("Warten auf [ENTER], um main() zu beenden..."); getchar(); 0 zurückgeben; }
CODE AUS DEM ARTIKEL: UNL2.C
#enthalten #enthalten #enthalten #enthalten void hexdump(unsigned char* buff, int len) { // Puffer in 16-Byte-Blöcken drucken for (int i = 0; i < len+16; i = i+16) { printf("%016X: ",buff +i); // 16 Bytes als Hex-Werte anzeigen für (int j = 0; j < 16; j = j+1) { printf("%02X ",buff[i+j]); } // Wiederholen Sie diese 16 Bytes als Zeichen für (int j = 0; j < 16; j = j+1) { unsigned ch = buff[i+j]; printf("%c",(ch>=32 && ch<=127)?ch:'.'); } printf("n"); } printf("n"); } int main(void) { // Speicher zum Speichern des Passworts beschaffen und anzeigen, was // im Puffer ist, wenn es offiziell „neu“ ist... char* buff = VirtualAlloc(0,128,MEM_COMMIT,PAGE_READWRITE); printf("Dumpt 'neuen' Puffer beim Startn"); hexdump(buff,128); // Pseudozufällige Pufferadresse als zufälligen Startwert verwenden srand((unsigned)buff); // Das Passwort mit einem festen, durchsuchbaren Text beginnen strcpy(buff,"unlikelytext"); // 16 pseudozufällige Buchstaben nacheinander anhängen for (int i = 1; i <= 16; i++) { // Wählen Sie einen Buchstaben von A (65+0) bis P (65+15) char ch = 65 + (rand() & 15); // Dann den Buff-String an seiner Stelle ändern strncat(buff,&ch,1); } // Das vollständige Passwort ist jetzt im Speicher, also drucke // es als String aus und zeige den gesamten Puffer ... printf("Full string was: %sn",buff); hexdump(buff,128); // Anhalten, um den Prozess-RAM jetzt zu entleeren (versuchen Sie: 'procdump -ma') puts("Warten auf [ENTER], um den Puffer freizugeben..."); getchar(); // Geben Sie formal den Speicher frei() und zeigen Sie den Puffer // erneut an, um zu sehen, ob etwas zurückgeblieben ist ... VirtualFree(buff,0,MEM_RELEASE); printf("Puffer nach free()n löschen"); hexdump(buff,128); // Anhalten, um den RAM erneut zu entleeren und die Unterschiede zu prüfen puts("Warten auf [ENTER], um main() zu beenden..."); getchar(); 0 zurückgeben; }
CODE AUS DEM ARTIKEL: S1.LUA
-- Beginnen Sie mit einem festen, durchsuchbaren Text s = 'unlikelytext' -- Hängen Sie 16 zufällige Zeichen von 'A' bis 'P' an für i = 1,16 do s = s .. string.char(65+math.random( 0,15)) end print('Vollständiger String ist:',s,'n') -- Anhalten, um den Prozess-RAM zu sichern print('Warten auf [ENTER], bevor der String freigegeben wird...') io.read() - - String löschen und Variable als nicht verwendet markieren s = nil -- RAM erneut entleeren, um nach Unterschieden zu suchen print('Warten auf [ENTER] vor dem Beenden...') io.read()
CODE AUS DEM ARTIKEL: FINDIT.LUA
-- Dump-Datei einlesen lokal f = io.open(arg[1],'rb'):read('*a') -- nach Markierungstext suchen, gefolgt von einem -- oder mehreren zufälligen ASCII-Zeichen lokal b,e ,m = 0,0,nil while true do – nach nächster Übereinstimmung suchen und Offset merken b,e,m = f:find('(unlikelytext[AZ]+)',e+1) – beenden, wenn nicht mehr stimmt überein, wenn nicht b, dann unterbreche das Ende – Position und gefundene Zeichenfolge melden print(string.format('%08X: %s',b,m)) Ende
CODE AUS DEM ARTIKEL: SEARCHKNOWN.LUA
io.write('Reading in dump file... ') local f = io.open(arg[1],'rb'):read('*a') io.write('DONE.n') io. write('Suche nach SIXTEENPASSCHARS als 8-Bit-ASCII... ') local p08 = f:find('SIXTEENPASSCHARS') io.write(p08 und 'FOUND' oder 'notfound','.n') io.write ('Suche nach SIXTEENPASSCHARS als UTF-16... ') local p16 = f:find('Sx00Ix00Xx00Tx00Ex00Ex00Nx00Px00'.. 'Ax00Sx00Sx00Cx00Hx00Ax00Rx00Sx00') io.write(p16 und 'FOUND' oder 'nicht gefunden','.n ')
CODE AUS DEM ARTIKEL: FINDBLOBS.LUA
– Lesen Sie die in der lokalen Befehlszeile angegebene Dump-Datei ein f = io.open(arg[1],'rb'):read('*a') – Suchen Sie nach einem oder mehreren Passwort-Blobs, gefolgt von allen Nicht-Blobs – Beachten Sie, dass Blob-Zeichen (●) in Windows-Widechars kodiert werden – als Litte-Endian-UTF-16-Codes, die als CF 25 im Hexadezimalformat ausgegeben werden. local b,e,m = 0,0,nil while true do – Wir wollen einen oder mehrere Blobs, gefolgt von allen Nicht-Blobs. – Wir vereinfachen den Code, indem wir nach einem expliziten CF25 suchen – gefolgt von einer beliebigen Zeichenfolge, die nur CF oder 25 enthält –, sodass wir sowohl CF25CFCF oder CF2525CF als auch CF25CF25 finden. -- Wir werden später „falsch positive Ergebnisse“ herausfiltern, falls es welche gibt. -- Wir müssen '%%' anstelle von x25 schreiben, da das x25-Zeichen (Prozentzeichen) ein spezielles Suchzeichen in Lua ist! b,e,m = f:find('(xCF%%[xCF%%]*)',e+1) – beenden, wenn keine Übereinstimmungen mehr vorhanden sind, wenn nicht b, dann abbrechen, Ende – CMD.EXE kann nicht drucken Blobs, also wandeln wir sie in Sterne um. print(string.format('%08X: %s',b,m:gsub('xCF%%','*'))) Ende
CODE AUS DEM ARTIKEL: SEARCHKP.LUA
– Einlesen der in der Befehlszeile angegebenen Dump-Datei lokal f = io.open(arg[1],'rb'):read('*a') lokal b,e,m,p = 0,0,nil,nil while true do – Jetzt wollen wir einen oder mehrere Blobs (CF25), gefolgt vom Code – für A..Z, gefolgt von einem 0-Byte, um ACSCII in UTF-16 zu konvertieren b,e,m = f:find(' (xCF%%[xCF%%]*[AZ])x00',e+1) – beenden, wenn keine Übereinstimmungen mehr vorhanden sind, wenn nicht b, dann unterbrechen Ende – CMD.EXE kann keine Blobs drucken, daher konvertieren wir sie in Sterne. – Um Platz zu sparen, unterdrücken wir aufeinanderfolgende Übereinstimmungen, wenn m ~= p then print(string.format('%08X: %s',b,m:gsub('xCF%%','*'))) p = m Ende Ende
- SEO-gestützte Content- und PR-Distribution. Holen Sie sich noch heute Verstärkung.
- PlatoAiStream. Web3-Datenintelligenz. Wissen verstärkt. Hier zugreifen.
- Die Zukunft prägen mit Adryenn Ashley. Hier zugreifen.
- Kaufen und verkaufen Sie Anteile an PRE-IPO-Unternehmen mit PREIPO®. Hier zugreifen.
- Quelle: https://nakedsecurity.sophos.com/2023/05/31/serious-security-that-keepass-master-password-crack-and-what-we-can-learn-from-it/
- :hast
- :Ist
- :nicht
- :Wo
- ][P
- $UP
- 1
- 10
- 12
- 15%
- 20
- 200
- 2023
- 24
- 250
- 27
- 31
- 3d
- 49
- 50
- 67
- 70
- 72
- 77
- 8
- 9
- a
- Fähig
- Über uns
- oben
- Absolute
- AC
- Zugang
- Konto
- erwerben
- Erwerb
- aktiv
- präsentieren
- berührt das Schneidwerkzeug
- hinzugefügt
- Zusätzliche
- Adresse
- Adressen
- Fügt
- Nach der
- danach
- aufs Neue
- Alle
- zugeordnet
- Zuweisen
- Zuteilung
- Zuweisungen
- erlauben
- allein
- entlang
- bereits
- ebenfalls
- verändert
- Obwohl
- immer
- an
- und
- Martin
- beantworten
- jedem
- etwas
- irgendwas kritisches
- erscheinen
- erschienen
- Ansatz
- genehmigt
- SIND
- um
- Artikel
- Artikel
- AS
- At
- Autor
- Auto
- automatische
- Im Prinzip so, wie Sie es von Google Maps kennen.
- verfügbar
- vermeiden
- vermieden
- bewusst
- ein Weg
- Zurück
- Hintergrund
- background-image
- BE
- weil
- wird
- war
- Bevor
- Anfang
- hinter
- hinter den Kulissen
- unten
- Besser
- Bit
- Blockieren
- Blockiert
- Grenze
- beide
- Boden
- Marke
- Brand New
- Break
- kurz
- bringen
- puffern
- Pufferüberlauf
- Fehler
- Bugs
- bauen
- aber
- by
- C + +
- rufen Sie uns an!
- Aufruf
- CAN
- Kann bekommen
- österreichische Unternehmen
- Häuser
- gefangen
- CD
- Center
- sicherlich
- Ketten
- Chance
- geändert
- Charakter
- Zeichen
- Überprüfung
- Schecks
- chinesisch
- Auswählen
- klar
- Menu
- Code
- Farbe
- COM
- kommt
- Kommen
- Kommentar
- Bemerkungen
- gemeinsam
- abschließen
- Komplex
- Computer
- Geht davon
- erheblich
- betrachtet
- Bestehend
- Bau
- Inhalt
- Inhalt
- ständig
- weiter
- Smartgeräte App
- verkaufen
- Urheberrecht
- Dazugehörigen
- könnte
- Abdeckung
- Riss
- erstellen
- erstellt
- Schöpfer
- kritischem
- Internet-Sicherheit
- ACHTUNG
- Gefährlich
- technische Daten
- Datenlecks
- Tage
- Deal
- entschieden
- gewidmet
- beschrieben
- DID
- Unterschiede
- anders
- Schwierigkeit
- DIG
- digital
- Direkt
- Direkter Zugang
- Direkt
- Display
- Anzeige
- hat
- do
- Unterlagen
- die
- Tut nicht
- Dabei
- erledigt
- Nicht
- nach unten
- Antrieb
- zwei
- abladen
- im
- e
- jeder
- Früher
- leicht
- Ökosystem
- entweder
- sonst
- E-Mails
- ermöglichen
- ermutigend
- Verschlüsselung
- Ende
- endet
- genug
- gewährleisten
- Gewährleistung
- Enter
- Eingabe
- Ganz
- Eintrag
- Arbeitsumfeld
- Äquivalent
- Fehler
- im Wesentlichen
- geschätzt
- etc
- Äther (ETH)
- Sogar
- schließlich
- immer größer
- Jedes
- alles
- genau
- Beispiel
- Außer
- Aufregung
- Ausführung
- existieren
- vorhandenen
- Beenden
- Faszinierende
- erwarten
- Erklären
- Ausnutzen
- ausgesetzt
- erweitern
- extra
- Extrakt
- Tatsache
- falsch
- faszinierend
- Eigenschaften
- Feedback
- Weniger
- Kampf
- Reichen Sie das
- Mappen
- Filter
- Finale
- Endlich
- Finden Sie
- Suche nach
- findet
- Ende
- Vorname
- fixiert
- Setzen Sie mit Achtsamkeit
- gefolgt
- Folgende
- Aussichten für
- unten stehende Formular
- Formal
- Format
- bevorstehend
- gefunden
- vier
- Frei
- für
- voller
- voll
- Funktion
- Funktionen
- weiter
- Zukunft
- erzeugt
- Generation
- bekommen
- bekommen
- GitHub
- ABSICHT
- gegeben
- gibt
- Unterstützung
- Go
- Goes
- gehen
- gut
- der Regierung
- greifen
- groß
- Garantie
- hätten
- Griffe
- passiert
- Los
- das passiert
- hart
- Haben
- mit
- Kopfschmerzen
- schwer
- Höhe
- hier
- HEX
- High-Level
- höher
- Treffer
- Loch
- ein Geschenk
- STUNDEN
- schweben
- Ultraschall
- Hilfe
- HTTPS
- Jagd
- i
- Kennzeichnung
- if
- sofort
- wichtig
- in
- Dazu gehören
- Einschließlich
- Information
- informiert
- beantragen müssen
- interessiert
- Mittel
- Internet
- in
- Probleme
- IT
- SEINE
- selbst
- Jargon
- Juni
- nur
- nur einer
- Behalten
- Wesentliche
- Wissen
- bekannt
- Koreanisch
- Sprache
- Sprachen
- Laptop
- Nachname
- später
- führen
- umwandeln
- Leck
- Undichtigkeiten
- LERNEN
- lernen
- am wenigsten
- Verlassen
- links
- Länge
- Brief
- Bibliothek
- Lebensdauer
- Gefällt mir
- wahrscheinlich
- Limitiert
- Line
- Linien
- Liste
- Gelistet
- ll
- Belastung
- aus einer regionalen
- Standorte
- Protokollierung
- Lang
- langfristig
- länger
- aussehen
- aussehen wie
- sah
- suchen
- SIEHT AUS
- Los
- Glück
- unterhält
- um
- Malware
- verwalten
- verwaltet
- Management
- Manager
- Managed
- Manipulation
- viele
- Marge
- Kennzeichen
- Marker
- Master
- Spiel
- Abstimmung
- max-width
- Kann..
- Mittel
- Memory
- erwähnt
- Microsoft
- könnte
- Minuten
- bescheiden
- geändert
- ändern
- Moment
- Überwachung
- mehr
- vor allem warme
- viel
- mehrere
- Ordentlich
- Need
- erforderlich
- Netto-
- hört niemals
- dennoch
- Neu
- News
- weiter
- schön
- nicht
- normal
- nichts
- Notiz..
- jetzt an
- Anzahl
- Zahlen
- Objekt
- offensichtlich
- of
- WOW!
- offiziell
- Offiziell
- Offset
- Alt
- on
- einmal
- EINEM
- einzige
- Open-Source-
- die
- Betriebssystem
- Betriebssysteme
- Operator
- Option
- or
- Auftrag
- Original
- Andere
- Anders
- Andernfalls
- UNSERE
- uns
- Möglichkeiten für das Ausgangssignal:
- übrig
- Gesamt-
- besitzen
- Seite
- Panik
- Teil
- Passwort
- Password Manager
- Passwörter
- Weg
- Schnittmuster
- Alexander
- Pause
- AUFMERKSAMKEIT
- Prozent
- vielleicht
- Zeit
- permanent
- persönliche
- Daten
- physikalisch
- ein Bild
- Stücke
- Ort
- Platzhalter
- Pest
- Plato
- Datenintelligenz von Plato
- PlatoData
- Reichlich
- Points
- Punkte
- Beliebt
- Position
- möglich
- BLOG-POSTS
- Potenzial
- genau
- Gegenwart
- ziemlich
- verhindern
- früher
- Preis
- Drucke
- wahrscheinlich
- Probleme
- Prozessdefinierung
- Programm
- Programmierer
- Programmierer
- Programmierung
- Programme
- ausgesprochen
- setzen
- Python
- F&A
- Frage
- wirft
- RAM
- zufällig
- Angebot
- lieber
- Roh
- Rohdaten
- RE
- erreicht
- Lesen Sie mehr
- Lesebrillen
- bereit
- echt
- wahres Leben
- Echtzeit
- wirklich
- erkenne
- Entspannung
- erholt
- bezogene
- verbleibenden
- merken
- entfernt
- entfernen
- wiederholen
- wiederholt
- WIEDERHOLT
- berichten
- vertreten
- Umwelt und Kunden
- beziehungsweise
- REST
- Die Ergebnisse
- Rückkehr
- Rückkehr
- zeigen
- Loswerden
- Recht
- Risiko
- Risiken
- Zimmer
- Führen Sie
- Laufen
- Laufzeitüberwachung
- s
- safe
- Sicherheit
- gleich
- zufrieden
- Speichern
- sagen
- Scan
- verstreut
- Szenen
- Suche
- Suche
- Zweite
- Sekunden
- Die Geheime
- Abschnitt
- Verbindung
- Sicherheitdienst
- sehen
- Samen
- Sehen
- scheinen
- gesehen
- sieht
- Modellreihe
- ernst
- kompensieren
- Einstellung
- Short
- Kurz
- sollte
- erklären
- gezeigt
- Schild
- Schilder
- ähnlich
- Ähnlich
- Einfacher
- vereinfachte
- vereinfachen
- einfach
- Single
- Größe
- schlafen
- klein
- Hinterhältig
- Snooping
- So
- Software
- solide
- einige
- etwas
- Bald
- Quelle
- Quellcode
- Raumfahrt
- besondere
- speziell
- angegeben
- Geschwindigkeit
- Sterne
- Anfang
- begonnen
- Beginnen Sie
- beginnt
- Anfang
- Immer noch
- Tisch
- Stoppen
- gestoppt
- speichern
- gelagert
- Geschichte
- Schnur
- stark
- Studie
- Erfolgreich
- so
- ausreichend
- vermutet
- Überraschung
- überrascht
- überraschend
- überleben
- SVG
- tauschen
- System
- Systeme und Techniken
- Nehmen
- gemacht
- nimmt
- Einnahme
- sprechen
- technisch
- Techniken
- vorübergehend
- Test
- Tests
- als
- zur Verbesserung der Gesundheitsgerechtigkeit
- Das
- Die Quelle
- ihr
- Sie
- sich
- dann
- Theorie
- Dort.
- deswegen
- vom Nutzer definierten
- Ding
- think
- fehlen uns die Worte.
- diejenigen
- obwohl?
- dachte
- Zeit
- Titel
- zu
- gemeinsam
- nahm
- Werkzeug
- Top
- verfolgen sind
- Tracking
- Übergang
- transparent
- was immer dies auch sein sollte.
- versuchen
- Turned
- XNUMX
- tippe
- typisch
- verstehen
- Unicode
- bis
- ungenutzt
- unerwünscht
- Aktualisierung
- aktualisiert
- URL
- us
- uns Regierung
- Anwendungsbereich
- USB
- -
- gebrauchsfertig
- benutzt
- Mitglied
- verwendet
- Verwendung von
- Nutzen
- Wert
- Werte
- Vielfalt
- überprüfen
- Version
- sehr
- Verwundbarkeit
- W
- warten
- Warten
- wollen
- wollte
- wurde
- Ansehen
- Weg..
- Wege
- we
- Wochen
- GUT
- waren
- Was
- wann
- ob
- welche
- während
- WHO
- wer auch immer
- ganze
- warum
- werden wir
- gewinnen
- Fenster
- wischen
- mit
- ohne
- fragen
- Worte
- Arbeiten
- gearbeitet
- arbeiten,
- Werk
- Sorgen
- würde
- Würde geben
- schreiben
- Schreiben
- geschrieben
- noch
- U
- Ihr
- sich selbst
- Zephyrnet
- Null