Cum se utilizează Slither pentru a găsi erori în contractele inteligente
Cum se utilizează Slither
Intenția acestui tutorial este de a arăta cum se utilizează Slither pentru a găsi automat erori în contracte inteligente.
- Instalare
- Utilizarea liniei de comandă
- Introducere în analiza statică: Scurtă introducere în analiza statică
- API: Descriere API Python
Instalare
Slitter necesită Python >= 3.6. Acesta poate fi instalat prin pip sau folosind docker.
Slither prin pip:
pip3 install --user slither-analyzer
Slither prin docker:
docker pull trailofbits/eth-security-toolboxdocker run -it -v "$PWD":/home/trufflecon trailofbits/eth-security-toolbox
Ultima comandă rulează eth-Security-Toolbox într-un docker care are acces la directorul curent. Poți să schimbi fișierele din gazdă și să execuți instrumentele de pe fișiere din docker
În interiorul docker, execută:
solc-select 0.5.11cd /home/trufflecon/
Rularea unui script
Pentru a rula un script python cu python 3:
python3 script.py
Linie de comandă
Linia de comandă în comparație cu scripturile definite de utilizator. Slither vine cu un set de detectoare predefinite care găsesc multe erori obișnuite. Apelarea Slither din linia de comandă va rula toate detectoarele, fără cunoștințe detaliate de analiză statică necesare:
slither project_paths
Pe lângă detectoare, Slither are capacități de revizuire a codului prin imprimante(opens in a new tab) și instrumente(opens in a new tab).
Use crytic.io(opens in a new tab) to get access to private detectors and GitHub integration.
Analiză statică
Capacitățile și designul cadrului de analiză statică Slither au fost descrise în postările de pe blog (1(opens in a new tab), 2(opens in a new tab)) și academic paper(opens in a new tab).
Analiza statică există în diferite arome. Cel mai probabil, vei realiza că unele compilatoare precum clang(opens in a new tab) și gcc(opens in a new tab) depind de aceste tehnici de cercetare, dar și sprijină (Infer(opens in a new tab), CodeClimate(opens in a new tab), FindBugs(opens in a new tab) și instrumente bazate pe metode formale precum Frama-C(opens in a new tab) și Polyspace(opens in a new tab).
Nu vom analiza exhaustiv tehnicile de analiză statică și cercetătorii aici. În schimb, ne vom concentra pe ce este nevoie pentru a înțelege modul de funcționare a Slither, încât să îl poți folosi mai eficient pentru a găsi erori și înțelege codul.
Reprezentarea codului
Spre deosebire de o analiză dinamică, care analizează o singură cale de execuție, analiza statică raționează toate căile simultan. Pentru a face acest lucru, se bazează pe o reprezentare de cod diferită. Două din cele mai frecvente sunt arborele de sintaxă abstractă (AST) și graficul fluxului de control (CFG).
Arborele de sintaxă abstractă (AST)
AST este utilizat de fiecare dată când compilatorul analizează codul. Este probabil cea mai de bază structură pe care poate fi efectuată analiza statică.
Pe scurt, un AST este un arbore structurat în care, de obicei, fiecare frunză conține o variabilă sau o constantă și nodurile interne sunt operatori sau operațiuni de control al fluxului. Să considerăm următorul cod:
1function safeAdd(uint a, uint b) pure internal returns(uint){2 if(a + b <= a){3 revert();4 }5 return a + b;6}Copiați
AST-ul corespunzător este indicat în:
Slither utilizează AST-ul exportat de solc.
Deși este simplu de construit, AST este o structură imbricată. Uneori, acest lucru nu este cel mai simplu de analizat. De exemplu, pentru a identifica operațiile utilizate de expresia a + b <= a
, trebuie să analizezi mai întâi <=
și apoi +
. O abordare comună este utilizarea așa-numitului model de vizitator, care navighează prin arbore recursiv. Slither conține un vizitator generic în ExpressionVisitor
(opens in a new tab).
Următorul cod utilizează ExpresionVisitor
pentru a detecta dacă expresia conține o adăugare:
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()}')Afișează totCopiați
Graficul fluxului de control (CFG)
Cea de-a doua reprezentare a codurilor este graficul fluxului de control (CFG). După cum sugerează și numele, este o reprezentare pe bază de grafic care expune toate căile de execuție. Fiecare nod conține una sau mai multe instrucțiuni. Marginile din grafic reprezintă operațiunile fluxului de control (dacă/atunci/altfel, buclă etc.). CFG-ul exemplului nostru anterior este:
CFG este reprezentarea pe baza căreia sunt construite cele mai multe analize.
Există multe alte reprezentări de cod. Fiecare reprezentare are avantaje și dezavantaje în funcție de analiza pe care dorești să o efectuezi.
Analiză
Cel mai simplu tip de analize pe care le poți efectua cu Slither sunt analizele sintactice.
Analiza sintaxei
Slither poate naviga prin diferite componente ale codului și reprezentarea lor pentru a găsi incoerențe și defecte folosind o abordare asemănătoare modelului.
De exemplu, următoarele detectoare caută probleme legate de sintaxă:
„Variabila de stare shadowing”(opens in a new tab): Iterează peste toate variabilele de stare și verifică dacă există vreo variabilă „shadow" dintr-un contract moștenit (state.py#L51-L62(opens in a new tab))
Interfață incorectă ERC20(opens in a new tab): Caută semnături incorecte pentru funcția ERC20 (incorrect_erc20_interface.py#L34-L55(opens in a new tab))
Analiza semantică
Spre deosebire de analiza sintaxei, o analiză semantică va merge mai adânc și va analiza „sensul" codului. Această familie include câteva tipuri largi de analize. Ele duc la rezultate mai puternice și utile, dar sunt, de asemenea, mai complex de scris.
Analizele semantice sunt utilizate pentru cele mai avansate detectări de vulnerabilitate.
Analiza dependenței de date
Se spune despre o variabilă cum ar fi variable_a
că este data dependentă de variable_b
dacă există o modalitate prin care valoarea variable_a
să fie influențată de variable_b
.
În codul următor, variable_a
depinde de variable_b
:
1// ...2variable_a = variable_b + 1;Copiați
Slither vine cu capabilități dependențe de date(opens in a new tab) încorporate, datorită reprezentării sale intermediare (discutate într-o secțiune ulterioară).
Un exemplu de utilizare a dependenței de date poate fi găsit în detectorul de egalitate strictă periculoasă(opens in a new tab). Aici Slither va căuta o comparație strictă a egalității cu o valoare periculoasă (incorrect_strict_equality.py#L86-L87(opens in a new tab)) și va informa utilizatorul că ar trebui să utilizeze >=
sau <=
în loc de ==
, pentru a împiedica un atacator să blocheze contractul. Printre altele, detectorul va considera ca fiind periculoasă valoarea returnată a unui apel către balanceOf(address)
(incorrect_strict_equality.py#L63-L64(opens in a new tab)) și va utiliza motorul dependent de date pentru a urmări utilizarea acestuia.
Calcul de punct fix
Dacă analiza ta navighează prin CFG și urmează marginile, este posibil să vezi noduri deja vizitate. De exemplu, dacă o buclă este prezentată precum cea de mai jos:
1for(uint i; i < range; ++){2 variable_a += 13}Copiați
Analiza ta va trebui să știe când să se oprească. Există două strategii principale aici: (1) iterare pe fiecare nod un număr finit de ori, (2) calcularea unui așa-numit fixpoint. Un fixpoint înseamnă că analiza acestui nod nu oferă nici o informație semnificativa.
Un exemplu de „fixpoint” utilizat poate fi găsit în detectoare de reintrare: Slither explorează nodurile și caută apeluri externe, scrie și citește în stocare. După ce a ajuns la un „fixpoint” (reentrancy.py#L125-L131(opens in a new tab)), acesta oprește explorarea și analizează rezultatele pentru a vedea dacă există o reintrare, prin diferite modele de reintrare (reentrancy_benign.py(opens in a new tab), reentrancy_read_before_write.py(opens in a new tab), reentrancy_eth.py(opens in a new tab)).
Scrierea analizelor folosind calculul eficient de punct fix necesită o bună înțelegere a modului în care analiza își propagă informațiile.
Reprezentarea intermediară
O reprezentare intermediară (IR) este un limbaj care se dorește a fi mai adecvat la analiza statică decât cea inițială. Slither traduce Solidity în propria sa IR: SlithIR(opens in a new tab).
Înțelegerea SlithIR nu este necesară dacă dorești numai să scrii verificări de bază. Cu toate acestea, va fi utilă dacă intenționezi să scrii analize semantice avansate. Imprimantele SlithIR(opens in a new tab) și SSA(opens in a new tab) te vor ajuta să înțelegi modul în care este tradus codul.
Principii API
Slither are un API care îți permite să explorezi atributele de bază ale contractului și funcțiile sale.
Pentru a încărca un cod de bază:
1from slither import Slither2slither = Slither('/path/to/project')3Copiați
Explorarea contractelor și a funcțiilor
Un obiect Slitter
are:
contracts (list(Contract)
: listă de contractecontracts_derived (list(Contract)
: lista contractelor care nu sunt moștenite de un alt contract (subset de contracte)get_contract_from_name (str)
: Returnează un contract din numele său
Un obiect contract
are:
name (str)
: Numele contractuluifunctions (list(Function))
: Listă de funcțiimodifiers (list(Modifier))
: Listă de funcțiiall_functions_called (list(Function/Modifier))
: Lista tuturor funcțiilor interne accesibile prin contractinheritance (list(Contract))
: Lista contractelor moșteniteget_function_from_signature (str)
: Returnează o funcție de la semnăturăget_modifier_from_signature (str)
: Returnează un modificator de la semnăturăget_state_variable_from_name (str)
: Returnează o variabilă de stare din numele său
Un obiect Function
sau un obiect Modifier
are:
name (str)
: Numele funcțieicontract (contract)
: contractul în care este declarată funcțianodes (list(Node))
: Lista nodurilor care compun CFG-ul funcției/modificatoruluientry_point (Node)
: Punctul de intrare al CFGvariables_read (list(Variable))
: Lista variabilelor cititevariables_written (list(Variable))
: Lista variabilelor scrisestate_variables_read (list(StateVariable))
: Lista variabilelor de stare citite (subset de variabile`read)state_variables_written (list(StateVariable))
: Lista variabilelor de stare scrise (subset de variabile`scris)
Ultima modificare: @nhsz(opens in a new tab), 15 august 2023