Lompat ke konten utama

Cara menggunakan Slither untuk menemukan bug kontrak pintar

Solidity
kontrak pintar
keamanan
pengujian
Lanjutan
Trailofbits
9 Juni 2020
7 menit baca

Cara menggunakan Slither

Tujuan dari tutorial ini adalah untuk menunjukkan cara menggunakan Slither untuk secara otomatis menemukan bug dalam kontrak pintar.

Instalasi

Slither membutuhkan Python >= 3.6. Ini dapat diinstal melalui pip atau menggunakan docker.

Slither melalui pip:

pip3 install --user slither-analyzer

Slither melalui docker:

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

Perintah terakhir menjalankan eth-security-toolbox dalam docker yang memiliki akses ke direktori Anda saat ini. Anda dapat mengubah file dari host Anda, dan menjalankan alat pada file dari docker

Di dalam docker, jalankan:

solc-select 0.5.11
cd /home/trufflecon/

Menjalankan skrip

Untuk menjalankan skrip python dengan python 3:

python3 script.py

Baris perintah

Baris perintah versus skrip yang ditentukan pengguna. Slither dilengkapi dengan serangkaian detektor yang telah ditentukan sebelumnya yang menemukan banyak bug umum. Memanggil Slither dari baris perintah akan menjalankan semua detektor, tidak diperlukan pengetahuan terperinci tentang analisis statis:

slither project_paths

Selain detektor, Slither memiliki kemampuan tinjauan kode melalui printer (opens in a new tab) dan alat (opens in a new tab) miliknya.

Gunakan crytic.io (opens in a new tab) untuk mendapatkan akses ke detektor privat dan integrasi GitHub.

Analisis statis

Kemampuan dan desain kerangka kerja analisis statis Slither telah dijelaskan dalam postingan blog (1 (opens in a new tab), 2 (opens in a new tab)) dan sebuah makalah akademis (opens in a new tab).

Analisis statis ada dalam berbagai bentuk. Anda kemungkinan besar menyadari bahwa kompiler seperti clang (opens in a new tab) dan gcc (opens in a new tab) bergantung pada teknik penelitian ini, tetapi ini juga mendasari (Infer (opens in a new tab), CodeClimate (opens in a new tab), FindBugs (opens in a new tab) dan alat-alat yang didasarkan pada metode formal seperti Frama-C (opens in a new tab) dan Polyspace (opens in a new tab).

Kita tidak akan mengulas teknik analisis statis dan penelitinya secara mendalam di sini. Sebaliknya, kita akan berfokus pada apa yang diperlukan untuk memahami cara kerja Slither sehingga Anda dapat menggunakannya dengan lebih efektif untuk menemukan bug dan memahami kode.

Representasi kode

Berbeda dengan analisis dinamis, yang menalar tentang satu jalur eksekusi, analisis statis menalar tentang semua jalur sekaligus. Untuk melakukannya, ini bergantung pada representasi kode yang berbeda. Dua yang paling umum adalah pohon sintaksis abstrak (AST) dan grafik aliran kontrol (CFG).

Pohon Sintaksis Abstrak (AST)

AST digunakan setiap kali kompiler mengurai kode. Ini mungkin merupakan struktur paling dasar di mana analisis statis dapat dilakukan.

Singkatnya, AST adalah pohon terstruktur di mana, biasanya, setiap daun berisi variabel atau konstanta dan node internal adalah operan atau operasi aliran kontrol. Pertimbangkan kode berikut:

1function safeAdd(uint a, uint b) pure internal returns(uint){
2 if(a + b <= a){
3 revert();
4 }
5 return a + b;
6}

AST yang sesuai ditunjukkan pada:

AST

Slither menggunakan AST yang diekspor oleh solc.

Meskipun mudah dibangun, AST adalah struktur bersarang. Terkadang, ini bukan yang paling mudah untuk dianalisis. Misalnya, untuk mengidentifikasi operasi yang digunakan oleh ekspresi a + b <= a, Anda harus terlebih dahulu menganalisis <= dan kemudian +. Pendekatan umum adalah menggunakan apa yang disebut pola pengunjung (visitor pattern), yang menavigasi melalui pohon secara rekursif. Slither berisi pengunjung generik di ExpressionVisitor (opens in a new tab).

Kode berikut menggunakan ExpressionVisitor untuk mendeteksi apakah ekspresi tersebut berisi penambahan:

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 # expression adalah ekspresi yang akan diuji
14print(f'The expression {expression} has a addition: {visitor.result()}')
Tampilkan semua

Grafik Aliran Kontrol (CFG)

Representasi kode paling umum kedua adalah grafik aliran kontrol (CFG). Seperti namanya, ini adalah representasi berbasis grafik yang mengekspos semua jalur eksekusi. Setiap node berisi satu atau beberapa instruksi. Tepi (edges) dalam grafik mewakili operasi aliran kontrol (if/then/else, loop, dll). CFG dari contoh kita sebelumnya adalah:

CFG

CFG adalah representasi di atas mana sebagian besar analisis dibangun.

Banyak representasi kode lainnya yang ada. Setiap representasi memiliki kelebihan dan kekurangan sesuai dengan analisis yang ingin Anda lakukan.

Analisis

Jenis analisis paling sederhana yang dapat Anda lakukan dengan Slither adalah analisis sintaksis.

Analisis sintaksis

Slither dapat menavigasi melalui berbagai komponen kode dan representasinya untuk menemukan ketidakkonsistenan dan kelemahan menggunakan pendekatan yang mirip dengan pencocokan pola.

Misalnya, detektor berikut mencari masalah terkait sintaksis:

Analisis semantik

Berbeda dengan analisis sintaksis, analisis semantik akan masuk lebih dalam dan menganalisis "makna" dari kode tersebut. Keluarga ini mencakup beberapa jenis analisis yang luas. Mereka mengarah pada hasil yang lebih kuat dan berguna, tetapi juga lebih kompleks untuk ditulis.

Analisis semantik digunakan untuk deteksi kerentanan yang paling canggih.

Analisis ketergantungan data

Sebuah variabel variable_a dikatakan bergantung pada data variable_b jika ada jalur di mana nilai variable_a dipengaruhi oleh variable_b.

Dalam kode berikut, variable_a bergantung pada variable_b:

1// ... // ...
2variable_a = variable_b + 1;

Slither dilengkapi dengan kemampuan ketergantungan data (opens in a new tab) bawaan, berkat representasi perantaranya (dibahas di bagian selanjutnya).

Contoh penggunaan ketergantungan data dapat ditemukan di detektor kesetaraan ketat yang berbahaya (opens in a new tab). Di sini Slither akan mencari perbandingan kesetaraan yang ketat dengan nilai yang berbahaya (incorrect_strict_equality.py#L86-L87 (opens in a new tab)), dan akan memberi tahu pengguna bahwa mereka harus menggunakan >= atau <= daripada ==, untuk mencegah penyerang menjebak kontrak. Antara lain, detektor akan menganggap berbahaya nilai kembalian dari panggilan ke balanceOf(address) (incorrect_strict_equality.py#L63-L64 (opens in a new tab)), dan akan menggunakan mesin ketergantungan data untuk melacak penggunaannya.

Komputasi titik tetap (Fixed-point computation)

Jika analisis Anda menavigasi melalui CFG dan mengikuti tepinya, Anda kemungkinan akan melihat node yang sudah dikunjungi. Misalnya, jika sebuah loop disajikan seperti yang ditunjukkan di bawah ini:

1for(uint i; i < range; ++){
2 variable_a += 1
3}

Analisis Anda perlu mengetahui kapan harus berhenti. Ada dua strategi utama di sini: (1) mengulangi pada setiap node dalam jumlah yang terbatas, (2) menghitung apa yang disebut fixpoint. Fixpoint pada dasarnya berarti bahwa menganalisis node ini tidak memberikan informasi yang berarti.

Contoh penggunaan fixpoint dapat ditemukan di detektor reentrancy: Slither mengeksplorasi node, dan mencari panggilan eksternal, menulis dan membaca ke penyimpanan. Setelah mencapai fixpoint (reentrancy.py#L125-L131 (opens in a new tab)), ia menghentikan eksplorasi, dan menganalisis hasil untuk melihat apakah ada reentrancy, melalui pola reentrancy yang berbeda (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)).

Menulis analisis menggunakan komputasi titik tetap yang efisien membutuhkan pemahaman yang baik tentang bagaimana analisis menyebarkan informasinya.

Representasi perantara

Representasi perantara (IR) adalah bahasa yang dimaksudkan agar lebih mudah menerima analisis statis daripada bahasa aslinya. Slither menerjemahkan Solidity ke IR-nya sendiri: SlithIR (opens in a new tab).

Memahami SlithIR tidak diperlukan jika Anda hanya ingin menulis pemeriksaan dasar. Namun, ini akan berguna jika Anda berencana untuk menulis analisis semantik tingkat lanjut. Printer SlithIR (opens in a new tab) dan SSA (opens in a new tab) akan membantu Anda memahami bagaimana kode diterjemahkan.

Dasar-dasar API

Slither memiliki API yang memungkinkan Anda menjelajahi atribut dasar kontrak dan fungsinya.

Untuk memuat basis kode:

1from slither import Slither
2slither = Slither('/path/to/project')
3

Menjelajahi kontrak dan fungsi

Objek Slither memiliki:

  • contracts (list(Contract): daftar kontrak
  • contracts_derived (list(Contract): daftar kontrak yang tidak diwarisi oleh kontrak lain (subset dari kontrak)
  • get_contract_from_name (str): Mengembalikan kontrak dari namanya

Objek Contract memiliki:

  • name (str): Nama kontrak
  • functions (list(Function)): Daftar fungsi
  • modifiers (list(Modifier)): Daftar fungsi
  • all_functions_called (list(Function/Modifier)): Daftar semua fungsi internal yang dapat dijangkau oleh kontrak
  • inheritance (list(Contract)): Daftar kontrak yang diwariskan
  • get_function_from_signature (str): Mengembalikan Fungsi dari tanda tangannya
  • get_modifier_from_signature (str): Mengembalikan Modifier dari tanda tangannya
  • get_state_variable_from_name (str): Mengembalikan StateVariable dari namanya

Objek Function atau Modifier memiliki:

  • name (str): Nama fungsi
  • contract (contract): kontrak tempat fungsi dideklarasikan
  • nodes (list(Node)): Daftar node yang menyusun CFG dari fungsi/modifier
  • entry_point (Node): Titik masuk CFG
  • variables_read (list(Variable)): Daftar variabel yang dibaca
  • variables_written (list(Variable)): Daftar variabel yang ditulis
  • state_variables_read (list(StateVariable)): Daftar variabel status yang dibaca (subset dari variables`read)
  • state_variables_written (list(StateVariable)): Daftar variabel status yang ditulis (subset dari variables`written)

Pembaruan terakhir halaman: 3 Maret 2026

Apakah tutorial ini membantu?