मुख्य आशयावर जा

कॉन्ट्रॅक्टचे रिव्हर्स इंजिनिअरिंग

evm
ऑपकोड्स
प्रगत
ओरी पोमेरँट्झ
30 डिसेंबर, 2021
30 मिनिटांचे वाचन

परिचय

ब्लॉकचेनवर कोणतीही गुपिते नसतात, जे काही घडते ते सुसंगत, पडताळणीयोग्य आणि सार्वजनिकरित्या उपलब्ध असते. आदर्शपणे, कॉन्ट्रॅक्ट्सचा सोर्स कोड Etherscan वर प्रकाशित आणि सत्यापित केलेला असावा (opens in a new tab). तथापि, नेहमीच असे नसते (opens in a new tab). या लेखामध्ये तुम्ही सोर्स कोड नसलेले कॉन्ट्रॅक्ट पाहून कॉन्ट्रॅक्ट्सचे रिव्हर्स इंजिनिअरिंग कसे करायचे ते शिकाल, 0x2510c039cc3b061d79e564b38836da87e31b342f (opens in a new tab).

रिव्हर्स कंपायलर उपलब्ध आहेत, परंतु ते नेहमीच वापरण्यायोग्य परिणाम (opens in a new tab) देत नाहीत. या लेखामध्ये तुम्ही ऑपकोड्सवरून (opens in a new tab) मॅन्युअली रिव्हर्स इंजिनिअरिंग कसे करायचे आणि कॉन्ट्रॅक्ट कसे समजून घ्यायचे, तसेच डीकंपायलरच्या परिणामांचा अर्थ कसा लावायचा हे शिकाल.

हा लेख समजून घेण्यासाठी तुम्हाला EVM च्या मूलभूत गोष्टी आधीच माहित असाव्यात आणि EVM असेंबलरची किमान थोडीफार माहिती असावी. तुम्ही या विषयांबद्दल येथे वाचू शकता (opens in a new tab).

एक्झिक्युटेबल कोड तयार करा

तुम्ही कॉन्ट्रॅक्टसाठी Etherscan वर जाऊन, Contract टॅबवर क्लिक करून आणि नंतर Switch to Opcodes View वर क्लिक करून ऑपकोड्स मिळवू शकता. तुम्हाला असे दृश्य मिळते ज्यामध्ये प्रति ओळ एक ऑपकोड असतो.

Opcode View from Etherscan

तथापि, जंप्स समजून घेण्यासाठी, तुम्हाला प्रत्येक ऑपकोड कोडमध्ये कुठे स्थित आहे हे माहित असणे आवश्यक आहे. हे करण्यासाठी, एक मार्ग म्हणजे Google Spreadsheet उघडणे आणि स्तंभ C मध्ये ऑपकोड्स पेस्ट करणे. तुम्ही या आधीच तयार केलेल्या स्प्रेडशीटची प्रत बनवून पुढील पायऱ्या वगळू शकता (opens in a new tab).

पुढची पायरी म्हणजे योग्य कोड लोकेशन्स मिळवणे जेणेकरून आपण जंप्स समजून घेऊ शकू. आपण ऑपकोडचा आकार स्तंभ B मध्ये आणि लोकेशन (हेक्साडेसिमलमध्ये) स्तंभ A मध्ये ठेवू. सेल B1 मध्ये हे फंक्शन टाइप करा आणि नंतर कोडच्या शेवटपर्यंत, उर्वरित स्तंभ B साठी ते कॉपी आणि पेस्ट करा. तुम्ही हे केल्यानंतर स्तंभ B लपवू शकता.

=1+IF(REGEXMATCH(C1,"PUSH"),REGEXEXTRACT(C1,"PUSH(\d+)"),0)

प्रथम हे फंक्शन स्वतः ऑपकोडसाठी एक बाइट जोडते, आणि नंतर PUSH शोधते. Push ऑपकोड्स विशेष असतात कारण पुश केल्या जाणाऱ्या मूल्यासाठी त्यांच्याकडे अतिरिक्त बाइट्स असणे आवश्यक असते. जर ऑपकोड PUSH असेल, तर आपण बाइट्सची संख्या काढतो आणि ती जोडतो.

A1 मध्ये पहिला ऑफसेट, शून्य ठेवा. त्यानंतर, A2 मध्ये, हे फंक्शन ठेवा आणि पुन्हा उर्वरित स्तंभ A साठी ते कॉपी आणि पेस्ट करा:

=dec2hex(hex2dec(A1)+B1)

आपल्याला हेक्साडेसिमल मूल्य देण्यासाठी या फंक्शनची आवश्यकता आहे कारण जंप्सच्या आधी पुश केलेली मूल्ये (JUMP आणि JUMPI) आपल्याला हेक्साडेसिमलमध्ये दिली जातात.

एंट्री पॉईंट (0x00)

कॉन्ट्रॅक्ट्स नेहमी पहिल्या बाइटपासून कार्यान्वित केले जातात. हा कोडचा सुरुवातीचा भाग आहे:

ऑफसेटऑपकोडस्टॅक (ऑपकोडनंतर)
0PUSH1 0x800x80
2PUSH1 0x400x40, 0x80
4MSTOREरिक्त
5PUSH1 0x040x04
7CALLDATASIZECALLDATASIZE 0x04
8LTCALLDATASIZE<4
9PUSH2 0x005e0x5E CALLDATASIZE<4
CJUMPIरिक्त

हा कोड दोन गोष्टी करतो:

  1. मेमरी लोकेशन्स 0x40-0x5F वर 32 बाइट मूल्य म्हणून 0x80 लिहा (0x80 हे 0x5F मध्ये साठवले जाते, आणि 0x40-0x5E सर्व शून्य असतात).
  2. कॉल डेटाचा आकार वाचा. साधारणपणे Ethereum कॉन्ट्रॅक्टसाठी कॉल डेटा ABI (अॅप्लिकेशन बायनरी इंटरफेस) (opens in a new tab) चे अनुसरण करतो, ज्यासाठी फंक्शन सिलेक्टरसाठी किमान चार बाइट्स आवश्यक असतात. जर कॉल डेटाचा आकार चारापेक्षा कमी असेल, तर 0x5E वर जा.

Flowchart for this portion

0x5E वरील हँडलर (नॉन-ABI कॉल डेटासाठी)

ऑफसेटऑपकोड
5EJUMPDEST
5FCALLDATASIZE
60PUSH2 0x007c
63JUMPI

हा स्निपेट JUMPDEST ने सुरू होतो. जर तुम्ही JUMPDEST नसलेल्या ऑपकोडवर उडी मारली तर EVM (इथेरियम व्हर्च्युअल मशीन) प्रोग्राम्स अपवाद (exception) थ्रो करतात. त्यानंतर ते CALLDATASIZE पाहते, आणि जर ते "true" असेल (म्हणजेच, शून्य नसेल) तर 0x7C वर उडी मारते. आपण त्याबद्दल खाली पाहू.

ऑफसेटऑपकोडस्टॅक (ऑपकोडनंतर)
64CALLVALUEकॉलद्वारे प्रदान केलेले . Solidity मध्ये याला msg.value म्हटले जाते
65PUSH1 0x066 CALLVALUE
67PUSH1 0x000 6 CALLVALUE
69DUP3CALLVALUE 0 6 CALLVALUE
6ADUP36 CALLVALUE 0 6 CALLVALUE
6BSLOADStorage[6] CALLVALUE 0 6 CALLVALUE

म्हणून जेव्हा कोणताही कॉल डेटा नसतो तेव्हा आपण Storage[6] चे मूल्य वाचतो. हे मूल्य काय आहे हे आपल्याला अद्याप माहित नाही, परंतु आपण अशा व्यवहारांचा शोध घेऊ शकतो जे कॉन्ट्रॅक्टला कोणत्याही कॉल डेटाशिवाय प्राप्त झाले आहेत. जे व्यवहार कोणत्याही कॉल डेटाशिवाय (आणि त्यामुळे कोणत्याही पद्धतीशिवाय) फक्त ETH चे हस्तांतरण करतात, त्यांच्यासाठी Etherscan मध्ये Transfer ही पद्धत असते. किंबहुना, कॉन्ट्रॅक्टला प्राप्त झालेला अगदी पहिला व्यवहार (opens in a new tab) हे एक हस्तांतरण आहे.

जर आपण त्या व्यवहारामध्ये पाहिले आणि Click to see More वर क्लिक केले, तर आपल्याला दिसेल की कॉल डेटा, ज्याला इनपुट डेटा म्हटले जाते, तो खरोखरच रिक्त आहे (0x). हे देखील लक्षात घ्या की मूल्य 1.559 ETH आहे, जे नंतर संबंधित असेल.

The call data is empty

पुढे, State टॅबवर क्लिक करा आणि आपण ज्या कॉन्ट्रॅक्टचे रिव्हर्स इंजिनिअरिंग करत आहोत (0x2510...) त्याचा विस्तार करा. तुम्ही पाहू शकता की व्यवहारादरम्यान Storage[6] बदलले आहे, आणि जर तुम्ही Hex ला Number मध्ये बदलले, तर तुम्हाला दिसेल की ते 1,559,000,000,000,000,000 झाले आहे, जे wei मध्ये हस्तांतरित केलेले मूल्य आहे (मी स्पष्टतेसाठी स्वल्पविराम जोडले आहेत), जे पुढील कॉन्ट्रॅक्ट मूल्याशी संबंधित आहे.

Storage[6] मधील बदल

जर आपण त्याच कालावधीतील इतर Transfer व्यवहारांमुळे (opens in a new tab) झालेल्या स्थितीतील बदलांमध्ये पाहिले तर आपल्याला दिसेल की Storage[6] ने काही काळासाठी कॉन्ट्रॅक्टच्या मूल्याचा मागोवा घेतला. सध्या आपण याला Value* म्हणू. अस्टरिस्क (*) आपल्याला आठवण करून देतो की हे व्हेरिएबल काय करते हे आपल्याला अद्याप माहित नाही, परंतु ते केवळ कॉन्ट्रॅक्टच्या मूल्याचा मागोवा घेण्यासाठी असू शकत नाही कारण जेव्हा तुम्ही ADDRESS BALANCE वापरून तुमच्या खात्यातील शिल्लक मिळवू शकता, तेव्हा स्टोरेज वापरण्याची कोणतीही आवश्यकता नाही, जे खूप महाग आहे. पहिला ऑपकोड कॉन्ट्रॅक्टचा स्वतःचा पत्ता पुश करतो. दुसरा ऑपकोड स्टॅकच्या शीर्षस्थानी असलेला पत्ता वाचतो आणि त्या पत्त्याच्या शिल्लक रकमेने तो बदलतो.

ऑफसेटऑपकोडस्टॅक
6CPUSH2 0x00750x75 Value* CALLVALUE 0 6 CALLVALUE
6FSWAP2CALLVALUE Value* 0x75 0 6 CALLVALUE
70SWAP1Value* CALLVALUE 0x75 0 6 CALLVALUE
71PUSH2 0x01a70x01A7 Value* CALLVALUE 0x75 0 6 CALLVALUE
74JUMP

आपण जंप डेस्टिनेशनवर या कोडचा मागोवा घेणे सुरू ठेवू.

ऑफसेटऑपकोडस्टॅक
1A7JUMPDESTValue* CALLVALUE 0x75 0 6 CALLVALUE
1A8PUSH1 0x000x00 Value* CALLVALUE 0x75 0 6 CALLVALUE
1AADUP3CALLVALUE 0x00 Value* CALLVALUE 0x75 0 6 CALLVALUE
1ABNOT2^256-CALLVALUE-1 0x00 Value* CALLVALUE 0x75 0 6 CALLVALUE

NOT हे बिटवाइज आहे, त्यामुळे ते कॉल व्हॅल्यूमधील प्रत्येक बिटचे मूल्य उलट करते.

ऑफसेटऑपकोडस्टॅक
1ACDUP3Value* 2^256-CALLVALUE-1 0x00 Value* CALLVALUE 0x75 0 6 CALLVALUE
1ADGTValue*>2^256-CALLVALUE-1 0x00 Value* CALLVALUE 0x75 0 6 CALLVALUE
1AEISZEROValue*<=2^256-CALLVALUE-1 0x00 Value* CALLVALUE 0x75 0 6 CALLVALUE
1AFPUSH2 0x01df0x01DF Value*<=2^256-CALLVALUE-1 0x00 Value* CALLVALUE 0x75 0 6 CALLVALUE
1B2JUMPI

जर Value* हे 2^256-CALLVALUE-1 पेक्षा लहान किंवा त्याच्या समान असेल तर आपण उडी मारतो. हे ओव्हरफ्लो टाळण्यासाठी लॉजिक असल्यासारखे दिसते. आणि खरोखरच, आपण पाहतो की काही निरर्थक ऑपरेशन्स नंतर (उदाहरणार्थ, मेमरीमध्ये लिहिलेले हटवले जाणार आहे) ऑफसेट 0x01DE वर जर ओव्हरफ्लो आढळला तर कॉन्ट्रॅक्ट पूर्ववत होते (reverts), जे सामान्य वर्तन आहे.

लक्षात घ्या की असा ओव्हरफ्लो होण्याची शक्यता अत्यंत कमी आहे, कारण त्यासाठी कॉल व्हॅल्यू अधिक Value* हे 2^256 wei च्या तुलनेत असणे आवश्यक आहे, जे सुमारे 10^59 ETH आहे. लेखनाच्या वेळी, एकूण ETH पुरवठा दोनशे दशलक्षांपेक्षा कमी आहे (opens in a new tab).

ऑफसेटऑपकोडस्टॅक
1DFJUMPDEST0x00 Value* CALLVALUE 0x75 0 6 CALLVALUE
1E0POPValue* CALLVALUE 0x75 0 6 CALLVALUE
1E1ADDValue*+CALLVALUE 0x75 0 6 CALLVALUE
1E2SWAP10x75 Value*+CALLVALUE 0 6 CALLVALUE
1E3JUMP

जर आपण येथे पोहोचलो, तर Value* + CALLVALUE मिळवा आणि ऑफसेट 0x75 वर उडी मारा.

ऑफसेटऑपकोडस्टॅक
75JUMPDESTValue*+CALLVALUE 0 6 CALLVALUE
76SWAP10 Value*+CALLVALUE 6 CALLVALUE
77SWAP26 Value*+CALLVALUE 0 CALLVALUE
78SSTORE0 CALLVALUE

जर आपण येथे पोहोचलो (ज्यासाठी कॉल डेटा रिक्त असणे आवश्यक आहे) तर आपण Value* मध्ये कॉल व्हॅल्यू जोडतो. हे आपण Transfer व्यवहार काय करतात याबद्दल जे म्हणतो त्याच्याशी सुसंगत आहे.

ऑफसेटऑपकोड
79POP
7APOP
7BSTOP

शेवटी, स्टॅक साफ करा (जे आवश्यक नाही) आणि व्यवहाराच्या यशस्वी समाप्तीचा संकेत द्या.

थोडक्यात सांगायचे तर, सुरुवातीच्या कोडसाठी येथे एक फ्लोचार्ट आहे.

Entry point flowchart

0x7C वरील हँडलर

या हँडलरचे काम काय आहे हे मी मुद्दाम शीर्षकात दिलेले नाही. हा विशिष्ट कॉन्ट्रॅक्ट कसा काम करतो हे शिकवणे हा उद्देश नाही, तर कॉन्ट्रॅक्ट्स रिव्हर्स इंजिनिअर कसे करायचे हे शिकवणे हा आहे. मी जसे कोड फॉलो करून शिकलो, तसेच तुम्हीही ते काय करते हे शिकाल.

आपण येथे अनेक ठिकाणांहून पोहोचतो:

  • जर 1, 2, किंवा 3 बाइट्सचा कॉल डेटा असेल (ऑफसेट 0x63 वरून)
  • जर मेथडची स्वाक्षरी अज्ञात असेल (ऑफसेट 0x42 आणि 0x5D वरून)
ऑफसेटऑपकोडस्टॅक
7CJUMPDEST
7DPUSH1 0x000x00
7FPUSH2 0x009d0x9D 0x00
82PUSH1 0x030x03 0x9D 0x00
84SLOADStorage[3] 0x9D 0x00

हा आणखी एक स्टोरेज सेल आहे, जो मला कोणत्याही व्यवहारांमध्ये सापडला नाही त्यामुळे त्याचा अर्थ काय आहे हे जाणून घेणे कठीण आहे. खालील कोड हे अधिक स्पष्ट करेल.

ऑफसेटऑपकोडस्टॅक
85PUSH20 0xffffffffffffffffffffffffffffffffffffffff0xff....ff Storage[3] 0x9D 0x00
9AANDStorage[3]-पत्ता-म्हणून 0x9D 0x00

हे ऑपकोड्स आपण Storage[3] मधून वाचलेले मूल्य 160 बिट्सपर्यंत ट्रंकेट करतात, जी एका Ethereum पत्त्याची लांबी असते.

ऑफसेटऑपकोडस्टॅक
9BSWAP10x9D Storage[3]-पत्ता-म्हणून 0x00
9CJUMPStorage[3]-पत्ता-म्हणून 0x00

ही जंप अनावश्यक आहे, कारण आपण पुढच्या ऑपकोडवर जात आहोत. हा कोड जितका गॅस-कार्यक्षम असू शकतो तितका नाही.

ऑफसेटऑपकोडस्टॅक
9DJUMPDESTStorage[3]-पत्ता-म्हणून 0x00
9ESWAP10x00 Storage[3]-पत्ता-म्हणून
9FPOPStorage[3]-पत्ता-म्हणून
A0PUSH1 0x400x40 Storage[3]-पत्ता-म्हणून
A2MLOADMem[0x40] Storage[3]-पत्ता-म्हणून

कोडच्या अगदी सुरुवातीला आपण Mem[0x40] ला 0x80 वर सेट करतो. जर आपण नंतर 0x40 शोधले, तर आपल्याला दिसेल की आपण ते बदलत नाही - त्यामुळे आपण असे गृहीत धरू शकतो की ते 0x80 आहे.

ऑफसेटऑपकोडस्टॅक
A3CALLDATASIZECALLDATASIZE 0x80 Storage[3]-पत्ता-म्हणून
A4PUSH1 0x000x00 CALLDATASIZE 0x80 Storage[3]-पत्ता-म्हणून
A6DUP30x80 0x00 CALLDATASIZE 0x80 Storage[3]-पत्ता-म्हणून
A7CALLDATACOPY0x80 Storage[3]-पत्ता-म्हणून

सर्व कॉल डेटा मेमरीमध्ये कॉपी करा, 0x80 पासून सुरू करून.

ऑफसेटऑपकोडस्टॅक
A8PUSH1 0x000x00 0x80 Storage[3]-पत्ता-म्हणून
AADUP10x00 0x00 0x80 Storage[3]-पत्ता-म्हणून
ABCALLDATASIZECALLDATASIZE 0x00 0x00 0x80 Storage[3]-पत्ता-म्हणून
ACDUP40x80 CALLDATASIZE 0x00 0x00 0x80 Storage[3]-पत्ता-म्हणून
ADDUP6Storage[3]-पत्ता-म्हणून 0x80 CALLDATASIZE 0x00 0x00 0x80 Storage[3]-पत्ता-म्हणून
AEGASGAS Storage[3]-पत्ता-म्हणून 0x80 CALLDATASIZE 0x00 0x00 0x80 Storage[3]-पत्ता-म्हणून
AFDELEGATE_CALL

आता गोष्टी अधिक स्पष्ट झाल्या आहेत. हा कॉन्ट्रॅक्ट एक प्रॉक्सी (opens in a new tab) म्हणून काम करू शकतो, जो खरे काम करण्यासाठी Storage[3] मधील पत्त्याला कॉल करतो. DELEGATE_CALL एका वेगळ्या कॉन्ट्रॅक्टला कॉल करतो, परंतु त्याच स्टोरेजमध्ये राहतो. याचा अर्थ असा की डेलिगेटेड कॉन्ट्रॅक्ट, ज्यासाठी आपण प्रॉक्सी आहोत, तो त्याच स्टोरेज स्पेसमध्ये प्रवेश करतो. कॉलसाठी पॅरामीटर्स खालीलप्रमाणे आहेत:

  • गॅस: सर्व उर्वरित गॅस
  • कॉल केलेला पत्ता: Storage[3]-पत्ता-म्हणून
  • कॉल डेटा: 0x80 पासून सुरू होणारे CALLDATASIZE बाइट्स, जिथे आपण मूळ कॉल डेटा ठेवला होता
  • रिटर्न डेटा: काहीही नाही (0x00 - 0x00) आपल्याला रिटर्न डेटा इतर मार्गांनी मिळेल (खाली पहा)
ऑफसेटऑपकोडस्टॅक
B0RETURNDATASIZERETURNDATASIZE (((कॉल यश/अपयश))) 0x80 Storage[3]-पत्ता-म्हणून
B1DUP1RETURNDATASIZE RETURNDATASIZE (((कॉल यश/अपयश))) 0x80 Storage[3]-पत्ता-म्हणून
B2PUSH1 0x000x00 RETURNDATASIZE RETURNDATASIZE (((कॉल यश/अपयश))) 0x80 Storage[3]-पत्ता-म्हणून
B4DUP50x80 0x00 RETURNDATASIZE RETURNDATASIZE (((कॉल यश/अपयश))) 0x80 Storage[3]-पत्ता-म्हणून
B5RETURNDATACOPYRETURNDATASIZE (((कॉल यश/अपयश))) 0x80 Storage[3]-पत्ता-म्हणून

येथे आपण सर्व रिटर्न डेटा 0x80 पासून सुरू होणाऱ्या मेमरी बफरमध्ये कॉपी करतो.

ऑफसेटऑपकोडस्टॅक
B6DUP2(((कॉल यश/अपयश))) RETURNDATASIZE (((कॉल यश/अपयश))) 0x80 Storage[3]-पत्ता-म्हणून
B7DUP1(((कॉल यश/अपयश))) (((कॉल यश/अपयश))) RETURNDATASIZE (((कॉल यश/अपयश))) 0x80 Storage[3]-पत्ता-म्हणून
B8ISZERO(((कॉल अयशस्वी झाला का))) (((कॉल यश/अपयश))) RETURNDATASIZE (((कॉल यश/अपयश))) 0x80 Storage[3]-पत्ता-म्हणून
B9PUSH2 0x00c00xC0 (((कॉल अयशस्वी झाला का))) (((कॉल यश/अपयश))) RETURNDATASIZE (((कॉल यश/अपयश))) 0x80 Storage[3]-पत्ता-म्हणून
BCJUMPI(((कॉल यश/अपयश))) RETURNDATASIZE (((कॉल यश/अपयश))) 0x80 Storage[3]-पत्ता-म्हणून
BDDUP2RETURNDATASIZE (((कॉल यश/अपयश))) RETURNDATASIZE (((कॉल यश/अपयश))) 0x80 Storage[3]-पत्ता-म्हणून
BEDUP50x80 RETURNDATASIZE (((कॉल यश/अपयश))) RETURNDATASIZE (((कॉल यश/अपयश))) 0x80 Storage[3]-पत्ता-म्हणून
BFRETURN

त्यामुळे कॉलनंतर आपण रिटर्न डेटा 0x80 - 0x80+RETURNDATASIZE या बफरमध्ये कॉपी करतो, आणि जर कॉल यशस्वी झाला तर आपण नेमक्या त्याच बफरसह RETURN करतो.

DELEGATECALL अयशस्वी

जर आपण येथे, 0xC0 वर पोहोचलो, तर याचा अर्थ असा की आपण कॉल केलेला कॉन्ट्रॅक्ट पूर्ववत (revert) झाला. आपण त्या कॉन्ट्रॅक्टसाठी फक्त एक प्रॉक्सी असल्यामुळे, आपल्याला तोच डेटा परत करायचा आहे आणि पूर्ववत देखील करायचे आहे.

ऑफसेटऑपकोडस्टॅक
C0JUMPDEST(((कॉल यश/अपयश))) RETURNDATASIZE (((कॉल यश/अपयश))) 0x80 Storage[3]-पत्ता-म्हणून
C1DUP2RETURNDATASIZE (((कॉल यश/अपयश))) RETURNDATASIZE (((कॉल यश/अपयश))) 0x80 Storage[3]-पत्ता-म्हणून
C2DUP50x80 RETURNDATASIZE (((कॉल यश/अपयश))) RETURNDATASIZE (((कॉल यश/अपयश))) 0x80 Storage[3]-पत्ता-म्हणून
C3REVERT

त्यामुळे आपण आधी RETURN साठी वापरलेल्या त्याच बफरसह REVERT करतो: 0x80 - 0x80+RETURNDATASIZE

Call to proxy flowchart

ABI कॉल्स

जर कॉल डेटाचा आकार चार बाइट्स किंवा त्याहून अधिक असेल, तर हा एक वैध ABI कॉल असू शकतो.

ऑफसेटऑपकोडस्टॅक
DPUSH1 0x000x00
FCALLDATALOAD(((कॉल डेटाचा पहिला शब्द (256 बिट्स))))
10PUSH1 0xe00xE0 (((कॉल डेटाचा पहिला शब्द (256 बिट्स))))
12SHR(((कॉल डेटाचे पहिले 32 बिट्स (4 बाइट्स))))

Etherscan आपल्याला सांगते की 1C हा एक अज्ञात ऑपकोड आहे, कारण Etherscan ने हे वैशिष्ट्य लिहिल्यानंतर तो जोडला गेला होता (opens in a new tab) आणि त्यांनी तो अद्यतनित केलेला नाही. एक अद्ययावत ऑपकोड तक्ता (opens in a new tab) आपल्याला दाखवतो की हे शिफ्ट राईट (shift right) आहे

ऑफसेटऑपकोडस्टॅक
13DUP1(((कॉल डेटाचे पहिले 32 बिट्स (4 बाइट्स)))) (((कॉल डेटाचे पहिले 32 बिट्स (4 बाइट्स))))
14PUSH4 0x3cd8045e0x3CD8045E (((कॉल डेटाचे पहिले 32 बिट्स (4 बाइट्स)))) (((कॉल डेटाचे पहिले 32 बिट्स (4 बाइट्स))))
19GT0x3CD8045E>first-32-bits-of-the-call-data (((कॉल डेटाचे पहिले 32 बिट्स (4 बाइट्स))))
1APUSH2 0x00430x43 0x3CD8045E>first-32-bits-of-the-call-data (((कॉल डेटाचे पहिले 32 बिट्स (4 बाइट्स))))
1DJUMPI(((कॉल डेटाचे पहिले 32 बिट्स (4 बाइट्स))))

पद्धत स्वाक्षरी जुळणी चाचण्यांना अशा प्रकारे दोन भागांत विभागल्याने सरासरी निम्म्या चाचण्या वाचतात. यानंतर लगेच येणारा कोड आणि 0x43 मधील कोड समान पॅटर्न फॉलो करतात: कॉल डेटाचे पहिले 32 बिट्स DUP1 करा, PUSH4 (((method signature> करा, समानतेची तपासणी करण्यासाठी EQ चालवा, आणि नंतर पद्धत स्वाक्षरी जुळत असल्यास JUMPI करा. येथे पद्धत स्वाक्षऱ्या, त्यांचे पत्ते आणि ज्ञात असल्यास संबंधित पद्धत व्याख्या (opens in a new tab) दिली आहे:

पद्धतपद्धत स्वाक्षरीजंप करण्यासाठी ऑफसेट
splitter() (opens in a new tab)0x3cd8045e0x0103
???0x81e580d30x0138
currentWindow() (opens in a new tab)0xba0bafb40x0158
???0x1f1358230x00C4
merkleRoot() (opens in a new tab)0x2eb4a7ab0x00ED

जर कोणतीही जुळणी आढळली नाही, तर कोड 0x7C वरील प्रॉक्सी हँडलरकडे जंप करतो, या आशेने की आपण ज्या कॉन्ट्रॅक्टसाठी प्रॉक्सी आहोत त्यात जुळणी असेल.

ABI calls flowchart

splitter()

ऑफसेटऑपकोडस्टॅक
103JUMPDEST
104CALLVALUECALLVALUE
105DUP1CALLVALUE CALLVALUE
106ISZEROCALLVALUE==0 CALLVALUE
107PUSH2 0x010f0x010F CALLVALUE==0 CALLVALUE
10AJUMPICALLVALUE
10BPUSH1 0x000x00 CALLVALUE
10DDUP10x00 0x00 CALLVALUE
10EREVERT

हे फंक्शन सर्वात आधी हे तपासते की कॉलने कोणतेही ETH पाठवले नाही. हे फंक्शन payable (opens in a new tab) नाही. जर कोणी आपल्याला ETH पाठवले असेल तर ती नक्कीच चूक असावी आणि ते ETH अशा ठिकाणी अडकून राहू नये जिथून ते परत मिळवता येणार नाही, हे टाळण्यासाठी आपण REVERT करू इच्छितो.

ऑफसेटऑपकोडस्टॅक
10FJUMPDEST
110POP
111PUSH1 0x030x03
113SLOAD(((Storage[3] म्हणजेच ते कॉन्ट्रॅक्ट ज्यासाठी आपण प्रॉक्सी आहोत)))
114PUSH1 0x400x40 (((Storage[3] म्हणजेच ते कॉन्ट्रॅक्ट ज्यासाठी आपण प्रॉक्सी आहोत)))
116MLOAD0x80 (((Storage[3] म्हणजेच ते कॉन्ट्रॅक्ट ज्यासाठी आपण प्रॉक्सी आहोत)))
117PUSH20 0xffffffffffffffffffffffffffffffffffffffff0xFF...FF 0x80 (((Storage[3] म्हणजेच ते कॉन्ट्रॅक्ट ज्यासाठी आपण प्रॉक्सी आहोत)))
12CSWAP10x80 0xFF...FF (((Storage[3] म्हणजेच ते कॉन्ट्रॅक्ट ज्यासाठी आपण प्रॉक्सी आहोत)))
12DSWAP2(((Storage[3] म्हणजेच ते कॉन्ट्रॅक्ट ज्यासाठी आपण प्रॉक्सी आहोत))) 0xFF...FF 0x80
12EANDProxyAddr 0x80
12FDUP20x80 ProxyAddr 0x80
130MSTORE0x80

आणि 0x80 मध्ये आता प्रॉक्सी पत्ता आहे

ऑफसेटऑपकोडस्टॅक
131PUSH1 0x200x20 0x80
133ADD0xA0
134PUSH2 0x00e40xE4 0xA0
137JUMP0xA0

E4 कोड

आपण या ओळी पहिल्यांदाच पाहत आहोत, परंतु त्या इतर पद्धतींसोबत (methods) सामायिक केल्या आहेत (खाली पहा). म्हणून आपण स्टॅकमधील मूल्याला X म्हणूया, आणि फक्त एवढे लक्षात ठेवा की splitter() मध्ये या X चे मूल्य 0xA0 आहे.

ऑफसेटऑपकोडस्टॅक
E4JUMPDESTX
E5PUSH1 0x400x40 X
E7MLOAD0x80 X
E8DUP10x80 0x80 X
E9SWAP2X 0x80 0x80
EASUBX-0x80 0x80
EBSWAP10x80 X-0x80
ECRETURN

तर हा कोड स्टॅकमध्ये (X) मेमरी पॉइंटर प्राप्त करतो, आणि कॉन्ट्रॅक्टला 0x80 - X असलेल्या बफरसह RETURN करण्यास प्रवृत्त करतो.

splitter() च्या बाबतीत, हे तो पत्ता परत करते ज्यासाठी आपण प्रॉक्सी आहोत. RETURN 0x80-0x9F मधील बफर परत करते, जिथे आपण हा डेटा लिहिला होता (वरील ऑफसेट 0x130).

currentWindow()

ऑफसेट्स 0x158-0x163 मधील कोड splitter() मधील 0x103-0x10E मध्ये आपण जे पाहिले त्याच्यासारखाच आहे (JUMPI डेस्टिनेशन व्यतिरिक्त), म्हणून आपल्याला माहित आहे की currentWindow() हे देखील payable नाही.

ऑफसेटऑपकोडस्टॅक
164JUMPDEST
165POP
166PUSH2 0x00da0xDA
169PUSH1 0x010x01 0xDA
16BSLOADStorage[1] 0xDA
16CDUP20xDA Storage[1] 0xDA
16DJUMPStorage[1] 0xDA

DA कोड

हा कोड इतर मेथड्ससोबत देखील सामायिक केला आहे. म्हणून आपण स्टॅकमधील मूल्याला Y म्हणू, आणि फक्त हे लक्षात ठेवा की currentWindow() मध्ये या Y चे मूल्य Storage[1] आहे.

ऑफसेटऑपकोडस्टॅक
DAJUMPDESTY 0xDA
DBPUSH1 0x400x40 Y 0xDA
DDMLOAD0x80 Y 0xDA
DESWAP1Y 0x80 0xDA
DFDUP20x80 Y 0x80 0xDA
E0MSTORE0x80 0xDA

Y ला 0x80-0x9F वर लिहा.

ऑफसेटऑपकोडस्टॅक
E1PUSH1 0x200x20 0x80 0xDA
E3ADD0xA0 0xDA

आणि उर्वरित भाग आधीच वर स्पष्ट केला आहे. म्हणून 0xDA वरील जंप्स स्टॅकच्या वरच्या भागाला (Y) 0x80-0x9F वर लिहितात आणि ते मूल्य परत करतात. currentWindow() च्या बाबतीत, ते Storage[1] परत करते.

merkleRoot()

ऑफसेट 0xED-0xF8 मधील कोड आपण splitter() मधील 0x103-0x10E मध्ये जे पाहिले त्याच्या अगदी समान आहे (JUMPI डेस्टिनेशन व्यतिरिक्त), त्यामुळे आपल्याला माहित आहे की merkleRoot() हे देखील payable नाही.

ऑफसेटऑपकोडस्टॅक
F9JUMPDEST
FAPOP
FBPUSH2 0x00da0xDA
FEPUSH1 0x000x00 0xDA
100SLOADStorage[0] 0xDA
101DUP20xDA Storage[0] 0xDA
102JUMPStorage[0] 0xDA

जंपनंतर काय होते हे आपण आधीच शोधून काढले आहे. त्यामुळे merkleRoot() Storage[0] परत करते.

0x81e580d3

ऑफसेट्स 0x138-0x143 मधील कोड splitter() मध्ये 0x103-0x10E मध्ये आपण जे पाहिले त्याच्या अगदी समान आहे (JUMPI डेस्टिनेशन व्यतिरिक्त), त्यामुळे आपल्याला माहित आहे की हे फंक्शन देखील payable नाही.

ऑफसेटऑपकोडस्टॅक
144JUMPDEST
145POP
146PUSH2 0x00da0xDA
149PUSH2 0x01530x0153 0xDA
14CCALLDATASIZECALLDATASIZE 0x0153 0xDA
14DPUSH1 0x040x04 CALLDATASIZE 0x0153 0xDA
14FPUSH2 0x018f0x018F 0x04 CALLDATASIZE 0x0153 0xDA
152JUMP0x04 CALLDATASIZE 0x0153 0xDA
18FJUMPDEST0x04 CALLDATASIZE 0x0153 0xDA
190PUSH1 0x000x00 0x04 CALLDATASIZE 0x0153 0xDA
192PUSH1 0x200x20 0x00 0x04 CALLDATASIZE 0x0153 0xDA
194DUP30x04 0x20 0x00 0x04 CALLDATASIZE 0x0153 0xDA
195DUP5CALLDATASIZE 0x04 0x20 0x00 0x04 CALLDATASIZE 0x0153 0xDA
196SUBCALLDATASIZE-4 0x20 0x00 0x04 CALLDATASIZE 0x0153 0xDA
197SLTCALLDATASIZE-4<32 0x00 0x04 CALLDATASIZE 0x0153 0xDA
198ISZEROCALLDATASIZE-4>=32 0x00 0x04 CALLDATASIZE 0x0153 0xDA
199PUSH2 0x01a00x01A0 CALLDATASIZE-4>=32 0x00 0x04 CALLDATASIZE 0x0153 0xDA
19CJUMPI0x00 0x04 CALLDATASIZE 0x0153 0xDA

असे दिसते की हे फंक्शन कॉल डेटाचे किमान 32 बाइट्स (एक वर्ड) घेते.

ऑफसेटऑपकोडस्टॅक
19DDUP10x00 0x00 0x04 CALLDATASIZE 0x0153 0xDA
19EDUP20x00 0x00 0x00 0x04 CALLDATASIZE 0x0153 0xDA
19FREVERT

जर त्याला कॉल डेटा मिळाला नाही तर कोणताही रिटर्न डेटा न देता व्यवहार पूर्ववत केला जातो.

फंक्शनला आवश्यक असलेला कॉल डेटा मिळाल्यास काय होते ते पाहूया.

ऑफसेटऑपकोडस्टॅक
1A0JUMPDEST0x00 0x04 CALLDATASIZE 0x0153 0xDA
1A1POP0x04 CALLDATASIZE 0x0153 0xDA
1A2CALLDATALOADcalldataload(4) CALLDATASIZE 0x0153 0xDA

calldataload(4) हा मेथड स्वाक्षरीच्या नंतरचा कॉल डेटाचा पहिला वर्ड आहे

ऑफसेटऑपकोडस्टॅक
1A3SWAP20x0153 CALLDATASIZE calldataload(4) 0xDA
1A4SWAP1CALLDATASIZE 0x0153 calldataload(4) 0xDA
1A5POP0x0153 calldataload(4) 0xDA
1A6JUMPcalldataload(4) 0xDA
153JUMPDESTcalldataload(4) 0xDA
154PUSH2 0x016e0x016E calldataload(4) 0xDA
157JUMPcalldataload(4) 0xDA
16EJUMPDESTcalldataload(4) 0xDA
16FPUSH1 0x040x04 calldataload(4) 0xDA
171DUP2calldataload(4) 0x04 calldataload(4) 0xDA
172DUP20x04 calldataload(4) 0x04 calldataload(4) 0xDA
173SLOADStorage[4] calldataload(4) 0x04 calldataload(4) 0xDA
174DUP2calldataload(4) Storage[4] calldataload(4) 0x04 calldataload(4) 0xDA
175LTcalldataload(4)<Storage[4] calldataload(4) 0x04 calldataload(4) 0xDA
176PUSH2 0x017e0x017EC calldataload(4)<Storage[4] calldataload(4) 0x04 calldataload(4) 0xDA
179JUMPIcalldataload(4) 0x04 calldataload(4) 0xDA

जर पहिला वर्ड Storage[4] पेक्षा कमी नसेल, तर फंक्शन अयशस्वी होते. ते कोणत्याही रिटर्न मूल्याशिवाय पूर्ववत होते:

ऑफसेटऑपकोडस्टॅक
17APUSH1 0x000x00 ...
17CDUP10x00 0x00 ...
17DREVERT

जर calldataload(4) हे Storage[4] पेक्षा कमी असेल, तर आपल्याला हा कोड मिळतो:

ऑफसेटऑपकोडस्टॅक
17EJUMPDESTcalldataload(4) 0x04 calldataload(4) 0xDA
17FPUSH1 0x000x00 calldataload(4) 0x04 calldataload(4) 0xDA
181SWAP20x04 calldataload(4) 0x00 calldataload(4) 0xDA
182DUP30x00 0x04 calldataload(4) 0x00 calldataload(4) 0xDA
183MSTOREcalldataload(4) 0x00 calldataload(4) 0xDA

आणि मेमरी लोकेशन्स 0x00-0x1F मध्ये आता 0x04 हा डेटा आहे (0x00-0x1E सर्व शून्य आहेत, 0x1F हे चार आहे)

ऑफसेटऑपकोडस्टॅक
184PUSH1 0x200x20 calldataload(4) 0x00 calldataload(4) 0xDA
186SWAP1calldataload(4) 0x20 0x00 calldataload(4) 0xDA
187SWAP20x00 0x20 calldataload(4) calldataload(4) 0xDA
188SHA3(((SHA3 of 0x00-0x1F))) calldataload(4) calldataload(4) 0xDA
189ADD(((SHA3 of 0x00-0x1F)))+calldataload(4) calldataload(4) 0xDA
18ASLOADStorage[(((SHA3 of 0x00-0x1F))) + calldataload(4)] calldataload(4) 0xDA

म्हणून स्टोरेजमध्ये एक लुकअप टेबल आहे, जे 0x000...0004 च्या SHA3 पासून सुरू होते आणि प्रत्येक वैध कॉल डेटा मूल्यासाठी (Storage[4] च्या खालील मूल्य) एक नोंद असते.

ऑफसेटऑपकोडस्टॅक
18BSWAP1calldataload(4) Storage[(((SHA3 of 0x00-0x1F))) + calldataload(4)] 0xDA
18CPOPStorage[(((SHA3 of 0x00-0x1F))) + calldataload(4)] 0xDA
18DDUP20xDA Storage[(((SHA3 of 0x00-0x1F))) + calldataload(4)] 0xDA
18EJUMPStorage[(((SHA3 of 0x00-0x1F))) + calldataload(4)] 0xDA

आपल्याला आधीच माहित आहे की ऑफसेट 0xDA वरील कोड काय करतो, तो कॉलरला स्टॅकचे सर्वोच्च मूल्य परत करतो. त्यामुळे हे फंक्शन लुकअप टेबलमधील मूल्य कॉलरला परत करते.

0x1f135823

0xC4-0xCF ऑफसेट्समधील कोड आपण splitter() मध्ये 0x103-0x10E मध्ये जे पाहिले त्याच्यासारखाच आहे (JUMPI डेस्टिनेशन व्यतिरिक्त), त्यामुळे आपल्याला माहित आहे की हे फंक्शन देखील payable नाही.

ऑफसेटऑपकोडस्टॅक
D0JUMPDEST
D1POP
D2PUSH2 0x00da0xDA
D5PUSH1 0x060x06 0xDA
D7SLOADValue* 0xDA
D8DUP20xDA Value* 0xDA
D9JUMPValue* 0xDA

0xDA ऑफसेटवरील कोड काय करतो हे आपल्याला आधीच माहित आहे, तो कॉलरला स्टॅकचे सर्वोच्च मूल्य परत करतो. त्यामुळे हे फंक्शन Value* परत करते.

मेथड सारांश

या टप्प्यावर तुम्हाला कॉन्ट्रॅक्ट समजले आहे असे वाटते का? मला वाटत नाही. आतापर्यंत आपल्याकडे या मेथड्स आहेत:

मेथडअर्थ
हस्तांतरणकॉलद्वारे प्रदान केलेले मूल्य स्वीकारा आणि Value* त्या रकमेने वाढवा
splitter()Storage[3], प्रॉक्सी पत्ता परत करा
currentWindow()Storage[1] परत करा
merkleRoot()Storage[0] परत करा
0x81e580d3पॅरामीटर Storage[4] पेक्षा कमी असल्यास, लुकअप टेबलमधून मूल्य परत करा
0x1f135823Storage[6], म्हणजेच Value* परत करा

परंतु आपल्याला माहित आहे की इतर कोणतीही कार्यक्षमता Storage[3] मधील कॉन्ट्रॅक्टद्वारे प्रदान केली जाते. कदाचित ते कॉन्ट्रॅक्ट काय आहे हे आपल्याला माहित असल्यास आपल्याला एक संकेत मिळेल. सुदैवाने, ही ब्लॉकचेन आहे आणि किमान सैद्धांतिकदृष्ट्या सर्व काही ज्ञात आहे. आपल्याला Storage[3] सेट करणारी कोणतीही मेथड दिसली नाही, त्यामुळे ती कन्स्ट्रक्टरद्वारे सेट केली गेली असावी.

कन्स्ट्रक्टर

जेव्हा आपण एखाद्या कॉन्ट्रॅक्टकडे पाहतो (opens in a new tab) तेव्हा आपण तो व्यवहार देखील पाहू शकतो ज्याने ते तयार केले आहे.

Click the create transaction

जर आपण त्या व्यवहारावर आणि त्यानंतर स्थिती टॅबवर क्लिक केले, तर आपण पॅरामीटर्सची प्रारंभिक मूल्ये पाहू शकतो. विशेषतः, आपण पाहू शकतो की Storage[3] मध्ये 0x2f81e57ff4f4d83b40a9f719fd892d8e806e0761 (opens in a new tab) आहे. त्या कॉन्ट्रॅक्टमध्ये गहाळ असलेली कार्यक्षमता असली पाहिजे. आपण ज्या कॉन्ट्रॅक्टची तपासणी करत आहोत त्यासाठी वापरलेल्या त्याच साधनांचा वापर करून आपण ते समजून घेऊ शकतो.

प्रॉक्सी कॉन्ट्रॅक्ट

वर मूळ कॉन्ट्रॅक्टसाठी आपण वापरलेल्या त्याच तंत्रांचा वापर करून आपण पाहू शकतो की जर खालील गोष्टी घडल्या तर कॉन्ट्रॅक्ट पूर्ववत होते (reverts):

  • कॉलला कोणताही ETH जोडलेला असेल (0x05-0x0F)
  • कॉल डेटाचा आकार 4 पेक्षा कमी असेल (0x10-0x19 आणि 0xBE-0xC2)

आणि ते समर्थन करत असलेल्या पद्धती (methods) खालीलप्रमाणे आहेत:

पद्धतपद्धतीची स्वाक्षरीउडी मारण्यासाठी ऑफसेट
scaleAmountByPercentage(uint256,uint256) (opens in a new tab)0x8ffb5c970x0135
isClaimed(uint256,address) (opens in a new tab)0xd2ef07950x0151
claim(uint256,address,uint256,bytes32[]) (opens in a new tab)0x2e7ba6ef0x00F4
incrementWindow() (opens in a new tab)0x338b1d310x0110
???0x3f26479e0x0118
???0x1e7df9d30x00C3
currentWindow() (opens in a new tab)0xba0bafb40x0148
merkleRoot() (opens in a new tab)0x2eb4a7ab0x0107
???0x81e580d30x0122
???0x1f1358230x00D8

आपण तळाच्या 4 पद्धतींकडे दुर्लक्ष करू शकतो कारण आपण त्यांच्यापर्यंत कधीही पोहोचणार नाही. त्यांच्या स्वाक्षऱ्या अशा आहेत की आपले मूळ कॉन्ट्रॅक्ट स्वतःच त्यांची काळजी घेते (तुम्ही वरील तपशील पाहण्यासाठी स्वाक्षऱ्यांवर क्लिक करू शकता), त्यामुळे त्या ओव्हरराइड केलेल्या पद्धती (opens in a new tab) असल्या पाहिजेत.

उर्वरित पद्धतींपैकी एक claim(<params>) आहे, आणि दुसरी isClaimed(<params>) आहे, त्यामुळे हे एक एअरड्रॉप कॉन्ट्रॅक्ट असल्याचे दिसते. उर्वरित ऑपकोड एक-एक करून तपासण्याऐवजी, आपण डीकंपायलर वापरून पाहू शकतो (opens in a new tab), जे या कॉन्ट्रॅक्टमधील 3 फंक्शन्ससाठी उपयुक्त परिणाम देते. इतरांचे रिव्हर्स इंजिनिअरिंग करणे वाचकांसाठी एक सराव म्हणून सोडले आहे.

scaleAmountByPercentage

या फंक्शनसाठी डीकंपायलर आपल्याला हे देते:

def unknown8ffb5c97(uint256 _param1, uint256 _param2) payable:
  require calldata.size - 4 >=64
  if _param1 and _param2 > -1 / _param1:
      revert with 0, 17
  return (_param1 * _param2 / 100 * 10^6)

पहिली require हे तपासते की कॉल डेटामध्ये, फंक्शन स्वाक्षरीच्या 4 बाइट्स व्यतिरिक्त, किमान 64 बाइट्स आहेत, जे दोन पॅरामीटर्ससाठी पुरेसे आहेत. तसे नसल्यास नक्कीच काहीतरी चुकीचे आहे.

if विधान हे तपासत असल्याचे दिसते की _param1 शून्य नाही, आणि _param1 * _param2 नकारात्मक नाही. हे बहुधा रॅप अराउंडची (wrap around) प्रकरणे टाळण्यासाठी आहे.

शेवटी, फंक्शन एक स्केल केलेले मूल्य (scaled value) परत करते.

claim

डीकंपायलरने तयार केलेला कोड गुंतागुंतीचा आहे, आणि त्यातील सर्वच आपल्यासाठी संबंधित नाही. मला उपयुक्त माहिती देणाऱ्या ओळींवर लक्ष केंद्रित करण्यासाठी मी त्यातील काही भाग वगळणार आहे

def unknown2e7ba6ef(uint256 _param1, uint256 _param2, uint256 _param3, array _param4) payable:
  ...
  require _param2 == addr(_param2)
  ...
  if currentWindow <= _param1:
      revert with 0, 'cannot claim for a future window'

आपण येथे दोन महत्त्वाच्या गोष्टी पाहतो:

  • _param2, जरी ते uint256 म्हणून घोषित केले असले तरी, प्रत्यक्षात तो एक पत्ता आहे
  • _param1 ही दावा केली जाणारी विंडो आहे, जी currentWindow किंवा त्यापूर्वीची असली पाहिजे.
  ...
  if stor5[_claimWindow][addr(_claimFor)]:
      revert with 0, 'Account already claimed the given window'

तर आता आपल्याला माहित आहे की Storage[5] हा विंडोज आणि पत्त्यांचा एक अ‍ॅरे (array) आहे, आणि त्या पत्त्याने त्या विंडोसाठी बक्षीसाचा दावा केला आहे की नाही हे दर्शवतो.

आपल्याला माहित आहे की unknown2eb4a7ab हे प्रत्यक्षात merkleRoot() फंक्शन आहे, त्यामुळे हा कोड मर्केल पुरावा (opens in a new tab) पडताळत असल्याचे दिसते. याचा अर्थ असा की _param4 हा एक मर्केल पुरावा आहे.

  call addr(_param2) with:
     value unknown81e580d3[_param1] * _param3 / 100 * 10^6 wei
       gas 30000 wei

अशा प्रकारे एखादे कॉन्ट्रॅक्ट स्वतःचा ETH दुसऱ्या पत्त्यावर (कॉन्ट्रॅक्ट किंवा बाह्य मालकीचे) हस्तांतरित करते. ते हस्तांतरित करावयाच्या रकमेच्या मूल्यासह त्याला कॉल करते. त्यामुळे हे ETH चे एअरड्रॉप असल्याचे दिसते.

  if not return_data.size:
      if not ext_call.success:
          require ext_code.size(stor2)
          call stor2.deposit() with:
             value unknown81e580d3[_param1] * _param3 / 100 * 10^6 wei

तळाच्या दोन ओळी आपल्याला सांगतात की Storage[2] हे देखील एक कॉन्ट्रॅक्ट आहे ज्याला आपण कॉल करतो. जर आपण कन्स्ट्रक्टर व्यवहाराकडे पाहिले (opens in a new tab) तर आपल्याला दिसेल की हे कॉन्ट्रॅक्ट 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 (opens in a new tab) आहे, जे एक रॅप्ड इथर (weth) कॉन्ट्रॅक्ट आहे ज्याचा सोर्स कोड Etherscan वर अपलोड केला गेला आहे (opens in a new tab).

त्यामुळे असे दिसते की कॉन्ट्रॅक्ट्स _param2 ला ETH पाठवण्याचा प्रयत्न करतात. जर ते तसे करू शकले, तर उत्तम. तसे नसल्यास, ते WETH (opens in a new tab) पाठवण्याचा प्रयत्न करतात. जर _param2 हे बाह्य मालकीचे खाते (EOA) असेल तर ते नेहमी ETH प्राप्त करू शकते, परंतु कॉन्ट्रॅक्ट्स ETH प्राप्त करण्यास नकार देऊ शकतात. तथापि, WETH हे ERC-20 आहे आणि कॉन्ट्रॅक्ट्स ते स्वीकारण्यास नकार देऊ शकत नाहीत.

  ...
  log 0xdbd5389f: addr(_param2), unknown81e580d3[_param1] * _param3 / 100 * 10^6, bool(ext_call.success)

फंक्शनच्या शेवटी आपण एक नोंद (log) तयार होताना पाहतो. तयार केलेल्या नोंदी पहा (opens in a new tab) आणि 0xdbd5... ने सुरू होणाऱ्या विषयावर फिल्टर करा. जर आपण अशी नोंद तयार करणाऱ्या व्यवहारांपैकी एकावर क्लिक केले (opens in a new tab) तर आपल्याला दिसेल की खरोखरच तो एक दावा (claim) असल्यासारखे दिसते - खात्याने आपण रिव्हर्स इंजिनिअरिंग करत असलेल्या कॉन्ट्रॅक्टला एक संदेश पाठवला, आणि बदल्यात ETH मिळवला.

A claim transaction

1e7df9d3

हे फंक्शन वरील claim च्या अगदी समान आहे. हे देखील मर्केल पुरावा तपासते, पहिल्याला ETH हस्तांतरित करण्याचा प्रयत्न करते, आणि त्याच प्रकारची नोंद तयार करते.

मुख्य फरक हा आहे की पहिला पॅरामीटर, काढण्यासाठीची (withdraw) विंडो, तिथे नाही. त्याऐवजी, दावा केल्या जाऊ शकणाऱ्या सर्व विंडोजवर एक लूप आहे.

त्यामुळे हे claim चे एक प्रकार (variant) असल्यासारखे दिसते जे सर्व विंडोजवर दावा करते.

निष्कर्ष

आतापर्यंत तुम्हाला समजले असेल की ज्या कॉन्ट्रॅक्ट्सचा सोर्स कोड उपलब्ध नाही ते ऑपकोड्स किंवा (जेव्हा ते काम करते तेव्हा) डीकंपायलर वापरून कसे समजून घ्यायचे. या लेखाच्या लांबीवरून हे स्पष्ट होते की, एखाद्या कॉन्ट्रॅक्टचे रिव्हर्स इंजिनिअरिंग करणे सोपे नाही, परंतु ज्या प्रणालीमध्ये सुरक्षा आवश्यक आहे तिथे कॉन्ट्रॅक्ट्स वचन दिल्याप्रमाणे काम करतात की नाही हे पडताळून पाहता येणे हे एक महत्त्वाचे कौशल्य आहे.

माझ्या अधिक कामासाठी येथे पहा (opens in a new tab).