Jak używać Slither do znajdowania błędów w inteligentnych kontraktach
Jak używać Slither
Celem tego samouczka jest pokazanie, jak używać Slither do automatycznego wyszukiwania błędów w inteligentnych kontraktach.
- Instalacja
- Użycie w wierszu poleceń
- Wprowadzenie do analizy statycznej: Krótkie wprowadzenie do analizy statycznej
- API: Opis API Pythona
Instalacja
Slither wymaga Pythona >= 3.6. Można go zainstalować za pomocą pip lub dockera.
Slither przez pip:
pip3 install --user slither-analyzerSlither przez Docker:
docker pull trailofbits/eth-security-toolboxdocker run -it -v "$PWD":/home/trufflecon trailofbits/eth-security-toolboxOstatnie polecenie uruchamia eth-security-toolbox w dockerze, który ma dostęp do bieżącego katalogu. Możesz zmienić pliki z hosta i uruchomić narzędzia na plikach z dockera
Wewnątrz dockera uruchom:
solc-select 0.5.11cd /home/trufflecon/Uruchamianie skryptu
Aby uruchomić skrypt Pythona za pomocą Pythona 3:
python3 script.pyWiersz poleceń
Wiersz poleceń a skrypty zdefiniowane przez użytkownika. Slither jest wyposażony w zestaw predefiniowanych detektorów, które znajdują wiele częstych błędów. Wywołanie Slither z wiersza poleceń uruchomi wszystkie detektory; nie jest potrzebna szczegółowa wiedza na temat analizy statycznej:
slither project_pathsOprócz detektorów Slither ma możliwości przeglądu kodu za pomocą swoich printerówopens in a new tab i narzędziopens in a new tab.
Użyj crytic.ioopens in a new tab, aby uzyskać dostęp do prywatnych detektorów i integracji z GitHub.
Analiza statyczna
Możliwości i projekt frameworka do analizy statycznej Slither zostały opisane w postach na blogu (1opens in a new tab, 2opens in a new tab) i w pracy naukowejopens in a new tab.
Istnieją różne rodzaje analizy statycznej. Najprawdopodobniej zdajesz sobie sprawę, że kompilatory takie jak clangopens in a new tab i gccopens in a new tab opierają się na tych technikach badawczych, ale stanowią one również podstawę (Inferopens in a new tab, CodeClimateopens in a new tab, FindBugsopens in a new tab) i narzędzi opartych na metodach formalnych, takich jak Frama-Copens in a new tab i Polyspaceopens in a new tab.
Nie będziemy tutaj wyczerpująco omawiać technik analizy statycznej ani badań w tej dziedzinie. Zamiast tego skoncentrujemy się na tym, co jest potrzebne, aby zrozumieć, jak działa Slither tak, abyś mógł go skuteczniej używać, aby znaleźć błędy i zrozumieć kod.
Reprezentacja kodu
W przeciwieństwie do analizy dynamicznej, która rozważa pojedynczą ścieżkę wykonania, analiza statyczna rozważa wszystkie ścieżki naraz. W tym celu opiera się na innej reprezentacji kodu. Dwa najczęściej spotykane to abstrakcyjne drzewo składni (AST) i graf przepływu sterowania (CFG).
Abstrakcyjne drzewa składni (AST)
AST są używane za każdym razem, gdy kompilator analizuje kod. Jest to prawdopodobnie najbardziej podstawowa struktura, na podstawie której można przeprowadzić analizę statyczną.
Krótko mówiąc, AST to ustrukturyzowane drzewo, w którym zazwyczaj każdy liść zawiera zmienną lub stałą, a węzły wewnętrzne to operandy lub operacje przepływu sterowania. Rozważmy następujący kod:
1function safeAdd(uint a, uint b) pure internal returns(uint){2 if(a + b <= a){3 revert();4 }5 return a + b;6}Odpowiadające mu drzewo AST pokazano poniżej:
Slither używa AST eksportowanego przez solc.
Choć prosty w budowie, AST jest strukturą zagnieżdżoną. Czasem jego przeanalizowanie nie jest proste. Na przykład, aby zidentyfikować operacje użyte w wyrażeniu a + b <= a, należy najpierw przeanalizować <= a następnie +. Powszechnym podejściem jest użycie tak zwanego wzorca wizytatora, który rekurencyjnie porusza się po drzewie. Slither zawiera ogólny wizytator w ExpressionVisitoropens in a new tab.
Poniższy kod używa ExpressionVisitor do wykrycia, czy wyrażenie zawiera operację dodawania:
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 is the expression to be tested14print(f'The expression {expression} has a addition: {visitor.result()}')Pokaż wszystkoGraf przepływu sterowania (CFG)
Drugą najpowszechniejszą reprezentacją kodu jest graf przepływu sterowania (CFG). Jak sama nazwa wskazuje, jest to reprezentacja oparta na grafie, która ujawnia wszystkie ścieżki wykonania. Każdy węzeł zawiera jedną lub wiele instrukcji. Krawędzie w grafie reprezentują operacje przepływu sterowania (if/then/else, pętle itp.). CFG naszego poprzedniego przykładu to:
CFG jest reprezentacją, na której opiera się większość analiz.
Istnieje wiele innych reprezentacji kodu. Każda reprezentacja ma zalety i wady w zależności od analizy, którą chcesz przeprowadzić.
Analiza
Najprostszym typem analiz, jakie można wykonać za pomocą Slither, są analizy składniowe.
Analiza składniowa
Slither może poruszać się po różnych komponentach kodu i ich reprezentacjach, aby znaleźć niespójności i wady, stosując podejście podobne do dopasowywania wzorców.
Na przykład poniższe detektory szukają problemów związanych ze składnią:
-
Przesłanianie zmiennej stanuopens in a new tab: iteruje po wszystkich zmiennych stanu i sprawdza, czy któraś z nich nie przesłania zmiennej z dziedziczonego kontraktu (state.py#L51-L62opens in a new tab)
-
Nieprawidłowy interfejs ERC20opens in a new tab: wyszukuje nieprawidłowe sygnatury funkcji ERC20 (incorrect_erc20_interface.py#L34-L55opens in a new tab)
Analiza semantyczna
W przeciwieństwie do analizy składniowej, analiza semantyczna sięga głębiej i analizuje „znaczenie” kodu. Ta rodzina obejmuje kilka szerokich typów analiz. Prowadzą one do skuteczniejszych i bardziej użytecznych wyników, ale są również bardziej złożone w tworzeniu.
Analizy semantyczne są wykorzystywane do wykrywania najbardziej zaawansowanych podatności.
Analiza zależności danych
Mówimy, że zmienna variable_a jest zależna od danych zmiennej variable_b, jeśli istnieje ścieżka wykonania, w której wartość variable_b wpływa na wartość variable_a.
W poniższym kodzie variable_a jest zależna od variable_b:
1// ...2variable_a = variable_b + 1;Slither ma wbudowane możliwości analizy zależności danychopens in a new tab dzięki swojej reprezentacji pośredniej (omówionej w dalszej części).
Przykład wykorzystania zależności danych można znaleźć w detektorze niebezpiecznych ścisłych równościopens in a new tab. W tym przypadku Slither będzie szukał porównania ścisłej równości z niebezpieczną wartością (incorrect_strict_equality.py#L86-L87opens in a new tab) i poinformuje użytkownika, że powinien użyć >= lub <= zamiast ==, aby uniemożliwić atakującemu zastawienie pułapki na kontrakt. Między innymi detektor uzna za niebezpieczną wartość zwracaną przez wywołanie balanceOf(address) (incorrect_strict_equality.py#L63-L64opens in a new tab) i użyje silnika zależności danych do śledzenia jej użycia.
Obliczanie punktu stałego
Jeśli twoja analiza porusza się po CFG i podąża za krawędziami, prawdopodobnie zobaczysz już odwiedzone węzły. Na przykład, jeśli pętla jest przedstawiona jak poniżej:
1for(uint i; i < zakres; ++){2 variable_a += 13}Twoja analiza będzie musiała wiedzieć, kiedy się zatrzymać. Istnieją tu dwie główne strategie: (1) iteracja na każdym węźle skończoną liczbę razy, (2) obliczenie tak zwanego punktu stałego. Punkt stały w zasadzie oznacza, że analiza tego węzła nie dostarcza żadnych istotnych informacji.
Przykład zastosowania punktu stałego można znaleźć w detektorach reentrancji: Slither bada węzły i szuka zewnętrznych wywołań, zapisów do pamięci i odczytów z niej. Gdy osiągnie punkt stały (reentrancy.py#L125-L131opens in a new tab), zatrzymuje eksplorację i analizuje wyniki, aby sprawdzić, czy występuje reentrancja, za pomocą różnych wzorców reentrancji (reentrancy_benign.pyopens in a new tab, reentrancy_read_before_write.pyopens in a new tab, reentrancy_eth.pyopens in a new tab).
Pisanie analiz z wykorzystaniem wydajnych obliczeń punktu stałego wymaga dobrego zrozumienia, w jaki sposób analiza propaguje swoje informacje.
Reprezentacja pośrednia
Reprezentacja pośrednia (IR) to język, który ma być bardziej podatny na analizę statyczną niż język oryginalny. Slither tłumaczy Solidity na własną reprezentację pośrednią (IR): SlithIRopens in a new tab.
Zrozumienie SlithIR nie jest konieczne, jeśli chcesz pisać tylko podstawowe sprawdzenia. Będzie to jednak przydatne, jeśli planujesz pisać zaawansowane analizy semantyczne. Printery SlithIRopens in a new tab i SSAopens in a new tab pomogą ci zrozumieć, w jaki sposób tłumaczony jest kod.
Podstawy API
Slither posiada API, które pozwala na eksplorację podstawowych atrybutów kontraktu i jego funkcji.
Aby załadować bazę kodu:
1from slither import Slither2slither = Slither('/path/to/project')3Eksploracja kontraktów i funkcji
Obiekt Slither posiada:
contracts (list(Contract)): lista kontraktówcontracts_derived (list(Contract)): lista kontraktów, które nie są dziedziczone przez żaden inny kontrakt (podzbiór kontraktów)get_contract_from_name (str): Zwraca kontrakt na podstawie jego nazwy
Obiekt Contract posiada:
name (str): Nazwa kontraktufunctions (list(Function)): Lista funkcjimodifiers (list(Modifier)): Lista modyfikatorówall_functions_called (list(Function/Modifier)): Lista wszystkich funkcji wewnętrznych osiągalnych z poziomu kontraktuinheritance (list(Contract)): Lista dziedziczonych kontraktówget_function_from_signature (str): Zwraca funkcję na podstawie jej sygnaturyget_modifier_from_signature (str): Zwraca modyfikator na podstawie jego sygnaturyget_state_variable_from_name (str): Zwraca zmienną stanu na podstawie jej nazwy
Obiekt Function lub Modifier posiada:
name (str): Nazwa funkcjicontract (Contract): kontrakt, w którym zadeklarowana jest funkcjanodes (list(Node)): Lista węzłów tworzących CFG funkcji/modyfikatoraentry_point (Node): Punkt wejściowy CFGvariables_read (list(Variable)): Lista odczytanych zmiennychvariables_written (list(Variable)): Lista zapisanych zmiennychstate_variables_read (list(StateVariable)): Lista odczytanych zmiennych stanu (podzbiórvariables_read)state_variables_written (list(StateVariable)): Lista zapisanych zmiennych stanu (podzbiórvariables_written)
Strona ostatnio zaktualizowana: 14 lutego 2026

