Przejdź do głównej zawartości

Jak używać Slither do znajdowania błędów w inteligentnych kontraktach

solidity
smart kontrakty
bezpieczeństwo
testowanie
Zaawansowane funkcje
Trailofbits
9 czerwca 2020
7 minuta czytania

Jak używać Slither

Celem tego samouczka jest pokazanie, jak używać Slither do automatycznego wyszukiwania błędów w inteligentnych kontraktach.

Instalacja

Slither wymaga Pythona >= 3.6. Można go zainstalować za pomocą pip lub dockera.

Slither przez pip:

pip3 install --user slither-analyzer

Slither przez Docker:

docker pull trailofbits/eth-security-toolbox
docker run -it -v "$PWD":/home/trufflecon trailofbits/eth-security-toolbox

Ostatnie 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.11
cd /home/trufflecon/

Uruchamianie skryptu

Aby uruchomić skrypt Pythona za pomocą Pythona 3:

python3 script.py

Wiersz 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_paths

Opró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:

AST

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 ExpressionVisitor
2from slither.core.expressions.binary_operation import BinaryOperationType
3
4class HasAddition(ExpressionVisitor):
5
6 def result(self):
7 return self._result
8
9 def _post_binary_operation(self, expression):
10 if expression.type == BinaryOperationType.ADDITION:
11 self._result = True
12
13visitor = HasAddition(expression) # expression is the expression to be tested
14print(f'The expression {expression} has a addition: {visitor.result()}')
Pokaż wszystko

Graf 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

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ą:

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 += 1
3}

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 Slither
2slither = Slither('/path/to/project')
3

Eksploracja kontraktów i funkcji

Obiekt Slither posiada:

  • contracts (list(Contract)): lista kontraktów
  • contracts_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 kontraktu
  • functions (list(Function)): Lista funkcji
  • modifiers (list(Modifier)): Lista modyfikatorów
  • all_functions_called (list(Function/Modifier)): Lista wszystkich funkcji wewnętrznych osiągalnych z poziomu kontraktu
  • inheritance (list(Contract)): Lista dziedziczonych kontraktów
  • get_function_from_signature (str): Zwraca funkcję na podstawie jej sygnatury
  • get_modifier_from_signature (str): Zwraca modyfikator na podstawie jego sygnatury
  • get_state_variable_from_name (str): Zwraca zmienną stanu na podstawie jej nazwy

Obiekt Function lub Modifier posiada:

  • name (str): Nazwa funkcji
  • contract (Contract): kontrakt, w którym zadeklarowana jest funkcja
  • nodes (list(Node)): Lista węzłów tworzących CFG funkcji/modyfikatora
  • entry_point (Node): Punkt wejściowy CFG
  • variables_read (list(Variable)): Lista odczytanych zmiennych
  • variables_written (list(Variable)): Lista zapisanych zmiennych
  • state_variables_read (list(StateVariable)): Lista odczytanych zmiennych stanu (podzbiór variables_read)
  • state_variables_written (list(StateVariable)): Lista zapisanych zmiennych stanu (podzbiór variables_written)

Strona ostatnio zaktualizowana: 14 lutego 2026

Czy ten samouczek był pomocny?