So verwenden Sie Slither, um Bugs in Smart Contracts zu finden
So verwenden Sie Slither
Das Ziel dieses Tutorials ist es zu zeigen, wie Sie Slither verwenden, um automatisch Fehler in Smart Contracts zu finden.
- Installation
- Verwendung der Befehlszeile
- Einführung in die statische Analyse: Kurze Einführung in die statische Analyse
- API: Python-API-Beschreibung
Installation
Slither erfordert Python >= 3.6. Die Installation kann über pip oder Docker erfolgen.
Slither über pip:
pip3 install --user slither-analyzerSlither über Docker:
docker pull trailofbits/eth-security-toolboxdocker run -it -v "$PWD":/home/trufflecon trailofbits/eth-security-toolboxDer letzte Befehl führt die eth-security-toolbox in einem Docker aus, der Zugriff auf dein aktuelles Verzeichnis hat. Du kannst die Dateien von deinem Host aus ändern und die Tools für die Dateien aus dem Docker ausführen
Führe im Docker aus:
solc-select 0.5.11cd /home/trufflecon/Ausführen eines Skripts
So führst du ein Python-Skript mit Python 3 aus:
python3 script.pyBefehlszeile
Befehlszeile versus benutzerdefinierte Skripte. Slither wird mit einer Reihe von vordefinierten Detektoren geliefert, die viele häufige Fehler finden. Wenn Sie Slither von der Befehlszeile aus aufrufen, werden alle Detektoren ausgeführt, ohne dass detaillierte Kenntnisse der statischen Analyse erforderlich sind:
slither project_pathsZusätzlich zu den Detektoren verfügt Slither über Code-Review-Funktionen durch seine Printersopens in a new tab und Toolsopens in a new tab.
Verwenden Sie crytic.ioopens in a new tab, um Zugang zu privaten Detektoren und zur GitHub-Integration zu erhalten.
Statische Analyse
Die Fähigkeiten und das Design des statischen Analyse-Frameworks von Slither wurden in Blog-Beiträgen (1opens in a new tab, 2opens in a new tab) und einem wissenschaftlichen Artikelopens in a new tab beschrieben.
Statische Analyse gibt es in verschiedenen Varianten. Ihnen ist wahrscheinlich bewusst, dass Compiler wie clangopens in a new tab und gccopens in a new tab auf diesen Forschungstechniken basieren, aber sie bilden auch die Grundlage für (Inferopens in a new tab, CodeClimateopens in a new tab, FindBugsopens in a new tab und auf formalen Methoden basierende Werkzeuge wie Frama-Copens in a new tab und Polyspaceopens in a new tab).
Wir werden hier nicht erschöpfend auf statische Analysetechniken und Forscher eingehen. Stattdessen konzentrieren wir uns darauf, was nötig ist, um zu verstehen, wie Slither funktioniert, damit Sie es effektiver nutzen können, um Fehler zu finden und den Code zu verstehen.
Code-Darstellung
Im Gegensatz zu einer dynamischen Analyse, die einen einzelnen Ausführungspfad betrachtet, betrachtet die statische Analyse alle Pfade auf einmal. Dazu stützt es sich auf eine andere Code-Darstellung. Die beiden häufigsten sind der abstrakte Syntaxbaum (AST) und der Kontrollflussgraph (CFG).
Abstrakte Syntaxbäume (AST)
ASTs werden jedes Mal verwendet, wenn der Compiler Code parst. Es ist wahrscheinlich die grundlegendste Struktur, auf der statische Analysen durchgeführt werden können.
Kurz gesagt, ein AST ist ein strukturierter Baum, in dem normalerweise jedes Blatt eine Variable oder eine Konstante enthält und interne Knoten Operanden oder Kontrollflussoperationen sind. Betrachten Sie den folgenden Code:
1function safeAdd(uint a, uint b) pure internal returns(uint){2 if(a + b <= a){3 revert();4 }5 return a + b;6}Der entsprechende AST ist hier dargestellt:
Slither verwendet den von solc exportierten AST.
Obwohl der AST einfach zu erstellen ist, handelt es sich um eine verschachtelte Struktur. Manchmal ist dies nicht am einfachsten zu analysieren. Um beispielsweise die vom Ausdruck a + b <= a verwendeten Operationen zu identifizieren, müssen Sie zuerst <= und dann + analysieren. Ein gängiger Ansatz ist die Verwendung des sogenannten Visitor-Patterns, das rekursiv durch den Baum navigiert. Slither enthält einen generischen Visitor in ExpressionVisitoropens in a new tab.
Der folgende Code verwendet ExpressionVisitor, um zu erkennen, ob der Ausdruck eine Addition enthält:
1from slither.visitors.expression.expression import ExpressionVisitor2from slither.core.expressions.binary_operation import BinaryOperationType34class HasAddition(ExpressionVisitor):56 def result(self):7 return self._result89 def _post_binary_operation(self, expression):10 if expression.type == BinaryOperationType.ADDITION:11 self._result = True1213visitor = HasAddition(expression) # expression ist der zu testende Ausdruck14print(f'Der Ausdruck {expression} enthält eine Addition: {visitor.result()}')Alles anzeigenKontrollflussgraph (CFG)
Die zweithäufigste Code-Darstellung ist der Kontrollflussgraph (CFG). Wie der Name schon sagt, handelt es sich um eine graphbasierte Darstellung, die alle Ausführungspfade aufzeigt. Jeder Knoten enthält eine oder mehrere Anweisungen. Kanten im Graphen stellen die Kontrollflussoperationen dar (if/then/else, Schleife usw.). Der CFG unseres vorherigen Beispiels lautet:
Der CFG ist die Darstellung, auf der die meisten Analysen aufbauen.
Es gibt viele andere Code-Darstellungen. Jede Darstellung hat Vor- und Nachteile, je nachdem, welche Analyse Sie durchführen möchten.
Analyse
Die einfachste Art von Analysen, die Sie mit Slither durchführen können, sind syntaktische Analysen.
Syntaktische Analyse
Slither kann durch die verschiedenen Komponenten des Codes und deren Darstellung navigieren, um Inkonsistenzen und Fehler mit einem Ansatz zu finden, der dem Pattern-Matching ähnelt.
Die folgenden Detektoren suchen beispielsweise nach syntaxbezogenen Problemen:
-
Zustandsvariablen-Shadowingopens in a new tab: iteriert über alle Zustandsvariablen und prüft, ob eine davon eine Variable aus einem geerbten Vertrag verschattet (state.py#L51-L62opens in a new tab)
-
Inkorrektes ERC20-Interfaceopens in a new tab: sucht nach inkorrekten ERC20-Funktionssignaturen (incorrect_erc20_interface.py#L34-L55opens in a new tab)
Semantische Analyse
Im Gegensatz zur Syntaxanalyse geht eine semantische Analyse tiefer und analysiert die "Bedeutung" des Codes. Diese Familie umfasst einige allgemeine Arten von Analysen. Sie führen zu aussagekräftigeren und nützlicheren Ergebnissen, sind aber auch komplexer zu schreiben.
Semantische Analysen werden für die fortschrittlichsten Schwachstellenerkennungen verwendet.
Datenabhängigkeitsanalyse
Eine Variable variable_a gilt als datenabhängig von variable_b, wenn es einen Pfad gibt, auf dem der Wert von variable_a durch variable_b beeinflusst wird.
Im folgenden Code ist variable_a von variable_b abhängig:
1// ...2variable_a = variable_b + 1;Slither verfügt über integrierte Datenabhängigkeitsopens in a new tab-Fähigkeiten, dank seiner Zwischendarstellung (die in einem späteren Abschnitt besprochen wird).
Ein Beispiel für die Verwendung der Datenabhängigkeit findet sich im Detektor für gefährliche strikte Gleichheitopens in a new tab. Hier sucht Slither nach einem strikten Gleichheitsvergleich mit einem gefährlichen Wert (incorrect_strict_equality.py#L86-L87opens in a new tab) und informiert den Benutzer, dass er >= oder <= anstelle von == verwenden sollte, um zu verhindern, dass ein Angreifer den Vertrag in eine Falle lockt. Unter anderem wird der Detektor den Rückgabewert eines Aufrufs von balanceOf(address) (incorrect_strict_equality.py#L63-L64opens in a new tab) als gefährlich einstufen und die Datenabhängigkeits-Engine verwenden, um seine Verwendung zu verfolgen.
Fixpunktberechnung
Wenn Ihre Analyse durch den CFG navigiert und den Kanten folgt, werden Sie wahrscheinlich bereits besuchte Knoten sehen. Wenn zum Beispiel eine Schleife wie unten dargestellt ist:
1for(uint i; i < range; ++){2 variable_a += 13}Ihre Analyse muss wissen, wann sie anhalten muss. Hier gibt es zwei Hauptstrategien: (1) jeden Knoten eine endliche Anzahl von Malen durchlaufen, (2) einen sogenannten Fixpunkt berechnen. Ein Fixpunkt bedeutet im Grunde, dass die Analyse dieses Knotens keine aussagekräftigen Informationen mehr liefert.
Ein Beispiel für die Verwendung eines Fixpunkts findet sich in den Reentrancy-Detektoren: Slither untersucht die Knoten und sucht nach externen Aufrufen sowie Schreib- und Lesezugriffen auf den Speicher. Sobald ein Fixpunkt erreicht ist (reentrancy.py#L125-L131opens in a new tab), wird die Untersuchung gestoppt und die Ergebnisse werden anhand verschiedener Reentrancy-Muster (reentrancy_benign.pyopens in a new tab, reentrancy_read_before_write.pyopens in a new tab, reentrancy_eth.pyopens in a new tab) analysiert, um festzustellen, ob eine Reentrancy vorliegt.
Das Schreiben von Analysen mit effizienter Fixpunktberechnung erfordert ein gutes Verständnis dafür, wie die Analyse ihre Informationen propagiert.
Zwischendarstellung
Eine Zwischendarstellung (Intermediate Representation, IR) ist eine Sprache, die sich besser für die statische Analyse eignen soll als die ursprüngliche. Slither übersetzt Solidity in seine eigene IR: SlithIRopens in a new tab.
Das Verständnis von SlithIR ist nicht notwendig, wenn Sie nur einfache Prüfungen schreiben wollen. Es wird sich jedoch als nützlich erweisen, wenn Sie vorhaben, fortgeschrittene semantische Analysen zu schreiben. Die SlithIRopens in a new tab- und SSAopens in a new tab-Printers helfen Ihnen zu verstehen, wie der Code übersetzt wird.
API-Grundlagen
Slither hat eine API, mit der Sie die grundlegenden Attribute des Vertrags und seiner Funktionen untersuchen können.
So laden Sie eine Codebasis:
1from slither import Slither2slither = Slither('/path/to/project')3Untersuchen von Verträgen und Funktionen
Ein Slither-Objekt hat:
contracts (list(Contract): Liste von Verträgencontracts_derived (list(Contract): Liste von Verträgen, die nicht von einem anderen Vertrag geerbt werden (Teilmenge voncontracts)get_contract_from_name (str): Gibt einen Vertrag anhand seines Namens zurück
Ein Contract-Objekt hat:
name (str): Name des Vertragsfunctions (list(Function)): Liste von Funktionenmodifiers (list(Modifier)): Liste von Modifikatorenall_functions_called (list(Function/Modifier)): Liste aller internen Funktionen, die vom Vertrag aus erreichbar sindinheritance (list(Contract)): Liste der geerbten Verträgeget_function_from_signature (str): Gibt eine Funktion anhand ihrer Signatur zurückget_modifier_from_signature (str): Gibt einen Modifikator anhand seiner Signatur zurückget_state_variable_from_name (str): Gibt eine Zustandsvariable anhand ihres Namens zurück
Ein Function- oder ein Modifier-Objekt hat:
name (str): Name der Funktioncontract (contract): der Vertrag, in dem die Funktion deklariert istnodes (list(Node)): Liste der Nodes, aus denen der CFG der Funktion/des Modifikators bestehtentry_point (Node): Einstiegspunkt des CFGvariables_read (list(Variable)): Liste der gelesenen Variablenvariables_written (list(Variable)): Liste der geschriebenen Variablenstate_variables_read (list(StateVariable)): Liste der gelesenen Zustandsvariablen (Teilmenge vonvariables_read)state_variables_written (list(StateVariable)): Liste der geschriebenen Zustandsvariablen (Teilmenge vonvariables_written)
Seite zuletzt aktualisiert: 16. Februar 2026

