मुख्य सामग्री पर जाएँ

एक कॉन्ट्रैक्ट की रिवर्स इंजीनियरिंग

evm
ऑपकोड
उन्नत
ओरी पोमेरेंट्ज़
30 दिसंबर 2021
34 मिनट का पठन

परिचय

ब्लॉकचेन पर कोई रहस्य नहीं हैं, जो कुछ भी होता है वह सुसंगत, सत्यापन योग्य और सार्वजनिक रूप से उपलब्ध होता है। आदर्श रूप से, कॉन्ट्रैक्ट्स का सोर्स कोड ईथरस्कैन पर प्रकाशित और सत्यापित होना चाहिए (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)

निष्पादन योग्य कोड तैयार करें

आप कॉन्ट्रैक्ट के लिए ईथरस्कैन पर जाकर, कॉन्ट्रैक्ट टैब पर क्लिक करके और फिर ऑपकोड व्यू पर स्विच करें पर क्लिक करके ऑपकोड प्राप्त कर सकते हैं। आपको एक ऐसा व्यू मिलता है जिसमें प्रति लाइन एक ऑपकोड होता है।

ईथरस्कैन से ऑपकोड व्यू

हालांकि, जंप को समझने के लिए, आपको यह जानना होगा कि प्रत्येक ऑपकोड कोड में कहाँ स्थित है। ऐसा करने के लिए, एक तरीका यह है कि एक Google स्प्रेडशीट खोलें और ऑपकोड को कॉलम C में पेस्ट करें। आप इस पहले से तैयार स्प्रेडशीट की एक कॉपी बनाकर निम्नलिखित चरणों को छोड़ सकते हैं (opens in a new tab)

अगला कदम सही कोड लोकेशन प्राप्त करना है ताकि हम जंप को समझ सकें। हम ऑपकोड साइज को कॉलम B में, और लोकेशन (हेक्साडेसिमल में) को कॉलम A में रखेंगे। सेल B1 में इस फ़ंक्शन को टाइप करें और फिर इसे कोड के अंत तक, कॉलम B के बाकी हिस्सों के लिए कॉपी और पेस्ट करें। ऐसा करने के बाद आप कॉलम B को छिपा सकते हैं।

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

सबसे पहले यह फ़ंक्शन ऑपकोड के लिए एक बाइट जोड़ता है, और फिर PUSH की तलाश करता है। पुश ऑपकोड विशेष होते हैं क्योंकि उन्हें पुश किए जा रहे मान के लिए अतिरिक्त बाइट्स की आवश्यकता होती है। यदि ऑपकोड एक PUSH है, तो हम बाइट्स की संख्या निकालते हैं और उसे जोड़ते हैं।

A1 में पहला ऑफ़सेट, शून्य डालें। फिर, A2 में, इस फ़ंक्शन को डालें और फिर से इसे कॉलम A के बाकी हिस्सों के लिए कॉपी और पेस्ट करें:

1=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 में 0x80 को 32 बाइट मान के रूप में लिखें (0x80 को 0x5F में संग्रहीत किया जाता है, और 0x40-0x5E सभी शून्य हैं)।
  2. कॉलडेटा का आकार पढ़ें। आमतौर पर एक एथेरियम कॉन्ट्रैक्ट के लिए कॉल डेटा ABI (एप्लिकेशन बाइनरी इंटरफ़ेस) (opens in a new tab) का पालन करता है, जिसके लिए फ़ंक्शन चयनकर्ता के लिए कम से कम चार बाइट्स की आवश्यकता होती है। यदि कॉल डेटा का आकार चार से कम है, तो 0x5E पर जंप करें।

इस भाग के लिए फ़्लोचार्ट

0x5E पर हैंडलर (गैर-ABI कॉल डेटा के लिए)

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

यह स्निपेट JUMPDEST से शुरू होता है। EVM (एथेरियम वर्चुअल मशीन) प्रोग्राम एक अपवाद फेंकते हैं यदि आप एक ऐसे ऑपकोड पर जंप करते हैं जो JUMPDEST नहीं है। फिर यह CALLDATASIZE को देखता है, और यदि यह "true" है (यानी, शून्य नहीं) तो 0x7C पर जंप करता है। हम नीचे उस पर आएंगे।

ऑफ़सेटऑपकोडस्टैक (ऑपकोड के बाद)
64CALLVALUEकॉल द्वारा प्रदान किया गया । सॉलिडिटी में msg.value कहा जाता है
65PUSH1 0x066 CALLVALUE
67PUSH1 0x000 6 CALLVALUE
69DUP3CALLVALUE 0 6 CALLVALUE
6ADUP36 CALLVALUE 0 6 CALLVALUE
6BSLOADभंडारण[6] CALLVALUE 0 6 CALLVALUE

तो जब कोई कॉल डेटा नहीं होता है तो हम Storage[6] का मान पढ़ते हैं। हमें अभी तक यह नहीं पता कि यह मान क्या है, लेकिन हम उन लेन-देन की तलाश कर सकते हैं जो कॉन्ट्रैक्ट को बिना किसी कॉल डेटा के प्राप्त हुए हैं। वे लेनदेन जो बिना किसी कॉल डेटा (और इसलिए कोई विधि नहीं) के केवल ETH ट्रांसफर करते हैं, ईथरस्कैन में Transfer विधि होती है। वास्तव में, कॉन्ट्रैक्ट को प्राप्त पहला लेनदेन (opens in a new tab) एक ट्रांसफर है।

यदि हम उस लेनदेन में देखते हैं और अधिक देखने के लिए क्लिक करें पर क्लिक करते हैं, तो हम देखते हैं कि कॉल डेटा, जिसे इनपुट डेटा कहा जाता है, वास्तव में खाली (0x) है। यह भी ध्यान दें कि मान 1.559 ETH है, जो बाद में प्रासंगिक होगा।

कॉल डेटा खाली है

इसके बाद, स्टेट टैब पर क्लिक करें और उस कॉन्ट्रैक्ट का विस्तार करें जिसकी हम रिवर्स इंजीनियरिंग कर रहे हैं (0x2510...)। आप देख सकते हैं कि लेनदेन के दौरान Storage[6] बदल गया, और यदि आप Hex को नंबर में बदलते हैं, तो आप देखते हैं कि यह 1,559,000,000,000,000,000 हो गया, wei में स्थानांतरित मान (मैंने स्पष्टता के लिए कॉमा जोड़ा है), जो अगले कॉन्ट्रैक्ट मान के अनुरूप है।

भंडारण[6] में परिवर्तन

यदि हम उसी अवधि के अन्य ट्रांसफर लेन-देन (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 पर कॉन्ट्रैक्ट ओवरफ़्लो का पता चलने पर रिवर्ट हो जाता है, जो सामान्य व्यवहार है।

ध्यान दें कि ऐसा ओवरफ़्लो अत्यंत असंभावित है, क्योंकि इसके लिए कॉल वैल्यू प्लस 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* में कॉल वैल्यू जोड़ते हैं। यह उसके अनुरूप है जो हम कहते हैं कि ट्रांसफर लेनदेन करते हैं।

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

अंत में, स्टैक को साफ़ करें (जो आवश्यक नहीं है) और लेनदेन के सफल अंत का संकेत दें।

संक्षेप में, यहाँ प्रारंभिक कोड के लिए एक फ़्लोचार्ट है।

एंट्री प्वाइंट फ़्लोचार्ट

0x7C पर हैंडलर

मैंने जानबूझकर शीर्षक में यह नहीं डाला कि यह हैंडलर क्या करता है। इसका उद्देश्य आपको यह सिखाना नहीं है कि यह विशिष्ट कॉन्ट्रैक्ट कैसे काम करता है, बल्कि यह सिखाना है कि कॉन्ट्रैक्ट की रिवर्स इंजीनियरिंग कैसे की जाती है। आप उसी तरह सीखेंगे कि यह क्या करता है जैसे मैंने किया, कोड का पालन करके।

हम यहाँ कई जगहों से आते हैं:

  • यदि 1, 2, या 3 बाइट्स का कॉल डेटा है (ऑफ़सेट 0x63 से)
  • यदि विधि हस्ताक्षर अज्ञात है (ऑफ़सेट 0x42 और 0x5D से)
ऑफ़सेटऑपकोडस्टैक
7CJUMPDEST
7DPUSH1 0x000x00
7FPUSH2 0x009d0x9D 0x00
82PUSH1 0x030x03 0x9D 0x00
84SLOADभंडारण[3] 0x9D 0x00

यह एक और भंडारण सेल है, जिसे मैं किसी भी लेनदेन में नहीं खोज सका, इसलिए यह जानना कठिन है कि इसका क्या मतलब है। नीचे दिया गया कोड इसे और स्पष्ट कर देगा।

ऑफ़सेटऑपकोडस्टैक
85PUSH20 0xffffffffffffffffffffffffffffffffffffffff0xff....ff भंडारण[3] 0x9D 0x00
9AANDभंडारण[3]-के-रूप-में-पता 0x9D 0x00

ये ऑपकोड हमारे द्वारा Storage[3] से पढ़े गए मान को 160 बिट्स तक छोटा कर देते हैं, जो एक एथेरियम पते की लंबाई है।

ऑफ़सेटऑपकोडस्टैक
9BSWAP10x9D भंडारण[3]-के-रूप-में-पता 0x00
9CJUMPभंडारण[3]-के-रूप-में-पता 0x00

यह जंप अनावश्यक है, क्योंकि हम अगले ऑपकोड पर जा रहे हैं। यह कोड उतना गैस-कुशल नहीं है जितना हो सकता था।

ऑफ़सेटऑपकोडस्टैक
9DJUMPDESTभंडारण[3]-के-रूप-में-पता 0x00
9ESWAP10x00 भंडारण[3]-के-रूप-में-पता
9FPOPभंडारण[3]-के-रूप-में-पता
A0PUSH1 0x400x40 भंडारण[3]-के-रूप-में-पता
A2MLOADMem[0x40] भंडारण[3]-के-रूप-में-पता

कोड की शुरुआत में ही हमने Mem[0x40] को 0x80 पर सेट किया है। यदि हम बाद में 0x40 की तलाश करते हैं, तो हम देखते हैं कि हम इसे नहीं बदलते हैं - इसलिए हम मान सकते हैं कि यह 0x80 है।

ऑफ़सेटऑपकोडस्टैक
A3CALLDATASIZECALLDATASIZE 0x80 भंडारण[3]-के-रूप-में-पता
A4PUSH1 0x000x00 CALLDATASIZE 0x80 भंडारण[3]-के-रूप-में-पता
A6DUP30x80 0x00 CALLDATASIZE 0x80 भंडारण[3]-के-रूप-में-पता
A7CALLDATACOPY0x80 भंडारण[3]-के-रूप-में-पता

सभी कॉल डेटा को मेमोरी में कॉपी करें, 0x80 से शुरू करते हुए।

ऑफ़सेटऑपकोडस्टैक
A8PUSH1 0x000x00 0x80 भंडारण[3]-के-रूप-में-पता
AADUP10x00 0x00 0x80 भंडारण[3]-के-रूप-में-पता
ABCALLDATASIZECALLDATASIZE 0x00 0x00 0x80 भंडारण[3]-के-रूप-में-पता
ACDUP40x80 CALLDATASIZE 0x00 0x00 0x80 भंडारण[3]-के-रूप-में-पता
ADDUP6भंडारण[3]-के-रूप-में-पता 0x80 CALLDATASIZE 0x00 0x00 0x80 भंडारण[3]-के-रूप-में-पता
AEGASGAS भंडारण[3]-के-रूप-में-पता 0x80 CALLDATASIZE 0x00 0x00 0x80 भंडारण[3]-के-रूप-में-पता
AFDELEGATE_CALL

अब चीजें बहुत स्पष्ट हैं। यह कॉन्ट्रैक्ट प्रॉक्सी (opens in a new tab) के रूप में कार्य कर सकता है, जो वास्तविक कार्य करने के लिए Storage[3] में पते को कॉल करता है। DELEGATE_CALL एक अलग कॉन्ट्रैक्ट को कॉल करता है, लेकिन उसी भंडारण में रहता है। इसका मतलब है कि प्रत्यायोजित कॉन्ट्रैक्ट, जिसके लिए हम एक प्रॉक्सी हैं, उसी भंडारण स्थान तक पहुँचता है। कॉल के लिए पैरामीटर हैं:

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

यहां हम सभी रिटर्न डेटा को मेमोरी बफर में कॉपी करते हैं जो 0x80 से शुरू होता है।

ऑफ़सेटऑपकोडस्टैक
B6DUP2(((कॉल सफलता/विफलता))) RETURNDATASIZE (((कॉल सफलता/विफलता))) 0x80 भंडारण[3]-के-रूप-में-पता
B7DUP1(((कॉल सफलता/विफलता))) (((कॉल सफलता/विफलता))) RETURNDATASIZE (((कॉल सफलता/विफलता))) 0x80 भंडारण[3]-के-रूप-में-पता
B8ISZERO(((क्या कॉल विफल हुआ))) (((कॉल सफलता/विफलता))) RETURNDATASIZE (((कॉल सफलता/विफलता))) 0x80 भंडारण[3]-के-रूप-में-पता
B9PUSH2 0x00c00xC0 (((क्या कॉल विफल हुआ))) (((कॉल सफलता/विफलता))) RETURNDATASIZE (((कॉल सफलता/विफलता))) 0x80 भंडारण[3]-के-रूप-में-पता
BCJUMPI(((कॉल सफलता/विफलता))) RETURNDATASIZE (((कॉल सफलता/विफलता))) 0x80 भंडारण[3]-के-रूप-में-पता
BDDUP2RETURNDATASIZE (((कॉल सफलता/विफलता))) RETURNDATASIZE (((कॉल सफलता/विफलता))) 0x80 भंडारण[3]-के-रूप-में-पता
BEDUP50x80 RETURNDATASIZE (((कॉल सफलता/विफलता))) RETURNDATASIZE (((कॉल सफलता/विफलता))) 0x80 भंडारण[3]-के-रूप-में-पता
BFRETURN

तो कॉल के बाद हम रिटर्न डेटा को बफर 0x80 - 0x80+RETURNDATASIZE पर कॉपी करते हैं, और यदि कॉल सफल होता है तो हम ठीक उसी बफर के साथ RETURN करते हैं।

DELEGATECALL विफल

अगर हम यहां, 0xC0 पर पहुंचते हैं, तो इसका मतलब है कि हमने जिस कॉन्ट्रैक्ट को कॉल किया था वह रिवर्ट हो गया। चूंकि हम केवल उस कॉन्ट्रैक्ट के लिए एक प्रॉक्सी हैं, हम उसी डेटा को वापस करना चाहते हैं और रिवर्ट भी करना चाहते हैं।

ऑफ़सेटऑपकोडस्टैक
C0JUMPDEST(((कॉल सफलता/विफलता))) RETURNDATASIZE (((कॉल सफलता/विफलता))) 0x80 भंडारण[3]-के-रूप-में-पता
C1DUP2RETURNDATASIZE (((कॉल सफलता/विफलता))) RETURNDATASIZE (((कॉल सफलता/विफलता))) 0x80 भंडारण[3]-के-रूप-में-पता
C2DUP50x80 RETURNDATASIZE (((कॉल सफलता/विफलता))) RETURNDATASIZE (((कॉल सफलता/विफलता))) 0x80 भंडारण[3]-के-रूप-में-पता
C3REVERT

तो हम उसी बफर के साथ REVERT करते हैं जिसका उपयोग हमने पहले RETURN के लिए किया था: 0x80 - 0x80+RETURNDATASIZE

प्रॉक्सी फ़्लोचार्ट पर कॉल करें

ABI कॉल

यदि कॉल डेटा का आकार चार बाइट या अधिक है तो यह एक वैध ABI कॉल हो सकता है।

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

ईथरस्कैन हमें बताता है कि 1C एक अज्ञात ऑपकोड है, क्योंकि इसे ईथरस्कैन द्वारा इस सुविधा को लिखने के बाद जोड़ा गया था (opens in a new tab) और उन्होंने इसे अपडेट नहीं किया है। एक अद्यतित ऑपकोड तालिका (opens in a new tab) हमें दिखाती है कि यह राइट शिफ्ट है

ऑफ़सेटऑपकोडस्टैक
13DUP1(((कॉल डेटा के पहले 32 बिट (4 बाइट्स)))) (((कॉल डेटा के पहले 32 बिट (4 बाइट्स))))
14PUSH4 0x3cd8045e0x3CD8045E (((कॉल डेटा के पहले 32 बिट (4 बाइट्स)))) (((कॉल डेटा के पहले 32 बिट (4 बाइट्स))))
19GT0x3CD8045E>कॉल-डेटा-के-पहले-32-बिट्स (((कॉल डेटा के पहले 32 बिट (4 बाइट्स))))
1APUSH2 0x00430x43 0x3CD8045E>कॉल-डेटा-के-पहले-32-बिट्स (((कॉल डेटा के पहले 32 बिट (4 बाइट्स))))
1DJUMPI(((कॉल डेटा के पहले 32 बिट (4 बाइट्स))))

इस तरह से विधि हस्ताक्षर मिलान परीक्षणों को दो भागों में विभाजित करने से औसतन आधे परीक्षण बच जाते हैं। इसके तुरंत बाद का कोड और 0x43 में कोड एक ही पैटर्न का पालन करता है: कॉल डेटा के पहले 32 बिट्स को DUP1 करें, PUSH4 (((विधि हस्ताक्षर)), समानता की जांच के लिए 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 कॉल फ़्लोचार्ट

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(((भंडारण[3] उर्फ कॉन्ट्रैक्ट जिसके लिए हम एक प्रॉक्सी हैं)))
114PUSH1 0x400x40 (((भंडारण[3] उर्फ कॉन्ट्रैक्ट जिसके लिए हम एक प्रॉक्सी हैं)))
116MLOAD0x80 (((भंडारण[3] उर्फ कॉन्ट्रैक्ट जिसके लिए हम एक प्रॉक्सी हैं)))
117PUSH20 0xffffffffffffffffffffffffffffffffffffffff0xFF...FF 0x80 (((भंडारण[3] उर्फ कॉन्ट्रैक्ट जिसके लिए हम एक प्रॉक्सी हैं)))
12CSWAP10x80 0xFF...FF (((भंडारण[3] उर्फ कॉन्ट्रैक्ट जिसके लिए हम एक प्रॉक्सी हैं)))
12DSWAP2(((भंडारण[3] उर्फ कॉन्ट्रैक्ट जिसके लिए हम एक प्रॉक्सी हैं))) 0xFF...FF 0x80
12EANDProxyAddr 0x80
12FDUP20x80 ProxyAddr 0x80
130MSTORE0x80

और 0x80 में अब प्रॉक्सी पता है

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

E4 कोड

यह पहली बार है जब हम इन पंक्तियों को देखते हैं, लेकिन वे अन्य विधियों के साथ साझा की जाती हैं (नीचे देखें)। तो हम स्टैक में मान को 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
16BSLOADभंडारण[1] 0xDA
16CDUP20xDA भंडारण[1] 0xDA
16DJUMPभंडारण[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
100SLOADभंडारण[0] 0xDA
101DUP20xDA भंडारण[0] 0xDA
102JUMPभंडारण[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
173SLOADभंडारण[4] calldataload(4) 0x04 calldataload(4) 0xDA
174DUP2calldataload(4) भंडारण[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(((0x00-0x1F का SHA3))) calldataload(4) calldataload(4) 0xDA
189ADD(((0x00-0x1F का SHA3)))+calldataload(4) calldataload(4) 0xDA
18ASLOADभंडारण[(((0x00-0x1F का SHA3))) + calldataload(4)] calldataload(4) 0xDA

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

ऑफ़सेटऑपकोडस्टैक
18BSWAP1calldataload(4) भंडारण[(((0x00-0x1F का SHA3))) + calldataload(4)] 0xDA
18CPOPभंडारण[(((0x00-0x1F का SHA3))) + calldataload(4)] 0xDA
18DDUP20xDA भंडारण[(((0x00-0x1F का SHA3))) + calldataload(4)] 0xDA
18EJUMPभंडारण[(((0x00-0x1F का SHA3))) + 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) तो हम वह लेनदेन भी देख सकते हैं जिसने इसे बनाया है।

बनाएँ लेनदेन पर क्लिक करें

यदि हम उस लेनदेन पर क्लिक करते हैं, और फिर स्टेट टैब पर, हम मापदंडों के प्रारंभिक मान देख सकते हैं। विशेष रूप से, हम देख सकते हैं कि Storage[3] में 0x2f81e57ff4f4d83b40a9f719fd892d8e806e0761 (opens in a new tab) शामिल है। उस कॉन्ट्रैक्ट में लापता कार्यक्षमता होनी चाहिए। हम इसे उन्हीं उपकरणों का उपयोग करके समझ सकते हैं जिनका उपयोग हमने उस कॉन्ट्रैक्ट के लिए किया था जिसकी हम जांच कर रहे हैं।

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

ऊपर मूल कॉन्ट्रैक्ट के लिए उपयोग की गई समान तकनीकों का उपयोग करके हम देख सकते हैं कि कॉन्ट्रैक्ट रिवर्ट हो जाता है यदि:

  • कॉल से कोई ETH जुड़ा हुआ है (0x05-0x0F)
  • कॉल डेटा का आकार चार से कम है (0x10-0x19 और 0xBE-0xC2)

और यह जिन विधियों का समर्थन करता है वे हैं:

विधिविधि हस्ताक्षरजंप करने के लिए ऑफ़सेट
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

हम नीचे की चार विधियों को अनदेखा कर सकते हैं क्योंकि हम उन तक कभी नहीं पहुंच पाएंगे। उनके हस्ताक्षर ऐसे हैं कि हमारा मूल कॉन्ट्रैक्ट स्वयं उनकी देखभाल करता है (आप ऊपर विवरण देखने के लिए हस्ताक्षर पर क्लिक कर सकते हैं), इसलिए वे ओवरराइड की गई विधियाँ (opens in a new tab) होनी चाहिए।

शेष विधियों में से एक claim(<params>) है, और दूसरी isClaimed(<params>) है, इसलिए यह एक एयरड्रॉप कॉन्ट्रैक्ट जैसा लगता है। बाकी ऑपकोड-दर-ऑपकोड जाने के बजाय, हम डीकंपाइलर को आजमा सकते हैं (opens in a new tab), जो इस कॉन्ट्रैक्ट से तीन कार्यों के लिए प्रयोग करने योग्य परिणाम देता है। दूसरों की रिवर्स इंजीनियरिंग पाठक के लिए एक अभ्यास के रूप में छोड़ दी गई है।

scaleAmountByPercentage

यह वह है जो डीकंपाइलर हमें इस फ़ंक्शन के लिए देता है:

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

पहला require यह परीक्षण करता है कि कॉल डेटा में, फ़ंक्शन हस्ताक्षर के चार बाइट्स के अलावा, कम से कम 64 बाइट्स हैं, जो दो मापदंडों के लिए पर्याप्त हैं। यदि नहीं तो स्पष्ट रूप से कुछ गड़बड़ है।

if स्टेटमेंट यह जांचता प्रतीत होता है कि _param1 शून्य नहीं है, और यह कि _param1 * _param2 ऋणात्मक नहीं है। यह शायद रैप अराउंड के मामलों को रोकने के लिए है।

अंत में, फ़ंक्शन एक स्केल्ड मान लौटाता है।

दावा

डीकंपाइलर द्वारा बनाया गया कोड जटिल है, और इसका सब कुछ हमारे लिए प्रासंगिक नहीं है। मैं उपयोगी जानकारी प्रदान करने वाली पंक्तियों पर ध्यान केंद्रित करने के लिए इसमें से कुछ को छोड़ने जा रहा हूं

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

हम यहां दो महत्वपूर्ण बातें देखते हैं:

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

तो अब हम जानते हैं कि Storage[5] विंडो और पतों की एक सरणी है, और क्या पते ने उस विंडो के लिए इनाम का दावा किया है।

1 ...
2 idx = 0
3 s = 0
4 while idx < _param4.length:
5 ...
6 if s + sha3(mem[(32 * _param4.length) + 328 len mem[(32 * _param4.length) + 296]]) > mem[(32 * idx) + 296]:
7 mem[mem[64] + 32] = mem[(32 * idx) + 296]
8 ...
9 s = sha3(mem[_62 + 32 len mem[_62]])
10 continue
11 ...
12 s = sha3(mem[_66 + 32 len mem[_66]])
13 continue
14 if unknown2eb4a7ab != s:
15 revert with 0, 'Invalid proof'

हम जानते हैं कि unknown2eb4a7ab वास्तव में फ़ंक्शन merkleRoot() है, इसलिए यह कोड ऐसा लगता है जैसे यह एक मर्कल प्रूफ (opens in a new tab) की पुष्टि कर रहा है। इसका मतलब है कि _param4 एक मर्कल प्रूफ है।

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

यह है कि कैसे एक कॉन्ट्रैक्ट अपने ETH को दूसरे पते (कॉन्ट्रैक्ट या बाहरी रूप से स्वामित्व वाले) में स्थानांतरित करता है। यह इसे एक मान के साथ कॉल करता है जो स्थानांतरित की जाने वाली राशि है। तो ऐसा लगता है कि यह ETH का एक एयरड्रॉप है।

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

नीचे की दो पंक्तियाँ हमें बताती हैं कि Storage[2] भी एक कॉन्ट्रैक्ट है जिसे हम कॉल करते हैं। यदि हम कंस्ट्रक्टर लेनदेन को देखते हैं (opens in a new tab) तो हम देखते हैं कि यह कॉन्ट्रैक्ट 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 (opens in a new tab) है, एक रैप्ड ईथर कॉन्ट्रैक्ट जिसका सोर्स कोड ईथरस्कैन पर अपलोड किया गया है (opens in a new tab)

तो ऐसा लगता है कि कॉन्ट्रैक्ट _param2 को ETH भेजने का प्रयास करता है। अगर यह कर सकता है, तो बढ़िया। यदि नहीं, तो यह WETH (opens in a new tab) भेजने का प्रयास करता है। यदि _param2 एक बाहरी स्वामित्व वाला खाता (EOA) है तो यह हमेशा ETH प्राप्त कर सकता है, लेकिन कॉन्ट्रैक्ट ETH प्राप्त करने से इनकार कर सकते हैं। हालांकि, WETH ERC-20 है और कॉन्ट्रैक्ट इसे स्वीकार करने से इनकार नहीं कर सकते हैं।

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

फ़ंक्शन के अंत में हम देखते हैं कि एक लॉग प्रविष्टि उत्पन्न हो रही है। उत्पन्न लॉग प्रविष्टियों को देखें (opens in a new tab) और उस विषय पर फ़िल्टर करें जो 0xdbd5... से शुरू होता है। यदि हम ऐसी प्रविष्टि उत्पन्न करने वाले लेनदेन में से किसी एक पर क्लिक करते हैं (opens in a new tab) तो हम देखते हैं कि वास्तव में यह एक दावे की तरह लगता है - खाते ने उस कॉन्ट्रैक्ट को एक संदेश भेजा जिसकी हम रिवर्स इंजीनियरिंग कर रहे हैं, और बदले में ETH मिला।

एक दावा लेनदेन

1e7df9d3

यह फ़ंक्शन ऊपर दिए गए claim के बहुत समान है। यह एक मर्कल प्रूफ की भी जांच करता है, पहले को ETH स्थानांतरित करने का प्रयास करता है, और उसी प्रकार की लॉग प्रविष्टि उत्पन्न करता है।

1def unknown1e7df9d3(uint256 _param1, uint256 _param2, array _param3) payable:
2 ...
3 idx = 0
4 s = 0
5 while idx < _param3.length:
6 if idx >= mem[96]:
7 revert with 0, 50
8 _55 = mem[(32 * idx) + 128]
9 if s + sha3(mem[(32 * _param3.length) + 160 len mem[(32 * _param3.length) + 128]]) > mem[(32 * idx) + 128]:
10 ...
11 s = sha3(mem[_58 + 32 len mem[_58]])
12 continue
13 mem[mem[64] + 32] = s + sha3(mem[(32 * _param3.length) + 160 len mem[(32 * _param3.length) + 128]])
14 ...
15 if unknown2eb4a7ab != s:
16 revert with 0, 'Invalid proof'
17 ...
18 call addr(_param1) with:
19 value s wei
20 gas 30000 wei
21 if not return_data.size:
22 if not ext_call.success:
23 require ext_code.size(stor2)
24 call stor2.deposit() with:
25 value s wei
26 gas gas_remaining wei
27 ...
28 log 0xdbd5389f: addr(_param1), s, bool(ext_call.success)

मुख्य अंतर यह है कि पहला पैरामीटर, निकालने के लिए विंडो, वहां नहीं है। इसके बजाय, दावा की जा सकने वाली सभी विंडो पर एक लूप है।

1 idx = 0
2 s = 0
3 while idx < currentWindow:
4 ...
5 if stor5[mem[0]]:
6 if idx == -1:
7 revert with 0, 17
8 idx = idx + 1
9 s = s
10 continue
11 ...
12 stor5[idx][addr(_param1)] = 1
13 if idx >= unknown81e580d3.length:
14 revert with 0, 50
15 mem[0] = 4
16 if unknown81e580d3[idx] and _param2 > -1 / unknown81e580d3[idx]:
17 revert with 0, 17
18 if s > !(unknown81e580d3[idx] * _param2 / 100 * 10^6):
19 revert with 0, 17
20 if idx == -1:
21 revert with 0, 17
22 idx = idx + 1
23 s = s + (unknown81e580d3[idx] * _param2 / 100 * 10^6)
24 continue

तो यह एक claim संस्करण जैसा लगता है जो सभी विंडो का दावा करता है।

निष्कर्ष

अब तक आपको पता होना चाहिए कि उन कॉन्ट्रैक्ट्स को कैसे समझा जाए जिनका सोर्स कोड उपलब्ध नहीं है, या तो ऑपकोड का उपयोग करके या (जब यह काम करता है) डीकंपाइलर का उपयोग करके। जैसा कि इस लेख की लंबाई से स्पष्ट है, किसी कॉन्ट्रैक्ट की रिवर्स इंजीनियरिंग करना कोई मामूली बात नहीं है, लेकिन एक ऐसी प्रणाली में जहाँ सुरक्षा आवश्यक है, यह एक महत्वपूर्ण कौशल है कि यह सत्यापित किया जा सके कि कॉन्ट्रैक्ट वादे के अनुसार काम करते हैं।

मेरे और काम के लिए यहाँ देखें (opens in a new tab)

पेज का अंतिम अपडेट: 3 मार्च 2026

क्या यह ट्यूटोरियल सहायक था?