स्मार्ट अनुबंध बग खोजने के लिए स्लिथर का उपयोग कैसे करें
स्लिथर का उपयोग कैसे करें
इस ट्यूटोरियल का उद्देश्य यह दिखाना है कि स्मार्ट अनुबंधों में स्वचालित रूप से बग खोजने के लिए स्लिथर का उपयोग कैसे करें।
- इंस्टॉलेशन
- कमांड लाइन का उपयोग
- स्टेटिक विश्लेषण का परिचय: स्टेटिक विश्लेषण का संक्षिप्त परिचय
- API: Python API विवरण
इंस्टॉलेशन
स्लिथर के लिए Python >= 3.6 की आवश्यकता होती है। इसे pip के माध्यम से या Docker का उपयोग करके इंस्टॉल किया जा सकता है।
pip के माध्यम से स्लिथर:
pip3 install --user slither-analyzer
Docker के माध्यम से स्लिथर:
docker pull trailofbits/eth-security-toolbox
docker run -it -v "$PWD":/home/trufflecon trailofbits/eth-security-toolbox
अंतिम कमांड एक Docker में eth-security-toolbox चलाता है जिसकी आपकी वर्तमान डायरेक्टरी तक पहुंच होती है। आप अपने होस्ट से फ़ाइलें बदल सकते हैं, और Docker से फ़ाइलों पर टूल चला सकते हैं
Docker के अंदर, चलाएं:
solc-select 0.5.11
cd /home/trufflecon/
स्क्रिप्ट चलाना
Python 3 के साथ Python स्क्रिप्ट चलाने के लिए:
python3 script.py
कमांड लाइन
कमांड लाइन बनाम उपयोगकर्ता-परिभाषित स्क्रिप्ट। स्लिथर पूर्वनिर्धारित डिटेक्टरों के एक सेट के साथ आता है जो कई सामान्य बग ढूंढते हैं। कमांड लाइन से स्लिथर को कॉल करने पर सभी डिटेक्टर चलेंगे, इसके लिए स्टेटिक विश्लेषण के विस्तृत ज्ञान की आवश्यकता नहीं है:
slither project_paths
डिटेक्टरों के अलावा, स्लिथर में इसके प्रिंटर (opens in a new tab) और टूल (opens in a new tab) के माध्यम से कोड समीक्षा क्षमताएं हैं।
निजी डिटेक्टरों और GitHub एकीकरण तक पहुंच प्राप्त करने के लिए crytic.io (opens in a new tab) का उपयोग करें।
स्टेटिक विश्लेषण
स्लिथर स्टेटिक विश्लेषण फ्रेमवर्क की क्षमताओं और डिज़ाइन का वर्णन ब्लॉग पोस्ट (1 (opens in a new tab), 2 (opens in a new tab)) और एक अकादमिक पेपर (opens in a new tab) में किया गया है।
स्टेटिक विश्लेषण विभिन्न प्रकारों में मौजूद है। आप शायद महसूस करते होंगे कि clang (opens in a new tab) और gcc (opens in a new tab) जैसे कंपाइलर इन शोध तकनीकों पर निर्भर करते हैं, लेकिन यह (Infer (opens in a new tab), CodeClimate (opens in a new tab), FindBugs (opens in a new tab) और Frama-C (opens in a new tab) तथा Polyspace (opens in a new tab) जैसे औपचारिक तरीकों पर आधारित टूल का भी आधार है।
हम यहां स्टेटिक विश्लेषण तकनीकों और शोधकर्ताओं की विस्तृत समीक्षा नहीं करेंगे। इसके बजाय, हम इस बात पर ध्यान केंद्रित करेंगे कि स्लिथर कैसे काम करता है, इसे समझने के लिए क्या आवश्यक है ताकि आप बग खोजने और कोड को समझने के लिए इसका अधिक प्रभावी ढंग से उपयोग कर सकें।
कोड प्रतिनिधित्व
डायनेमिक विश्लेषण के विपरीत, जो एक ही निष्पादन पथ के बारे में तर्क करता है, स्टेटिक विश्लेषण एक ही बार में सभी पथों के बारे में तर्क करता है। ऐसा करने के लिए, यह एक अलग कोड प्रतिनिधित्व पर निर्भर करता है। दो सबसे आम एब्सट्रैक्ट सिंटैक्स ट्री (AST) और कंट्रोल फ्लो ग्राफ (CFG) हैं।
एब्सट्रैक्ट सिंटैक्स ट्री (AST)
हर बार जब कंपाइलर कोड को पार्स करता है तो AST का उपयोग किया जाता है। यह शायद सबसे बुनियादी संरचना है जिस पर स्टेटिक विश्लेषण किया जा सकता है।
संक्षेप में, एक AST एक संरचित ट्री है जहां, आमतौर पर, प्रत्येक लीफ में एक चर (variable) या एक स्थिरांक (constant) होता है और आंतरिक नोड ऑपरेंड या कंट्रोल फ्लो ऑपरेशन होते हैं। निम्नलिखित कोड पर विचार करें:
function safeAdd(uint a, uint b) pure internal returns(uint){
if(a + b <= a){
revert();
}
return a + b;
}
संबंधित AST इसमें दिखाया गया है:
स्लिथर solc द्वारा निर्यात किए गए AST का उपयोग करता है।
बनाने में सरल होने के बावजूद, AST एक नेस्टेड संरचना है। कभी-कभी, इसका विश्लेषण करना सबसे सीधा नहीं होता है। उदाहरण के लिए, a + b <= a अभिव्यक्ति द्वारा उपयोग किए जाने वाले ऑपरेशनों की पहचान करने के लिए, आपको पहले <= और फिर + का विश्लेषण करना होगा। एक सामान्य दृष्टिकोण तथाकथित विज़िटर पैटर्न का उपयोग करना है, जो ट्री के माध्यम से पुनरावर्ती (recursively) रूप से नेविगेट करता है। स्लिथर में ExpressionVisitor (opens in a new tab) में एक सामान्य विज़िटर होता है।
निम्नलिखित कोड यह पता लगाने के लिए ExpressionVisitor का उपयोग करता है कि क्या अभिव्यक्ति में कोई जोड़ (addition) शामिल है:
from slither.visitors.expression.expression import ExpressionVisitor
from slither.core.expressions.binary_operation import BinaryOperationType
class HasAddition(ExpressionVisitor):
def result(self):
return self._result
def _post_binary_operation(self, expression):
if expression.type == BinaryOperationType.ADDITION:
self._result = True
visitor = HasAddition(expression) # expression परीक्षण किया जाने वाला व्यंजक है
print(f'The expression {expression} has a addition: {visitor.result()}')
कंट्रोल फ्लो ग्राफ (CFG)
दूसरा सबसे आम कोड प्रतिनिधित्व कंट्रोल फ्लो ग्राफ (CFG) है। जैसा कि इसके नाम से पता चलता है, यह एक ग्राफ-आधारित प्रतिनिधित्व है जो सभी निष्पादन पथों को उजागर करता है। प्रत्येक नोड में एक या कई निर्देश होते हैं। ग्राफ में किनारे (edges) कंट्रोल फ्लो ऑपरेशनों (if/then/else, loop, आदि) का प्रतिनिधित्व करते हैं। हमारे पिछले उदाहरण का CFG है:
CFG वह प्रतिनिधित्व है जिसके ऊपर अधिकांश विश्लेषण बनाए जाते हैं।
कई अन्य कोड प्रतिनिधित्व मौजूद हैं। आप जो विश्लेषण करना चाहते हैं उसके अनुसार प्रत्येक प्रतिनिधित्व के फायदे और कमियां हैं।
विश्लेषण
स्लिथर के साथ आप जो सबसे सरल प्रकार के विश्लेषण कर सकते हैं वे सिंटैक्टिक विश्लेषण हैं।
सिंटैक्स विश्लेषण
स्लिथर पैटर्न मैचिंग-जैसे दृष्टिकोण का उपयोग करके विसंगतियों और खामियों को खोजने के लिए कोड के विभिन्न घटकों और उनके प्रतिनिधित्व के माध्यम से नेविगेट कर सकता है।
उदाहरण के लिए निम्नलिखित डिटेक्टर सिंटैक्स-संबंधित समस्याओं की तलाश करते हैं:
-
स्थिति चर शैडोइंग (State variable shadowing) (opens in a new tab): सभी स्थिति चरों पर पुनरावृति करता है और जांचता है कि क्या कोई विरासत में मिले अनुबंध से किसी चर को शैडो करता है (state.py#L51-L62 (opens in a new tab))
-
गलत ERC-20 इंटरफ़ेस (opens in a new tab): गलत ERC-20 फ़ंक्शन हस्ताक्षरों की तलाश करें (incorrect_erc20_interface.py#L34-L55 (opens in a new tab))
सिमेंटिक विश्लेषण
सिंटैक्स विश्लेषण के विपरीत, एक सिमेंटिक विश्लेषण गहराई में जाएगा और कोड के "अर्थ" का विश्लेषण करेगा। इस परिवार में कुछ व्यापक प्रकार के विश्लेषण शामिल हैं। वे अधिक शक्तिशाली और उपयोगी परिणाम देते हैं, लेकिन उन्हें लिखना भी अधिक जटिल है।
सिमेंटिक विश्लेषण का उपयोग सबसे उन्नत भेद्यता (vulnerability) का पता लगाने के लिए किया जाता है।
डेटा निर्भरता विश्लेषण
एक चर variable_a को variable_b पर डेटा-निर्भर कहा जाता है यदि कोई ऐसा पथ है जिसके लिए variable_a का मान variable_b से प्रभावित होता है।
निम्नलिखित कोड में, variable_a, variable_b पर निर्भर है:
// ...
variable_a = variable_b + 1;
स्लिथर अंतर्निहित डेटा निर्भरता (opens in a new tab) क्षमताओं के साथ आता है, जो इसके मध्यवर्ती प्रतिनिधित्व (बाद के अनुभाग में चर्चा की गई) के कारण संभव है।
डेटा निर्भरता उपयोग का एक उदाहरण खतरनाक सख्त समानता डिटेक्टर (dangerous strict equality detector) (opens in a new tab) में पाया जा सकता है। यहां स्लिथर एक खतरनाक मान (incorrect_strict_equality.py#L86-L87 (opens in a new tab)) के साथ सख्त समानता तुलना की तलाश करेगा, और उपयोगकर्ता को सूचित करेगा कि उसे == के बजाय >= या <= का उपयोग करना चाहिए, ताकि किसी हमलावर को अनुबंध को फंसाने से रोका जा सके। अन्य बातों के अलावा, डिटेक्टर balanceOf(address) (incorrect_strict_equality.py#L63-L64 (opens in a new tab)) पर कॉल के रिटर्न मान को खतरनाक मानेगा, और इसके उपयोग को ट्रैक करने के लिए डेटा निर्भरता इंजन का उपयोग करेगा।
फिक्स्ड-पॉइंट गणना
यदि आपका विश्लेषण CFG के माध्यम से नेविगेट करता है और किनारों का अनुसरण करता है, तो आपको पहले से देखे गए नोड दिखाई देने की संभावना है। उदाहरण के लिए, यदि कोई लूप नीचे दिखाए अनुसार प्रस्तुत किया गया है:
for(uint i; i < range; ++){
variable_a += 1
}
आपके विश्लेषण को यह जानना होगा कि कब रुकना है। यहां दो मुख्य रणनीतियां हैं: (1) प्रत्येक नोड पर एक सीमित संख्या में पुनरावृति करें, (2) एक तथाकथित फिक्सपॉइंट (fixpoint) की गणना करें। फिक्सपॉइंट का मूल रूप से मतलब है कि इस नोड का विश्लेषण करने से कोई सार्थक जानकारी नहीं मिलती है।
उपयोग किए गए फिक्सपॉइंट का एक उदाहरण पुन:प्रवेश (reentrancy) डिटेक्टरों में पाया जा सकता है: स्लिथर नोड्स का अन्वेषण करता है, और बाहरी कॉल, स्टोरेज में लिखने और पढ़ने की तलाश करता है। एक बार जब यह एक फिक्सपॉइंट (reentrancy.py#L125-L131 (opens in a new tab)) पर पहुंच जाता है, तो यह अन्वेषण को रोक देता है, और विभिन्न पुन:प्रवेश पैटर्न (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)) के माध्यम से यह देखने के लिए परिणामों का विश्लेषण करता है कि क्या कोई पुन:प्रवेश मौजूद है।
कुशल फिक्स्ड पॉइंट गणना का उपयोग करके विश्लेषण लिखने के लिए इस बात की अच्छी समझ की आवश्यकता होती है कि विश्लेषण अपनी जानकारी का प्रसार कैसे करता है।
मध्यवर्ती प्रतिनिधित्व
एक मध्यवर्ती प्रतिनिधित्व (IR) एक ऐसी भाषा है जो मूल भाषा की तुलना में स्टेटिक विश्लेषण के लिए अधिक अनुकूल है। स्लिथर Solidity को अपने स्वयं के IR में अनुवादित करता है: SlithIR (opens in a new tab)।
यदि आप केवल बुनियादी जांच लिखना चाहते हैं तो SlithIR को समझना आवश्यक नहीं है। हालांकि, यदि आप उन्नत सिमेंटिक विश्लेषण लिखने की योजना बना रहे हैं तो यह काम आएगा। SlithIR (opens in a new tab) और SSA (opens in a new tab) प्रिंटर आपको यह समझने में मदद करेंगे कि कोड का अनुवाद कैसे किया जाता है।
API की मूल बातें
स्लिथर में एक API है जो आपको अनुबंध और उसके कार्यों की बुनियादी विशेषताओं का पता लगाने देता है।
कोडबेस लोड करने के लिए:
from slither import Slither
slither = Slither('/path/to/project')
अनुबंधों और कार्यों की खोज
एक Slither ऑब्जेक्ट में होता है:
contracts (list(Contract): अनुबंधों की सूचीcontracts_derived (list(Contract): उन अनुबंधों की सूची जो किसी अन्य अनुबंध द्वारा विरासत में नहीं मिले हैं (अनुबंधों का उपसमुच्चय)get_contract_from_name (str): इसके नाम से एक अनुबंध लौटाएं
एक Contract ऑब्जेक्ट में होता है:
name (str): अनुबंध का नामfunctions (list(Function)): कार्यों की सूचीmodifiers (list(Modifier)): कार्यों की सूचीall_functions_called (list(Function/Modifier)): अनुबंध द्वारा पहुंचने योग्य सभी आंतरिक कार्यों की सूचीinheritance (list(Contract)): विरासत में मिले अनुबंधों की सूचीget_function_from_signature (str): इसके हस्ताक्षर से एक फ़ंक्शन लौटाएंget_modifier_from_signature (str): इसके हस्ताक्षर से एक संशोधक (Modifier) लौटाएंget_state_variable_from_name (str): इसके नाम से एक स्थिति चर (StateVariable) लौटाएं
एक Function या एक Modifier ऑब्जेक्ट में होता है:
name (str): फ़ंक्शन का नामcontract (contract): वह अनुबंध जहां फ़ंक्शन घोषित किया गया हैnodes (list(Node)): फ़ंक्शन/संशोधक के CFG की रचना करने वाले नोड्स की सूचीentry_point (Node): CFG का प्रवेश बिंदुvariables_read (list(Variable)): पढ़े गए चरों की सूचीvariables_written (list(Variable)): लिखे गए चरों की सूचीstate_variables_read (list(StateVariable)): पढ़े गए स्थिति चरों की सूची (पढ़े गए चरों का उपसमुच्चय)state_variables_written (list(StateVariable)): लिखे गए स्थिति चरों की सूची (लिखे गए चरों का उपसमुच्चय)

