
Artikel Autor: 0x64rem
Eintrag
Vor anderthalb Jahren hatte ich die Idee, meinen Phaser im Rahmen der Diplomarbeit an der Universität zu realisieren. Ich begann Materialien über Kontrollflussdiagramme, Datenflussdiagramme, symbolische Ausführung usw. zu studieren. Als nächstes folgte die Suche nach Werkzeugen, einer Auswahl verschiedener Bibliotheken (Angr, Triton, Pin, Z3). Am Ende passierte nichts Konkretes, bis ich diesen Sommer zum Summer of Hack 2019-Programm von Digital Security ging, wo mir die Erweiterung des Clang Static Analyzer als Thema für das Projekt angeboten wurde. Es schien mir, dass dieses Thema mir helfen würde, mein theoretisches Wissen in die Regale zu stellen, etwas Wesentliches umzusetzen und Empfehlungen von erfahrenen Mentoren zu erhalten. Als nächstes werde ich Ihnen erzählen, wie der Prozess des Schreibens des Plug-Ins verlaufen ist, und den Verlauf meiner Gedanken während des Praktikumsmonats beschreiben.
Clang statischer Analysator
Für die Entwicklung bietet Clang drei Schnittstellenoptionen für die Interaktion:
- LibClang ist eine übergeordnete C-Schnittstelle, mit der Sie mit AST interagieren können, jedoch nicht vollständig. Eine gute Option, wenn Sie eine Interaktion mit einer anderen Sprache (z. B. die Implementierung von Bindungen ) oder eine stabile Schnittstelle benötigen.
- Clang Plugins - dynamische Bibliotheken, die zur Kompilierungszeit aufgerufen werden. Ermöglicht die vollständige Bearbeitung des AST.
- LibTooling - eine Bibliothek zum Erstellen separater Tools basierend auf Clang. Ermöglicht außerdem den vollständigen Zugriff auf die Interaktion mit AST. Der resultierende Code kann außerhalb der Build-Umgebung des überprüften Projekts ausgeführt werden.
Da wir die Funktionen von Clang Static Analyzer erweitern werden, wählen wir die Implementierung des Plugins. Sie können Code für das Plugin in C ++ oder Python schreiben.
Für letztere gibt es Ordner , mit denen Sie den Quellcode analysieren, über die Knoten des resultierenden abstrakten Syntaxbaums iterieren, auf die Eigenschaften der Knoten zugreifen und den Knoten der Zeile des Quellcodes zuordnen können. Ein solches Set eignet sich für einen einfachen Prüfer. Weitere Informationen finden Sie im llvm-Repository .
Meine Aufgabe erfordert eine detaillierte Analyse des Codes, daher wurde C ++ für die Entwicklung ausgewählt. Als nächstes folgt eine Einführung in das Tool.
Clang Staic Analyzer (im Folgenden CSA) ist ein Tool zur statischen Analyse von C / C ++ / Objective-C-Code basierend auf symbolischer Ausführung. Der Analysator kann über das Clang-Frontend aufgerufen werden, indem dem Befehl build die Flags -cc1 und -analyze hinzugefügt werden, oder über eine separate Scan-Build-Binärdatei. Zusätzlich zur Analyse selbst ermöglicht CSA die Erstellung visueller HTML-Berichte.

CSA verfügt über eine hervorragende Bibliothek zum Parsen von Quellcode mithilfe von AST (Abstract Syntax Tree) und CFG (Control Flow Graph). Aus den Strukturen können Sie weiter die Deklarationen von Variablen, ihre Typen, die Verwendung von binären und unären Operatoren sehen, Sie können symbolische Ausdrücke erhalten usw. Mein Plugin wird die Funktionalität von AST-Klassen verwenden, diese Auswahl wird weiter gerechtfertigt sein. Das Folgende ist eine Liste von Klassen, die bei der Implementierung des Plugins verwendet wurden. Die Liste hilft dabei, ein primäres Verständnis der Funktionen von CSA zu erlangen:
Stmt - Dies schließt binäre Operationen ein.
Deklaration - Deklaration von Variablen.
Ausdruck - speichert die linken und rechten Teile von Ausdrücken, ihren Typ.
ASTContext - Informationen zum Baum, dem aktuellen Knoten.
Quellmanager - Informationen zum tatsächlichen Code, der dem Teil des Baums entspricht.
RecursiveASTVisitor, ASTMatcher - Klassen zum Durchlaufen eines Baums.
Ich wiederhole, dass CSA dem Entwickler die Möglichkeit bietet, die Struktur des Codes im Detail zu untersuchen, und die oben aufgeführten Klassen sind nur ein kleiner Teil der verfügbaren. Ich empfehle auf jeden Fall, die Dokumentation Ihrer Clang-Version zu lesen, wenn Sie nicht wissen, wie Sie Daten extrahieren können. höchstwahrscheinlich wurde bereits etwas Passendes geschrieben.
Integer Overflow Search
Um mit der Implementierung des Plugins zu beginnen, müssen Sie die Aufgabe auswählen, die es lösen soll. In diesem Fall enthält die llvm-Website Listen potenzieller Prüfer . Sie können auch vorhandene stabile oder Alpha- Prüfer ändern. Bei der Überprüfung des Codes der verfügbaren Prüfer wurde klar, dass es für eine erfolgreichere Entwicklung von libclang besser ist, den Prüfer von Grund auf neu zu schreiben. Daher wurde die Auswahl aus einer Liste nicht realisierter Ideen getroffen . Infolgedessen wurde die Option ausgewählt, einen Prüfer für die Erkennung von Ganzzahlüberläufen zu erstellen. Clang verfügt bereits über Funktionen, um diese Sicherheitsanfälligkeit zu verhindern (die Flags -ftrapv, -fwrapv und dergleichen sind für seine Verwendung angegeben), es ist in den Compiler integriert, und dieser Auspuff wird in Warnungen eingefüllt, und es wird dort nicht oft gesucht. Es gibt immer noch UBSan , aber dies sind Desinfektionsmittel, die nicht von allen verwendet werden. Bei dieser Methode werden Probleme zur Laufzeit identifiziert, und das CSA-Plug-In funktioniert zur Kompilierungszeit und analysiert die Quellen.
Als nächstes folgt die Sammlung von Material zur ausgewählten Sicherheitsanfälligkeit. Ein ganzzahliger Überlauf war früher etwas Einfaches und nicht Ernstes. In der Tat ist die Sicherheitsanfälligkeit unterhaltsam und kann beeindruckende Konsequenzen haben.
Ganzzahlüberläufe sind eine Art von Sicherheitsanfälligkeit, die dazu führen kann, dass Daten vom Typ Ganzzahl im Code unerwartete Werte annehmen. Überlauf - Wenn die Variable größer geworden ist als beabsichtigt, Unterlauf - weniger als der ursprüngliche Typ. Solche Fehler können sowohl aufgrund des Programmierers als auch aufgrund des Compilers auftreten.
In C ++ werden während einer arithmetischen Vergleichsoperation ganzzahlige Werte in denselben Typ umgewandelt, häufiger in einen größeren Typ in Bezug auf die Bittiefe. Und solche Geister kommen überall und ständig vor, sie können explizit oder implizit sein. Es gibt verschiedene Regeln, nach denen Geister auftreten [1]:
- Konvertieren von einem signierten in einen Typ mit einem signierten, aber größeren Bit: Fügen Sie einfach die hohe Ordnung hinzu.
- Konvertieren einer vorzeichenbehafteten Ganzzahl in eine vorzeichenlose Ganzzahl mit derselben Kapazität: Das Negative wird in ein Positiv umgewandelt und erhält eine neue Bedeutung. Ein Beispiel für einen ähnlichen Fehler in DirectFB ist CVE-2014-2977 .
- Konvertieren einer vorzeichenbehafteten Ganzzahl in eine vorzeichenlose Ganzzahl mit größerer Bitkapazität: Zuerst wird die Bitkapazität erweitert, und wenn die Zahl negativ ist, wird der Wert falsch geändert. Zum Beispiel: 0xff (-1) wird zu 0xffffffff.
- Eine vorzeichenlose Ganzzahl mit einem Vorzeichen derselben Bitkapazität: Eine Zahl kann den Wert abhängig vom Wert des hohen Bits ändern.
- Eine vorzeichenlose Ganzzahl mit einer Ganzzahl mit einem Zeichen größerer Kapazität: Zuerst erhöht sich die Kapazität einer vorzeichenlosen Zahl, dann die Konvertierung in eine vorzeichenbehaftete.
- Abwärtskonvertierung: Bits werden nur abgeschnitten. Dies kann vorzeichenlose Werte negativ machen und so weiter. Ein Beispiel für eine solche Sicherheitslücke in PHP .
Das heißt, Der Auslöser für die Sicherheitsanfälligkeit können unsichere Benutzereingaben, falsche Arithmetik und falsche Typkonvertierung sein, die von einem Programmierer oder Compiler während der Optimierung verursacht werden. Die Zeitbombenoption ist auch möglich, wenn ein Teil des Codes mit einer Version des Compilers harmlos ist, aber mit der Veröffentlichung eines neuen Optimierungsalgorithmus „explodiert“ und unerwartetes Verhalten verursacht. In der Geschichte gab es bereits einen solchen Fall mit der SafeInt-Klasse (sehr ironisch) [5, 6.5.2].
Ganzzahlüberläufe öffnen einen breiten Vektor: Es ist möglich, die Ausführung zu zwingen, einen anderen Pfad einzuschlagen (wenn der Überlauf bedingte Anweisungen beeinflusst), was zu einem Pufferüberlauf führt. Aus Gründen der Übersichtlichkeit können Sie sich mit bestimmten CVEs vertraut machen und deren Ursachen und Folgen ermitteln. Natürlich ist es besser, in Open Source-Produkten nach einem Ganzzahlüberlauf zu suchen, damit Sie nicht nur die Beschreibung lesen, sondern auch den Code sehen.
- CVE-2019-3560 - Ein Integer-Überlauf in Fizz (ein Projekt, das TLS für Facebook implementiert) könnte die Sicherheitsanfälligkeit für DoS-Angriffe mithilfe eines engen Netzwerkpakets ausnutzen.
- CVE-2018-14618 - Pufferüberlauf in Curl durch ganzzahligen Überlauf aufgrund der Kennwortlänge.
- CVE-2018-6092 - Auf 32-Bit-Systemen ermöglichte eine Sicherheitsanfälligkeit in WebAssembly für Chrome die Implementierung von RCE über eine spezielle HTML-Seite.
Um das Rad nicht neu zu erfinden, wurde der Code zum Erkennen eines Ganzzahlüberlaufs im statischen Analysator CppCheck berücksichtigt. Sein Ansatz ist wie folgt:
- Bestimmen Sie, ob ein Ausdruck ein binärer Operator ist.
- Wenn ja, überprüfen Sie, ob beide Argumente vom Typ Integer sind.
- Bestimmen Sie die Größe der Typen.
- Prüfen Sie anhand von Berechnungen, ob der Wert seine Höchst- oder Mindestgrenze überschreiten kann.
Aber zu diesem Zeitpunkt gab es keine Klarheit. Es gibt viele verschiedene Geschichten, und durch diese Systematisierung von Informationen wird es schwieriger. Alles an seiner Stelle stellte die Liste der CWE . Insgesamt sind auf der Site 9 Arten von Ganzzahlüberläufen zugeordnet:
- 190 - ganzzahliger Überlauf
- 191 - ganzzahliger Unterlauf
- 192 - ganzzahliger Koertionsfehler
- 193 - eins nach dem anderen
- 194 - Unerwartete Zeichenerweiterung
- 195 - Signed to Unsigned Conversion Error
- 196 - Fehler beim Konvertieren ohne Vorzeichen
- 197 - Numerischer Kürzungsfehler
- 198 - Verwendung einer falschen Bytereihenfolge
Wir betrachten den Grund für jede Option und verstehen, dass Überläufe mit falschen expliziten / impliziten Casts auftreten. Und weil Alle Casts werden in der Struktur des abstrakten Syntaxbaums angezeigt. Wir werden AST für die Analyse verwenden. In der folgenden Abbildung (Abb. 3) ist zu sehen, dass jede Operation, die eine Umwandlung in den Baum verursacht, ein separater Knoten ist. Wenn Sie sich im Baum bewegen, können Sie alle Typkonvertierungen anhand einer Tabelle mit Transformationen überprüfen, die einen Fehler verursachen können.

Genauer gesagt klingt der Algorithmus so: Wir gehen um Casts herum und betrachten IntegralCast (Integer-Konvertierungen). Wenn Sie einen geeigneten Knoten finden, suchen Sie die Nachkommen auf der Suche nach einer binären Operation oder Decl (Variablendeklaration). Im ersten Fall müssen Sie das Vorzeichen und die Bittiefe überprüfen, die von der Binäroperation verwendet werden. Vergleichen Sie im zweiten Fall nur die Art der Deklaration.
Checker-Implementierung
Kommen wir zur Implementierung. Wir benötigen ein Skelett für einen Checker, das eine eigenständige Bibliothek sein oder als Teil von Clang zusammengestellt werden kann. Im Code ist der Unterschied gering. Wenn Sie bereits vorhaben, ein eigenes Plugin zu schreiben, empfehle ich Ihnen, sofort ein kleines PDF zu lesen: "Clang Static Analyzer: Ein Checker-Entwicklerhandbuch" . Die grundlegenden Dinge sind dort gut beschrieben, obwohl etwas möglicherweise nicht mehr relevant ist. Die Bibliothek wird regelmäßig aktualisiert, aber Sie sofort greifen.
Wenn Sie Ihren Checker zu Ihrer Clang-Baugruppe hinzufügen möchten, müssen Sie:
Schreiben Sie den Checker selbst mit ungefähr folgendem Inhalt:
namespace { class SuperChecker : public Checker<check::PreStmt<BinaryOperator>> {
Anschließend müssen Sie im Quellcode von Clang die Dateien CMakeLists.txt
und Checkers.td
ändern. Lebe hier herum ${llvm-source-path}/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
und hier ${llvm-source-path}/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
.
In der ersten müssen Sie nur den Dateinamen mit dem Code hinzufügen, in der zweiten müssen Sie eine strukturelle Beschreibung hinzufügen:
#Checkers.td def SuperChecker : Checker<"SuperChecker">, HelpText<"test checker">, Documentation<HasDocumentation>;
Wenn es nicht klar ist, finden Sie in der Datei Checkers.td
genügend Beispiele dafür, wie und was zu tun ist.
Höchstwahrscheinlich möchten Sie Clang nicht neu erstellen, und Sie greifen auf die Option mit der Bibliotheksassembly (so / dll) zurück. Dann sollte im Code des Checkers so etwas lauten:
namespace { class SuperChecker : public Checker<check::PreStmt<BinaryOperator>> {
Als nächstes sammeln Sie Ihren Code, Sie können Ihr eigenes Skript für die Assemblierung schreiben, aber wenn Sie Probleme damit haben (wie der Autor :)), können Sie das Makefile im Quellcode von clang verwenden und den Befehl clangStaticAnalyzerCheckers auf seltsame Weise ausführen.
Rufen Sie als nächstes den Checker auf:
für eingebaute Prüfer
clang++ -cc1 -analyze -analyzer-checker=core.DivideZero test.cpp
für extern
clang++ -cc1 -load ${PATH_TO_CHECKER}/SuperChecker.so -analyze -analyzer-checker=test.Me -analyzer-config test.Me:UsrInp1="foo" test.Me:Inp1="bar" -analyzer-config test.Me:Inp2=123 test.cpp
Zu diesem Zeitpunkt haben wir bereits ein Ergebnis (Abb. 4), aber der geschriebene Code kann nur mögliche Überläufe erkennen. Und das bedeutet eine große Anzahl von Fehlalarmen.

Um dies zu beheben, können wir:
- Gehen Sie im Diagramm hin und her und überprüfen Sie die spezifischen Werte der Variablen auf Fälle, in denen ein potenzieller Überlauf vorliegt.
- Speichern Sie während der AST-Durchquerung sofort bestimmte Werte für Variablen und überprüfen Sie sie bei Bedarf.
- Verwenden Sie die Verschmutzungsanalyse.
Um weitere Argumente zu untermauern, ist zu erwähnen, dass bei der Analyse von Clang alle in der Direktive #include
angegebenen Dateien auch analysiert werden, wodurch sich die Größe des resultierenden AST erhöht. Infolgedessen ist von den vorgeschlagenen Optionen nur eine in Bezug auf eine bestimmte Aufgabe rational:
- Erstens dauert die Fertigstellung sehr lange. Das Gehen in einem Baum, das Suchen und Zählen von allem, was Sie benötigen, wird lange dauern. Es kann schwierig werden, ein großes Projekt mit einem solchen Code zu analysieren. Um den Baum im Code zu
clang::RecursiveASTVisitor
, verwenden wir die Klasse clang::RecursiveASTVisitor
, die eine rekursive Tiefensuche durchführt. Eine Schätzung der Zeit dieses Ansatzes wird sein
Dabei ist V die Menge der Eckpunkte und E die Menge der Kanten des Graphen. - Die zweite - Sie können sicherlich speichern, aber wir wissen nicht, was wir brauchen und was nicht. Darüber hinaus benötigen die Baumstrukturen selbst, die wir in der Analyse verwenden, viel Speicher, sodass es eine schlechte Idee ist, solche Ressourcen für etwas anderes auszugeben.
- Drittens ist eine gute Idee, für diese Methode finden Sie genügend Forschung und Beispiele. Aber in CSA gibt es keinen fertigen Makel. In den Quellen befindet sich ein Prüfer , der später zur Liste der Alpha-Prüfer (alpha.security.taint.TaintPropagation) hinzugefügt wurde. Er wird in der Datei
GenericTaintChecker.cpp
. Der Checker ist gut, aber nur für bekannte unsichere E / A-Funktionen von C geeignet. Er "markiert" nur Variablen, die Argumente oder Ergebnisse gefährlicher Funktionen waren. Zusätzlich zu den beschriebenen Optionen sollten globale Variablen, Klassenfelder usw. berücksichtigt werden, um das "Verteilungs" -Modell korrekt wiederherzustellen.
Die verbleibende Zeit für das Praktikum wurde damit verbracht, GenericTaintChecker.cpp
lesen und zu versuchen, es an Ihre Bedürfnisse anzupassen. Es hat bis zum Ende des Semesters nicht erfolgreich geklappt, aber es blieb eine Aufgabe zur Verfeinerung, die bereits über den Umfang der Ausbildung bei DSec hinausging. Während der Entwicklung wurde auch klar, dass das Identifizieren gefährlicher Funktionen eine separate Aufgabe ist. Nicht immer gefährliche Stellen im Projekt stammen von einigen Standardfunktionen. Daher wurde dem Prüfer ein Flag hinzugefügt, um eine Liste von Funktionen anzuzeigen, die als „vergiftet“ / „markiert“ gelten. während der Verschmutzungsanalyse.
Zusätzlich wurde eine Prüfung hinzugefügt, um festzustellen, ob die Variable ein Bitfeld ist. Bei Standard-CSA-Tools wird die Größe nach Typ bestimmt. Wenn wir mit einem Bitfeld arbeiten, hat seine Größe den Wert des Bittyps des gesamten Felds und nicht die Anzahl der in der Variablendeklaration angegebenen Bits.
Was ist das ergebnis
Derzeit wurde ein einfacher Prüfer implementiert, der nur vor möglichen Ganzzahlüberläufen warnen kann. Eine modifizierte Klasse für die Verschmutzungsanalyse, die noch viel zu tun hat. Danach müssen Sie SMT verwenden, um Überläufe zu bestimmen. Hierfür eignet sich der Z3 SMT-Solver, der der Clang-Baugruppe in Version 5.0.0 hinzugefügt wurde (gemessen an den Versionshinweisen). Um den Solver verwenden zu können, muss Clang mit der Option CLANG_ANALYZER_BUILD_Z3=ON
werden. Wenn das CSA-Plug-In direkt aufgerufen wird, werden die -Xanalyzer -analyzer-constraints=z3
übertragen.
GitHub Results Repository
Referenzen:
Howard M., Leblanc D., Viega J. "Die 24 Sünden der Computersicherheit"
So schreiben Sie einen Checker in 24 Stunden
Clang Static Analyzer: Ein Checker-Entwicklerhandbuch
CSA Checker Entwicklungshandbuch
Dietz W. et al. Grundlegendes zum Ganzzahlüberlauf in C / C ++