ஒரு ஒப்பந்தத்தை ரிவர்ஸ் இன்ஜினியரிங் செய்தல்
அறிமுகம்
பிளாக்செயினில் ரகசியங்கள் எதுவும் இல்லை, நடக்கும் அனைத்தும் சீரானதாகவும், சரிபார்க்கக்கூடியதாகவும், பொதுவில் கிடைக்கக்கூடியதாகவும் உள்ளது. சிறந்தமுறையில், ஒப்பந்தங்களின் மூலக் குறியீட்டை Etherscan-இல் வெளியிட்டு சரிபார்க்க வேண்டும்opens in a new tab. இருப்பினும், அது எப்போதும் அப்படி இருப்பதில்லைopens in a new tab. இந்தக் கட்டுரையில், மூலக் குறியீடு இல்லாத ஒரு ஒப்பந்தமான 0x2510c039cc3b061d79e564b38836da87e31b342fopens in a new tab-ஐப் பார்த்து, ஒப்பந்தங்களை எவ்வாறு ரிவர்ஸ் இன்ஜினியரிங் செய்வது என்பதை நீங்கள் கற்றுக்கொள்கிறீர்கள்.
ரிவர்ஸ் கம்பைலர்கள் உள்ளன, ஆனால் அவை எப்போதும் பயன்படுத்தக்கூடிய முடிவுகளைத்opens in a new tab தருவதில்லை. இந்தக் கட்டுரையில், the opcodesopens in a new tab இலிருந்து ஒரு ஒப்பந்தத்தை கைமுறையாக ரிவர்ஸ் இன்ஜினியரிங் செய்து புரிந்துகொள்வது எப்படி, அத்துடன் ஒரு டீகம்பைலரின் முடிவுகளை எவ்வாறு விளக்குவது என்பதையும் நீங்கள் கற்றுக்கொள்கிறீர்கள்.
இந்தக் கட்டுரையைப் புரிந்துகொள்ள, நீங்கள் ஏற்கனவே EVM-இன் அடிப்படைகளை அறிந்திருக்க வேண்டும், மேலும் EVM அசெம்ப்ளருடன் ஓரளவாவது பரிச்சயமாக இருக்க வேண்டும். இந்த தலைப்புகளைப் பற்றி இங்கே படிக்கலாம்opens in a new tab.
செயல்படுத்தக்கூடிய குறியீட்டைத் தயார்செய்தல்
ஒப்பந்தத்திற்காக Etherscan-க்குச் சென்று, Contract தாவலைக் கிளிக் செய்து, பின்னர் Switch to Opcodes View என்பதைக் கிளிக் செய்வதன் மூலம் நீங்கள் ஆப்கோட்களைப் பெறலாம். ஒரு வரிக்கு ஒரு opcode கொண்ட ஒரு பார்வையை நீங்கள் பெறுவீர்கள்.
இருப்பினும், தாவல்களைப் புரிந்துகொள்ள, ஒவ்வொரு opcode-ம் குறியீட்டில் எங்கு அமைந்துள்ளது என்பதை நீங்கள் தெரிந்து கொள்ள வேண்டும். அதைச் செய்ய, ஒரு வழி, ஒரு Google விரிதாளைத் திறந்து நெடுவரிசை C இல் opcodes-களை ஒட்டுவது. ஏற்கனவே தயாரிக்கப்பட்ட இந்த விரிதாளின் நகலை உருவாக்குவதன் மூலம் பின்வரும் படிகளை நீங்கள் தவிர்க்கலாம்opens in a new tab.
அடுத்த கட்டமாக, சரியான குறியீடு இருப்பிடங்களைப் பெறுவது, அதன்மூலம் நம்மால் தாவல்களைப் புரிந்துகொள்ள முடியும். நாம் opcode அளவை நெடுவரிசை B-யிலும், இருப்பிடத்தை (ஹெக்ஸாடெசிமலில்) நெடுவரிசை A-யிலும் வைப்போம். செல் B1-இல் இந்தச் சார்பைத் தட்டச்சு செய்து, பின்னர் அதை நெடுவரிசை B-இன் மற்ற பகுதிகளுக்கு, குறியீட்டின் இறுதி வரை நகலெடுத்து ஒட்டவும். இதைச் செய்த பிறகு நீங்கள் நெடுவரிசை B-ஐ மறைக்கலாம்.
1=1+IF(REGEXMATCH(C1,"PUSH"),REGEXEXTRACT(C1,"PUSH(\d+)"),0)முதலில் இந்தச் சார்பு opcode-க்காகவே ஒரு பைட்டைச் சேர்க்கிறது, பின்னர் PUSH-ஐத் தேடுகிறது. Push opcodes சிறப்பானவை, ஏனெனில் தள்ளப்படும் மதிப்புக்கு அவற்றுக்குக் கூடுதல் பைட்டுகள் தேவை. opcode ஒரு PUSH ஆக இருந்தால், நாம் பைட்டுகளின் எண்ணிக்கையைப் பிரித்தெடுத்து அதைச் சேர்ப்போம்.
A1-இல் முதல் ஆஃப்செட்டான பூஜ்ஜியத்தை உள்ளிடவும். பின்னர், A2-இல், இந்தச் சார்பை உள்ளிட்டு, மீண்டும் அதை நெடுவரிசை A-இன் மற்ற பகுதிகளுக்கு நகலெடுத்து ஒட்டவும்:
1=dec2hex(hex2dec(A1)+B1)இந்தச் சார்பு நமக்கு ஹெக்ஸாடெசிமல் மதிப்பைத் தர வேண்டும். ஏனெனில் தாவல்களுக்கு (JUMP மற்றும் JUMPI) முன்பு தள்ளப்படும் மதிப்புகள் நமக்கு ஹெக்ஸாடெசிமலில் கொடுக்கப்பட்டுள்ளன.
நுழைவு புள்ளி (0x00)
ஒப்பந்தங்கள் எப்போதும் முதல் பைட்டிலிருந்து செயல்படுத்தப்படுகின்றன. இது குறியீட்டின் ஆரம்பப் பகுதி:
| ஆஃப்செட் | Opcode | ஸ்டாக் (opcode-க்கு பிறகு) |
|---|---|---|
| 0 | PUSH1 0x80 | 0x80 |
| 2 | PUSH1 0x40 | 0x40, 0x80 |
| 4 | MSTORE | காலி |
| 5 | PUSH1 0x04 | 0x04 |
| 7 | CALLDATASIZE | CALLDATASIZE 0x04 |
| 8 | LT | CALLDATASIZE<4 |
| 9 | PUSH2 0x005e | 0x5E CALLDATASIZE<4 |
| சி | JUMPI | காலி |
இந்தக் குறியீடு இரண்டு விஷயங்களைச் செய்கிறது:
- 0x80-ஐ 32 பைட் மதிப்பாக நினைவக இருப்பிடங்கள் 0x40-0x5F-இல் எழுதுங்கள் (0x80 ஆனது 0x5F-இல் சேமிக்கப்படுகிறது, மற்றும் 0x40-0x5E அனைத்தும் பூஜ்ஜியங்களாக உள்ளன).
- calldata அளவைப் படிக்கவும். வழக்கமாக ஒரு எத்தேரியம் ஒப்பந்தத்திற்கான அழைப்புத் தரவு the ABI (பயன்பாட்டு பைனரி இடைமுகம்)opens in a new tab-ஐப் பின்பற்றுகிறது, இதற்கு சார்புத் தேர்விக்கு குறைந்தபட்சம் நான்கு பைட்டுகள் தேவை. அழைப்பு தரவு அளவு நான்கிற்கும் குறைவாக இருந்தால், 0x5E-க்குச் செல்லவும்.
0x5E-இல் உள்ள ஹேண்ட்லர் (ABI அல்லாத அழைப்புத் தரவுகளுக்கு)
| ஆஃப்செட் | Opcode |
|---|---|
| 5E | JUMPDEST |
| 5F | CALLDATASIZE |
| 60 | PUSH2 0x007c |
| 63 | JUMPI |
இந்தத் துணுக்கு ஒரு JUMPDEST உடன் தொடங்குகிறது. EVM (எத்தேரியம் மெய்நிகர் இயந்திரம்) நிரல்கள் நீங்கள் JUMPDEST இல்லாத ஒரு opcode-க்கு தாவினால் ஒரு விதிவிலக்கை வீசும். பின்னர் அது CALLDATASIZE-ஐப் பார்க்கிறது, அது "உண்மை" ஆக இருந்தால் (அதாவது, பூஜ்ஜியம் இல்லை) 0x7C-க்கு தாவுகிறது. நாம் கீழே அதைப்பற்றி பார்ப்போம்.
| ஆஃப்செட் | Opcode | ஸ்டாக் (opcode-க்கு பிறகு) |
|---|---|---|
| 64 | CALLVALUE | அழைப்பால் வழங்கப்படும் . Solidity-இல் msg.value என்று அழைக்கப்படுகிறது |
| 65 | PUSH1 0x06 | 6 CALLVALUE |
| 67 | PUSH1 0x00 | 0 6 CALLVALUE |
| 69 | DUP3 | CALLVALUE 0 6 CALLVALUE |
| 6A | DUP3 | 6 CALLVALUE 0 6 CALLVALUE |
| 6B | SLOAD | Storage[6] CALLVALUE 0 6 CALLVALUE |
எனவே அழைப்புத் தரவு இல்லாதபோது, நாம் Storage[6]-இன் மதிப்பை படிக்கிறோம். இந்த மதிப்பு என்னவென்று நமக்கு இன்னும் தெரியாது, ஆனால் அழைப்புத் தரவுகள் இல்லாத ஒப்பந்தம் பெற்ற பரிவர்த்தனைகளை நாம் தேடலாம். எந்தவொரு அழைப்புத் தரவும் இல்லாமல் (எனவே எந்த முறையும் இல்லாமல்) ETH-ஐ மட்டும் மாற்றும் பரிவர்த்தனைகள் Etherscan-இல் Transfer முறையைக் கொண்டுள்ளன. உண்மையில், ஒப்பந்தம் பெற்ற முதல் பரிவர்த்தனைopens in a new tab ஒரு பரிமாற்றமாகும்.
அந்தப் பரிவர்த்தனையில் நாம் மேலும் பார்க்க கிளிக் செய்யவும் என்பதை கிளிக் செய்தால், உள்ளீட்டுத் தரவு என்று அழைக்கப்படும் அழைப்புத் தரவு உண்மையில் காலியாக (0x) இருப்பதைக் காணலாம். மதிப்பு 1.559 ETH என்பதையும் கவனியுங்கள், அது பின்னர் பொருத்தமானதாக இருக்கும்.
அடுத்து, State தாவலைக் கிளிக் செய்து, நாம் ரிவர்ஸ் இன்ஜினியரிங் செய்யும் ஒப்பந்தத்தை (0x2510...) விரிவாக்கவும். பரிவர்த்தனையின் போது Storage[6] மாறியதைக் காணலாம், மேலும் நீங்கள் Hex-ஐ எண் ஆக மாற்றினால், அது 1,559,000,000,000,000,000 ஆக மாறியதைக் காண்பீர்கள். இது wei-இல் மாற்றப்பட்ட மதிப்பாகும் (தெளிவுக்காக நான் காற்புள்ளிகளைச் சேர்த்துள்ளேன்), அடுத்த ஒப்பந்த மதிப்புக்கு ஒத்திருக்கிறது.
அதே காலகட்டத்தில் இருந்து பிற Transfer பரிவர்த்தனைகளால் ஏற்படும் நிலை மாற்றங்களைப்opens in a new tab பார்த்தால், Storage[6] சிறிது காலத்திற்கு ஒப்பந்தத்தின் மதிப்பைக் கண்காணித்திருப்பதைக் காணலாம். இப்போதைக்கு அதை Value* என்று அழைப்போம். நட்சத்திரக்குறி (*) இந்த மாறி என்ன செய்கிறது என்பது நமக்கு இன்னும் தெரியாது என்பதை நினைவூட்டுகிறது, ஆனால் அது ஒப்பந்த மதிப்பை மட்டும் கண்காணிப்பதாக இருக்க முடியாது. ஏனெனில், ADDRESS BALANCE-ஐப் பயன்படுத்தி உங்கள் கணக்குகளின் இருப்பைப் பெறக்கூடிய நிலையில், மிகவும் விலையுயர்ந்த சேமிப்பகத்தைப் பயன்படுத்தத் தேவையில்லை. முதல் opcode ஒப்பந்தத்தின் சொந்த முகவரியை தள்ளுகிறது. இரண்டாவது, ஸ்டேக்கின் மேலே உள்ள முகவரியைப் படித்து, அதை அந்த முகவரியின் இருப்புடன் மாற்றுகிறது.
| ஆஃப்செட் | Opcode | அடுக்கு |
|---|---|---|
| 6C | PUSH2 0x0075 | 0x75 Value* CALLVALUE 0 6 CALLVALUE |
| 6F | SWAP2 | CALLVALUE Value* 0x75 0 6 CALLVALUE |
| 70 | SWAP1 | Value* CALLVALUE 0x75 0 6 CALLVALUE |
| 71 | PUSH2 0x01a7 | 0x01A7 Value* CALLVALUE 0x75 0 6 CALLVALUE |
| 74 | JUMP |
இந்தக் குறியீட்டை நாம் ஜம்ப் டெஸ்டினேஷனில் தொடர்ந்து கண்டுபிடிப்போம்.
| ஆஃப்செட் | Opcode | அடுக்கு |
|---|---|---|
| 1A7 | JUMPDEST | Value* CALLVALUE 0x75 0 6 CALLVALUE |
| 1A8 | PUSH1 0x00 | 0x00 Value* CALLVALUE 0x75 0 6 CALLVALUE |
| 1AA | DUP3 | CALLVALUE 0x00 Value* CALLVALUE 0x75 0 6 CALLVALUE |
| 1AB | NOT | 2^256-CALLVALUE-1 0x00 Value* CALLVALUE 0x75 0 6 CALLVALUE |
NOT என்பது பிட் வாரியானது, எனவே அது அழைப்பு மதிப்பில் உள்ள ஒவ்வொரு பிட்டின் மதிப்பையும் மாற்றுகிறது.
| ஆஃப்செட் | Opcode | அடுக்கு |
|---|---|---|
| 1AC | DUP3 | Value* 2^256-CALLVALUE-1 0x00 Value* CALLVALUE 0x75 0 6 CALLVALUE |
| 1AD | GT | Value*>2^256-CALLVALUE-1 0x00 Value* CALLVALUE 0x75 0 6 CALLVALUE |
| 1AE | ISZERO | Value*<=2^256-CALLVALUE-1 0x00 Value* CALLVALUE 0x75 0 6 CALLVALUE |
| 1AF | PUSH2 0x01df | 0x01DF Value*<=2^256-CALLVALUE-1 0x00 Value* CALLVALUE 0x75 0 6 CALLVALUE |
| 1B2 | JUMPI |
Value* ஆனது 2^256-CALLVALUE-1 ஐ விட சிறியதாக அல்லது அதற்கு சமமாக இருந்தால் நாம் தாவுகிறோம். இது வழிதலைத் தடுக்கும் தர்க்கம் போல் தெரிகிறது. உண்மையில், சில அர்த்தமற்ற செயல்பாடுகளுக்குப் பிறகு (உதாரணமாக, நினைவகத்தில் எழுதுவது நீக்கப்பட உள்ளது), ஓவர்ஃப்ளோ கண்டறியப்பட்டால், ஆஃப்செட் 0x01DE-இல் ஒப்பந்தம் திரும்புகிறது, இது இயல்பான நடத்தை.
அத்தகைய ஒரு ஓவர்ஃப்ளோ மிகவும் சாத்தியமற்றது என்பதை கவனியுங்கள், ஏனெனில் அதற்கு அழைப்பு மதிப்பு மற்றும் Value* ஆகியவை 2^256 wei உடன் ஒப்பிடக்கூடியதாக இருக்க வேண்டும், இது சுமார் 10^59 ETH. எழுதும்போது மொத்த ETH விநியோகம் இருநூறு மில்லியனுக்கும் குறைவாக உள்ளதுopens in a new tab.
| ஆஃப்செட் | Opcode | அடுக்கு |
|---|---|---|
| 1DF | JUMPDEST | 0x00 Value* CALLVALUE 0x75 0 6 CALLVALUE |
| 1E0 | POP | Value* CALLVALUE 0x75 0 6 CALLVALUE |
| 1E1 | ADD | Value*+CALLVALUE 0x75 0 6 CALLVALUE |
| 1E2 | SWAP1 | 0x75 Value*+CALLVALUE 0 6 CALLVALUE |
| 1E3 | JUMP |
நாம் இங்கே வந்தால், Value* + CALLVALUE பெற்று ஆஃப்செட் 0x75 க்கு தாவவும்.
| ஆஃப்செட் | Opcode | அடுக்கு |
|---|---|---|
| 75 | JUMPDEST | Value*+CALLVALUE 0 6 CALLVALUE |
| 76 | SWAP1 | 0 Value*+CALLVALUE 6 CALLVALUE |
| 77 | SWAP2 | 6 Value*+CALLVALUE 0 CALLVALUE |
| 78 | SSTORE | 0 CALLVALUE |
நாம் இங்கே வந்தால் (அழைப்புத் தரவு காலியாக இருக்க வேண்டும்), Value*-க்கு அழைப்பு மதிப்பைச் சேர்ப்போம். இது Transfer பரிவர்த்தனைகள் செய்வதாக நாம் கூறுவதுடன் ஒத்துப்போகிறது.
| ஆஃப்செட் | Opcode |
|---|---|
| 79 | POP |
| 7A | POP |
| 7B | STOP |
இறுதியாக, அடுக்கைத் துடைத்து (இது அவசியமில்லை) பரிவர்த்தனையின் வெற்றிகரமான முடிவை சமிக்ஞை செய்யவும்.
சுருக்கமாகச் சொன்னால், ஆரம்பக் குறியீட்டிற்கான ஒரு பாய்வு விளக்கப்படம் இங்கே.
0x7C இல் உள்ள ஹேண்ட்லர்
இந்த ஹேண்ட்லர் என்ன செய்கிறது என்பதை நான் வேண்டுமென்றே தலைப்பில் வைக்கவில்லை. இந்த குறிப்பிட்ட ஒப்பந்தம் எவ்வாறு செயல்படுகிறது என்பதை உங்களுக்கு கற்பிப்பது நோக்கம் அல்ல, ஆனால் ஒப்பந்தங்களை எவ்வாறு ரிவர்ஸ் இன்ஜினியரிங் செய்வது என்பதே நோக்கம். நான் செய்ததைப் போலவே, குறியீட்டைப் பின்பற்றி அது என்ன செய்கிறது என்பதை நீங்கள் கற்றுக்கொள்வீர்கள்.
பல இடங்களில் இருந்து நாம் இங்கே வருகிறோம்:
- 1, 2, அல்லது 3 பைட்டுகள் கொண்ட அழைப்புத் தரவு இருந்தால் (ஆஃப்செட் 0x63 இலிருந்து)
- முறை கையொப்பம் தெரியவில்லை என்றால் (ஆஃப்செட்டுகள் 0x42 மற்றும் 0x5D இலிருந்து)
| ஆஃப்செட் | Opcode | அடுக்கு |
|---|---|---|
| 7C | JUMPDEST | |
| 7D | PUSH1 0x00 | 0x00 |
| 7F | PUSH2 0x009d | 0x9D 0x00 |
| 82 | PUSH1 0x03 | 0x03 0x9D 0x00 |
| 84 | SLOAD | Storage[3] 0x9D 0x00 |
இது மற்றொரு சேமிப்பக செல், எந்தவொரு பரிவர்த்தனையிலும் என்னால் கண்டுபிடிக்க முடியாத ஒன்று, எனவே இதன் பொருள் என்னவென்று அறிவது கடினம். கீழேயுள்ள குறியீடு அதைத் தெளிவாக்கும்.
| ஆஃப்செட் | Opcode | அடுக்கு |
|---|---|---|
| 85 | PUSH20 0xffffffffffffffffffffffffffffffffffffffff | 0xff....ff Storage[3] 0x9D 0x00 |
| 9A | AND | Storage[3]-as-address 0x9D 0x00 |
இந்த ஆப்கோட்கள் நாம் Storage[3]-இலிருந்து படிக்கும் மதிப்பை 160 பிட்களுக்கு, அதாவது ஒரு எத்தேரியம் முகவரியின் நீளத்திற்கு, குறைக்கின்றன.
| ஆஃப்செட் | Opcode | அடுக்கு |
|---|---|---|
| 9B | SWAP1 | 0x9D Storage[3]-as-address 0x00 |
| 9C | JUMP | Storage[3]-as-address 0x00 |
இந்த ஜம்ப் தேவையற்றது, ஏனெனில் நாம் அடுத்த opcode-க்குச் செல்கிறோம். இந்தக் குறியீடு இருக்கக்கூடிய அளவுக்கு எரிவாயு திறன் கொண்டதாக இல்லை.
| ஆஃப்செட் | Opcode | அடுக்கு |
|---|---|---|
| 9D | JUMPDEST | Storage[3]-as-address 0x00 |
| 9E | SWAP1 | 0x00 Storage[3]-as-address |
| 9F | POP | Storage[3]-as-address |
| A0 | PUSH1 0x40 | 0x40 Storage[3]-as-address |
| A2 | MLOAD | Mem[0x40] Storage[3]-as-address |
குறியீட்டின் ஆரம்பத்திலேயே Mem[0x40]-ஐ 0x80 என அமைத்தோம். பின்னர் 0x40-ஐ தேடினால், அதை மாற்றவில்லை என்பதைக் காணலாம் - எனவே அது 0x80 என்று கருதலாம்.
| ஆஃப்செட் | Opcode | அடுக்கு |
|---|---|---|
| A3 | CALLDATASIZE | CALLDATASIZE 0x80 Storage[3]-as-address |
| A4 | PUSH1 0x00 | 0x00 CALLDATASIZE 0x80 Storage[3]-as-address |
| A6 | DUP3 | 0x80 0x00 CALLDATASIZE 0x80 Storage[3]-as-address |
| A7 | CALLDATACOPY | 0x80 Storage[3]-as-address |
அனைத்து அழைப்புத் தரவையும் நினைவகத்தில், 0x80-இல் தொடங்கி, நகலெடுக்கவும்.
| ஆஃப்செட் | Opcode | அடுக்கு |
|---|---|---|
| A8 | PUSH1 0x00 | 0x00 0x80 Storage[3]-as-address |
| AA | DUP1 | 0x00 0x00 0x80 Storage[3]-as-address |
| AB | CALLDATASIZE | CALLDATASIZE 0x00 0x00 0x80 Storage[3]-as-address |
| AC | DUP4 | 0x80 CALLDATASIZE 0x00 0x00 0x80 Storage[3]-as-address |
| AD | DUP6 | Storage[3]-as-address 0x80 CALLDATASIZE 0x00 0x00 0x80 Storage[3]-as-address |
| AE | GAS | GAS Storage[3]-as-address 0x80 CALLDATASIZE 0x00 0x00 0x80 Storage[3]-as-address |
| AF | DELEGATE_CALL |
இப்போது விஷயங்கள் மிகவும் தெளிவாக உள்ளன. இந்த ஒப்பந்தம் ஒரு ப்ராக்ஸிopens in a new tab-ஆக செயல்பட முடியும், உண்மையான வேலையைச் செய்ய Storage[3]-இல் உள்ள முகவரியை அழைக்கிறது. DELEGATE_CALL ஒரு தனி ஒப்பந்தத்தை அழைக்கிறது, ஆனால் அதே சேமிப்பகத்தில் உள்ளது. இதன் பொருள், நாம் ப்ராக்ஸியாக இருக்கும் பிரதிநிதித்துவ ஒப்பந்தம், அதே சேமிப்பக இடத்தை அணுகுகிறது. அழைப்பின் அளவுருக்கள்:
- Gas: மீதமுள்ள அனைத்து எரிவாயு
- அழைக்கப்பட்ட முகவரி: Storage[3]-as-address
- அழைப்புத் தரவு: 0x80-இல் தொடங்கும் CALLDATASIZE பைட்டுகள், இங்குதான் நாம் அசல் அழைப்புத் தரவை வைத்தோம்
- திரும்பும் தரவு: எதுவுமில்லை (0x00 - 0x00) நாம் திரும்பும் தரவை மற்ற வழிகளில் பெறுவோம் (கீழே காண்க)
| ஆஃப்செட் | Opcode | அடுக்கு |
|---|---|---|
| B0 | RETURNDATASIZE | RETURNDATASIZE (((அழைப்பு வெற்றி/தோல்வி))) 0x80 Storage[3]-as-address |
| B1 | DUP1 | RETURNDATASIZE RETURNDATASIZE (((அழைப்பு வெற்றி/தோல்வி))) 0x80 Storage[3]-as-address |
| B2 | PUSH1 0x00 | 0x00 RETURNDATASIZE RETURNDATASIZE (((அழைப்பு வெற்றி/தோல்வி))) 0x80 Storage[3]-as-address |
| B4 | DUP5 | 0x80 0x00 RETURNDATASIZE RETURNDATASIZE (((அழைப்பு வெற்றி/தோல்வி))) 0x80 Storage[3]-as-address |
| B5 | RETURNDATACOPY | RETURNDATASIZE (((அழைப்பு வெற்றி/தோல்வி))) 0x80 Storage[3]-as-address |
இங்கே நாம் அனைத்து திரும்பும் தரவையும் 0x80-இல் தொடங்கும் நினைவக பஃப்பரில் நகலெடுக்கிறோம்.
| ஆஃப்செட் | Opcode | அடுக்கு |
|---|---|---|
| B6 | DUP2 | (((அழைப்பு வெற்றி/தோல்வி))) RETURNDATASIZE (((அழைப்பு வெற்றி/தோல்வி))) 0x80 Storage[3]-as-address |
| B7 | DUP1 | (((அழைப்பு வெற்றி/தோல்வி))) (((அழைப்பு வெற்றி/தோல்வி))) RETURNDATASIZE (((அழைப்பு வெற்றி/தோல்வி))) 0x80 Storage[3]-as-address |
| B8 | ISZERO | (((அழைப்பு தோல்வியுற்றதா))) (((அழைப்பு வெற்றி/தோல்வி))) RETURNDATASIZE (((அழைப்பு வெற்றி/தோல்வி))) 0x80 Storage[3]-as-address |
| B9 | PUSH2 0x00c0 | 0xC0 (((அழைப்பு தோல்வியுற்றதா))) (((அழைப்பு வெற்றி/தோல்வி))) RETURNDATASIZE (((அழைப்பு வெற்றி/தோல்வி))) 0x80 Storage[3]-as-address |
| BC | JUMPI | (((அழைப்பு வெற்றி/தோல்வி))) RETURNDATASIZE (((அழைப்பு வெற்றி/தோல்வி))) 0x80 Storage[3]-as-address |
| BD | DUP2 | RETURNDATASIZE (((அழைப்பு வெற்றி/தோல்வி))) RETURNDATASIZE (((அழைப்பு வெற்றி/தோல்வி))) 0x80 Storage[3]-as-address |
| BE | DUP5 | 0x80 RETURNDATASIZE (((அழைப்பு வெற்றி/தோல்வி))) RETURNDATASIZE (((அழைப்பு வெற்றி/தோல்வி))) 0x80 Storage[3]-as-address |
| BF | RETURN |
எனவே அழைப்பிற்குப் பிறகு, திரும்பும் தரவை 0x80 - 0x80+RETURNDATASIZE என்ற பஃபரில் நகலெடுத்து, அழைப்பு வெற்றிகரமாக இருந்தால், அந்த பஃபரைக் கொண்டு RETURN செய்கிறோம்.
DELEGATECALL தோல்வியுற்றது
நாம் இங்கே 0xC0-க்கு வந்தால், நாம் அழைத்த ஒப்பந்தம் திரும்பப் பெறப்பட்டது என்று பொருள். நாம் அந்த ஒப்பந்தத்திற்கு ஒரு ப்ராக்ஸியாக இருப்பதால், அதே தரவைத் திருப்பித் தந்து, திரும்பப் பெறவும் விரும்புகிறோம்.
| ஆஃப்செட் | Opcode | அடுக்கு |
|---|---|---|
| C0 | JUMPDEST | (((அழைப்பு வெற்றி/தோல்வி))) RETURNDATASIZE (((அழைப்பு வெற்றி/தோல்வி))) 0x80 Storage[3]-as-address |
| C1 | DUP2 | RETURNDATASIZE (((அழைப்பு வெற்றி/தோல்வி))) RETURNDATASIZE (((அழைப்பு வெற்றி/தோல்வி))) 0x80 Storage[3]-as-address |
| C2 | DUP5 | 0x80 RETURNDATASIZE (((அழைப்பு வெற்றி/தோல்வி))) RETURNDATASIZE (((அழைப்பு வெற்றி/தோல்வி))) 0x80 Storage[3]-as-address |
| C3 | REVERT |
எனவே நாம் முன்பு RETURN க்கு பயன்படுத்திய அதே பஃபர் மூலம் REVERT செய்கிறோம்: 0x80 - 0x80+RETURNDATASIZE
ABI அழைப்புகள்
அழைப்பு தரவு அளவு நான்கு பைட்டுகள் அல்லது அதற்கு அதிகமாக இருந்தால், இது ஒரு செல்லுபடியாகும் ABI அழைப்பாக இருக்கலாம்.
| ஆஃப்செட் | Opcode | அடுக்கு |
|---|---|---|
| டி | PUSH1 0x00 | 0x00 |
| எஃப் | CALLDATALOAD | (((அழைப்பு தரவின் முதல் சொல் (256 பிட்கள்)))) |
| 10 | PUSH1 0xe0 | 0xE0 (((அழைப்பு தரவின் முதல் சொல் (256 பிட்கள்)))) |
| 12 | SHR | (((அழைப்பு தரவின் முதல் 32 பிட்கள் (4 பைட்டுகள்)))) |
1C என்பது அறியப்படாத opcode என்று Etherscan நமக்குச் சொல்கிறது, ஏனெனில் Etherscan இந்த அம்சத்தை எழுதிய பிறகு அது சேர்க்கப்பட்டதுopens in a new tab மற்றும் அவர்கள் அதை புதுப்பிக்கவில்லை. ஒரு புதுப்பிக்கப்பட்ட opcode அட்டவணைopens in a new tab இது வலதுபுறம் நகர்த்துதல் என்பதைக் காட்டுகிறது
| ஆஃப்செட் | Opcode | அடுக்கு |
|---|---|---|
| 13 | DUP1 | (((அழைப்பு தரவின் முதல் 32 பிட்கள் (4 பைட்டுகள்))) (((அழைப்பு தரவின் முதல் 32 பிட்கள் (4 பைட்டுகள்)))) |
| 14 | PUSH4 0x3cd8045e | 0x3CD8045E (((அழைப்பு தரவின் முதல் 32 பிட்கள் (4 பைட்டுகள்))) (((அழைப்பு தரவின் முதல் 32 பிட்கள் (4 பைட்டுகள்)))) |
| 19 | GT | 0x3CD8045E>அழைப்பு-தரவின்-முதல்-32-பிட்கள் (((அழைப்பு தரவின் முதல் 32 பிட்கள் (4 பைட்டுகள்)))) |
| 1A | PUSH2 0x0043 | 0x43 0x3CD8045E>அழைப்பு-தரவின்-முதல்-32-பிட்கள் (((அழைப்பு தரவின் முதல் 32 பிட்கள் (4 பைட்டுகள்)))) |
| 1D | JUMPI | (((அழைப்பு தரவின் முதல் 32 பிட்கள் (4 பைட்டுகள்)))) |
முறை கையொப்பம் பொருந்தக்கூடிய சோதனைகளை இதுபோன்று இரண்டாகப் பிரிப்பதன் மூலம் சராசரியாக பாதி சோதனைகளை சேமிக்க முடியும். இதைத் தொடர்ந்து வரும் குறியீடு மற்றும் 0x43-இல் உள்ள குறியீடு ஒரே மாதிரியான வடிவத்தைப் பின்பற்றுகின்றன: அழைப்புத் தரவின் முதல் 32 பிட்களை DUP1 செய்யவும், PUSH4 (((முறை கையொப்பம்)))-ஐ செய்யவும், சமத்துவத்தைச் சரிபார்க்க EQ-ஐ இயக்கவும், பின்னர் முறை கையொப்பம் பொருந்தினால் JUMPI` செய்யவும். இங்கே முறை கையொப்பங்கள், அவற்றின் முகவரிகள், மற்றும் தெரிந்தால் தொடர்புடைய முறை வரையறைopens in a new tab:
| முறை | முறை கையொப்பம் | குதிப்பதற்கான ஆஃப்செட் |
|---|---|---|
| splitter()opens in a new tab | 0x3cd8045e | 0x0103 |
| ??? | 0x81e580d3 | 0x0138 |
| currentWindow()opens in a new tab | 0xba0bafb4 | 0x0158 |
| ??? | 0x1f135823 | 0x00C4 |
| merkleRoot()opens in a new tab | 0x2eb4a7ab | 0x00ED |
பொருத்தம் காணப்படவில்லை என்றால், குறியீடு 0x7C இல் உள்ள ப்ராக்ஸி ஹேண்ட்லருக்கு தாவுகிறது, நாம் ப்ராக்ஸியாக இருக்கும் ஒப்பந்தத்தில் ஒரு பொருத்தம் இருக்கும் என்ற நம்பிக்கையில்.
splitter()
| ஆஃப்செட் | Opcode | அடுக்கு |
|---|---|---|
| 103 | JUMPDEST | |
| 104 | CALLVALUE | CALLVALUE |
| 105 | DUP1 | CALLVALUE CALLVALUE |
| 106 | ISZERO | CALLVALUE==0 CALLVALUE |
| 107 | PUSH2 0x010f | 0x010F CALLVALUE==0 CALLVALUE |
| 10A | JUMPI | CALLVALUE |
| 10B | PUSH1 0x00 | 0x00 CALLVALUE |
| 10D | DUP1 | 0x00 0x00 CALLVALUE |
| 10E | REVERT |
இந்தச் சார்பு செய்யும் முதல் விஷயம், அழைப்பு எந்த ETH-ஐயும் அனுப்பவில்லை என்பதைச் சரிபார்ப்பது. இந்தச் சார்பு payableopens in a new tab அல்ல. யாராவது நமக்கு ETH அனுப்பினால், அது ஒரு தவறாக இருக்க வேண்டும், மேலும் அவர்கள் அதைத் திரும்பப் பெற முடியாத இடத்தில் அந்த ETH இருப்பதைத் தவிர்க்க நாம் REVERT செய்ய விரும்புகிறோம்.
| ஆஃப்செட் | Opcode | அடுக்கு |
|---|---|---|
| 10F | JUMPDEST | |
| 110 | POP | |
| 111 | PUSH1 0x03 | 0x03 |
| 113 | SLOAD | (((Storage[3] a.k.a நாம் ஒரு ப்ராக்ஸியாக இருக்கும் ஒப்பந்தம்))) |
| 114 | PUSH1 0x40 | 0x40 (((Storage[3] a.k.a நாம் ஒரு ப்ராக்ஸியாக இருக்கும் ஒப்பந்தம்))) |
| 116 | MLOAD | 0x80 (((Storage[3] a.k.a நாம் ஒரு ப்ராக்ஸியாக இருக்கும் ஒப்பந்தம்))) |
| 117 | PUSH20 0xffffffffffffffffffffffffffffffffffffffff | 0xFF...FF 0x80 (((Storage[3] a.k.a நாம் ஒரு ப்ராக்ஸியாக இருக்கும் ஒப்பந்தம்))) |
| 12C | SWAP1 | 0x80 0xFF...FF (((Storage[3] a.k.a நாம் ஒரு ப்ராக்ஸியாக இருக்கும் ஒப்பந்தம்))) |
| 12D | SWAP2 | (((Storage[3] a.k.a நாம் ஒரு ப்ராக்ஸியாக இருக்கும் ஒப்பந்தம்))) 0xFF...FF 0x80 |
| 12E | AND | ProxyAddr 0x80 |
| 12F | DUP2 | 0x80 ProxyAddr 0x80 |
| 130 | MSTORE | 0x80 |
மற்றும் 0x80 இப்போது ப்ராக்ஸி முகவரியைக் கொண்டுள்ளது
| ஆஃப்செட் | Opcode | அடுக்கு |
|---|---|---|
| 131 | PUSH1 0x20 | 0x20 0x80 |
| 133 | ADD | 0xA0 |
| 134 | PUSH2 0x00e4 | 0xE4 0xA0 |
| 137 | JUMP | 0xA0 |
E4 குறியீடு
இந்த வரிகளை நாம் முதல் முறையாகப் பார்க்கிறோம், ஆனால் அவை பிற முறைகளுடன் பகிரப்பட்டுள்ளன (கீழே காண்க). எனவே நாம் ஸ்டேக்கில் உள்ள மதிப்பை X என்று அழைப்போம், மேலும் splitter() இல் இந்த X இன் மதிப்பு 0xA0 என்பதை நினைவில் கொள்வோம்.
| ஆஃப்செட் | Opcode | அடுக்கு |
|---|---|---|
| E4 | JUMPDEST | X |
| E5 | PUSH1 0x40 | 0x40 X |
| E7 | MLOAD | 0x80 X |
| E8 | DUP1 | 0x80 0x80 X |
| E9 | SWAP2 | X 0x80 0x80 |
| EA | SUB | X-0x80 0x80 |
| EB | SWAP1 | 0x80 X-0x80 |
| EC | RETURN |
எனவே இந்தக் குறியீடு ஸ்டேக்கில் (X) ஒரு நினைவக சுட்டிக்காட்டியியைப் பெறுகிறது, மேலும் ஒப்பந்தத்தை 0x80 - X என்ற பஃபருடன் RETURN செய்ய வைக்கிறது.
splitter() வழக்கில், இது நாம் ப்ராக்ஸியாக இருக்கும் முகவரியைத் தருகிறது. RETURN ஆனது 0x80-0x9F இல் உள்ள பஃப்பரைத் தருகிறது, இங்குதான் நாம் இந்தத் தரவை எழுதினோம் (மேலே ஆஃப்செட் 0x130).
currentWindow()
ஆஃப்செட்கள் 0x158-0x163 இல் உள்ள குறியீடு, நாம் splitter() இல் 0x103-0x10E இல் பார்த்தது போலவே உள்ளது (JUMPI இலக்கைத் தவிர), எனவே currentWindow() என்பதும் payable அல்ல என்பதை நாம் அறிவோம்.
| ஆஃப்செட் | Opcode | அடுக்கு |
|---|---|---|
| 164 | JUMPDEST | |
| 165 | POP | |
| 166 | PUSH2 0x00da | 0xDA |
| 169 | PUSH1 0x01 | 0x01 0xDA |
| 16B | SLOAD | Storage[1] 0xDA |
| 16C | DUP2 | 0xDA Storage[1] 0xDA |
| 16D | JUMP | Storage[1] 0xDA |
DA குறியீடு
இந்தக் குறியீடு பிற முறைகளுடனும் பகிரப்பட்டுள்ளது. எனவே நாம் ஸ்டேக்கில் உள்ள மதிப்பை Y என்று அழைப்போம், மேலும் currentWindow() இல் இந்த Y இன் மதிப்பு Storage[1] என்பதை நினைவில் கொள்வோம்.
| ஆஃப்செட் | Opcode | அடுக்கு |
|---|---|---|
| DA | JUMPDEST | Y 0xDA |
| DB | PUSH1 0x40 | 0x40 Y 0xDA |
| DD | MLOAD | 0x80 Y 0xDA |
| DE | SWAP1 | Y 0x80 0xDA |
| DF | DUP2 | 0x80 Y 0x80 0xDA |
| E0 | MSTORE | 0x80 0xDA |
Y-ஐ 0x80-0x9F இல் எழுதுங்கள்.
| ஆஃப்செட் | Opcode | அடுக்கு |
|---|---|---|
| E1 | PUSH1 0x20 | 0x20 0x80 0xDA |
| E3 | ADD | 0xA0 0xDA |
மீதமுள்ளவை ஏற்கனவே மேலே விளக்கப்பட்டுள்ளன. எனவே 0xDA-க்குச் செல்லும் தாவல்கள், ஸ்டேக்கின் மேல் மதிப்பை (Y) 0x80-0x9F-க்கு எழுதி, அந்த மதிப்பைத் திருப்பித் தருகின்றன. currentWindow() வழக்கில், அது Storage[1]-ஐத் தருகிறது.
merkleRoot()
ஆஃப்செட்கள் 0xED-0xF8-இல் உள்ள குறியீடு, நாம் splitter()-இல் 0x103-0x10E-இல் பார்த்தது போலவே உள்ளது (JUMPI இலக்கைத் தவிர), எனவே merkleRoot() என்பதும் payable அல்ல என்பதை நாம் அறிவோம்.
| ஆஃப்செட் | Opcode | அடுக்கு |
|---|---|---|
| F9 | JUMPDEST | |
| FA | POP | |
| FB | PUSH2 0x00da | 0xDA |
| FE | PUSH1 0x00 | 0x00 0xDA |
| 100 | SLOAD | Storage[0] 0xDA |
| 101 | DUP2 | 0xDA Storage[0] 0xDA |
| 102 | JUMP | Storage[0] 0xDA |
தாண்டலுக்குப் பிறகு என்ன நடக்கும் என்பதை நாம் ஏற்கனவே கண்டுபிடித்துவிட்டோம். எனவே merkleRoot() Storage[0]-ஐத் தருகிறது.
0x81e580d3
ஆஃப்செட்கள் 0x138-0x143-இல் உள்ள குறியீடு, நாம் splitter()-இல் 0x103-0x10E-இல் பார்த்தது போலவே உள்ளது (JUMPI இலக்கைத் தவிர), எனவே இந்த சார்பும் payable அல்ல என்பதை நாம் அறிவோம்.
| ஆஃப்செட் | Opcode | அடுக்கு |
|---|---|---|
| 144 | JUMPDEST | |
| 145 | POP | |
| 146 | PUSH2 0x00da | 0xDA |
| 149 | PUSH2 0x0153 | 0x0153 0xDA |
| 14C | CALLDATASIZE | CALLDATASIZE 0x0153 0xDA |
| 14D | PUSH1 0x04 | 0x04 CALLDATASIZE 0x0153 0xDA |
| 14F | PUSH2 0x018f | 0x018F 0x04 CALLDATASIZE 0x0153 0xDA |
| 152 | JUMP | 0x04 CALLDATASIZE 0x0153 0xDA |
| 18F | JUMPDEST | 0x04 CALLDATASIZE 0x0153 0xDA |
| 190 | PUSH1 0x00 | 0x00 0x04 CALLDATASIZE 0x0153 0xDA |
| 192 | PUSH1 0x20 | 0x20 0x00 0x04 CALLDATASIZE 0x0153 0xDA |
| 194 | DUP3 | 0x04 0x20 0x00 0x04 CALLDATASIZE 0x0153 0xDA |
| 195 | DUP5 | CALLDATASIZE 0x04 0x20 0x00 0x04 CALLDATASIZE 0x0153 0xDA |
| 196 | SUB | CALLDATASIZE-4 0x20 0x00 0x04 CALLDATASIZE 0x0153 0xDA |
| 197 | SLT | CALLDATASIZE-4<32 0x00 0x04 CALLDATASIZE 0x0153 0xDA |
| 198 | ISZERO | CALLDATASIZE-4>=32 0x00 0x04 CALLDATASIZE 0x0153 0xDA |
| 199 | PUSH2 0x01a0 | 0x01A0 CALLDATASIZE-4>=32 0x00 0x04 CALLDATASIZE 0x0153 0xDA |
| 19C | JUMPI | 0x00 0x04 CALLDATASIZE 0x0153 0xDA |
இந்தச் சார்பு குறைந்தபட்சம் 32 பைட்டுகள் (ஒரு சொல்) அழைப்புத் தரவை எடுப்பதாகத் தெரிகிறது.
| ஆஃப்செட் | Opcode | அடுக்கு |
|---|---|---|
| 19D | DUP1 | 0x00 0x00 0x04 CALLDATASIZE 0x0153 0xDA |
| 19E | DUP2 | 0x00 0x00 0x00 0x04 CALLDATASIZE 0x0153 0xDA |
| 19F | REVERT |
அது அழைப்புத் தரவைப் பெறவில்லை என்றால், பரிவர்த்தனை எந்தவொரு திரும்பும் தரவும் இல்லாமல் திரும்பப் பெறப்படுகிறது.
சார்பு தனக்குத் தேவையான அழைப்புத் தரவைப் பெற்றால் என்ன நடக்கும் என்று பார்ப்போம்.
| ஆஃப்செட் | Opcode | அடுக்கு |
|---|---|---|
| 1A0 | JUMPDEST | 0x00 0x04 CALLDATASIZE 0x0153 0xDA |
| 1A1 | POP | 0x04 CALLDATASIZE 0x0153 0xDA |
| 1A2 | CALLDATALOAD | calldataload(4) CALLDATASIZE 0x0153 0xDA |
calldataload(4) என்பது முறை கையொப்பத்திற்குப் பிறகு அழைப்புத் தரவின் முதல் சொல்
| ஆஃப்செட் | Opcode | அடுக்கு |
|---|---|---|
| 1A3 | SWAP2 | 0x0153 CALLDATASIZE calldataload(4) 0xDA |
| 1A4 | SWAP1 | CALLDATASIZE 0x0153 calldataload(4) 0xDA |
| 1A5 | POP | 0x0153 calldataload(4) 0xDA |
| 1A6 | JUMP | calldataload(4) 0xDA |
| 153 | JUMPDEST | calldataload(4) 0xDA |
| 154 | PUSH2 0x016e | 0x016E calldataload(4) 0xDA |
| 157 | JUMP | calldataload(4) 0xDA |
| 16E | JUMPDEST | calldataload(4) 0xDA |
| 16F | PUSH1 0x04 | 0x04 calldataload(4) 0xDA |
| 171 | DUP2 | calldataload(4) 0x04 calldataload(4) 0xDA |
| 172 | DUP2 | 0x04 calldataload(4) 0x04 calldataload(4) 0xDA |
| 173 | SLOAD | Storage[4] calldataload(4) 0x04 calldataload(4) 0xDA |
| 174 | DUP2 | calldataload(4) Storage[4] calldataload(4) 0x04 calldataload(4) 0xDA |
| 175 | LT | calldataload(4)<Storage[4] calldataload(4) 0x04 calldataload(4) 0xDA |
| 176 | PUSH2 0x017e | 0x017EC calldataload(4)<Storage[4] calldataload(4) 0x04 calldataload(4) 0xDA |
| 179 | JUMPI | calldataload(4) 0x04 calldataload(4) 0xDA |
முதல் சொல் Storage[4]-ஐ விடக் குறைவாக இல்லையென்றால், சார்பு தோல்வியடைகிறது. அது எந்தத் திரும்பிய மதிப்பும் இல்லாமல் திரும்பப் பெறுகிறது:
| ஆஃப்செட் | Opcode | அடுக்கு |
|---|---|---|
| 17A | PUSH1 0x00 | 0x00 ... |
| 17C | DUP1 | 0x00 0x00 ... |
| 17D | REVERT |
calldataload(4) Storage[4]-ஐ விடக் குறைவாக இருந்தால், இந்தக் குறியீட்டைப் பெறுகிறோம்:
| ஆஃப்செட் | Opcode | அடுக்கு |
|---|---|---|
| 17E | JUMPDEST | calldataload(4) 0x04 calldataload(4) 0xDA |
| 17F | PUSH1 0x00 | 0x00 calldataload(4) 0x04 calldataload(4) 0xDA |
| 181 | SWAP2 | 0x04 calldataload(4) 0x00 calldataload(4) 0xDA |
| 182 | DUP3 | 0x00 0x04 calldataload(4) 0x00 calldataload(4) 0xDA |
| 183 | MSTORE | calldataload(4) 0x00 calldataload(4) 0xDA |
மற்றும் நினைவக இருப்பிடங்கள் 0x00-0x1F இப்போது தரவு 0x04-ஐக் கொண்டுள்ளது (0x00-0x1E அனைத்தும் பூஜ்ஜியங்கள், 0x1F நான்கு ஆகும்)
| ஆஃப்செட் | Opcode | அடுக்கு |
|---|---|---|
| 184 | PUSH1 0x20 | 0x20 calldataload(4) 0x00 calldataload(4) 0xDA |
| 186 | SWAP1 | calldataload(4) 0x20 0x00 calldataload(4) 0xDA |
| 187 | SWAP2 | 0x00 0x20 calldataload(4) calldataload(4) 0xDA |
| 188 | SHA3 | (((0x00-0x1F இன் SHA3))) calldataload(4) calldataload(4) 0xDA |
| 189 | ADD | (((0x00-0x1F இன் SHA3)))+calldataload(4) calldataload(4) 0xDA |
| 18A | SLOAD | Storage[(((0x00-0x1F இன் SHA3))) + calldataload(4)] calldataload(4) 0xDA |
எனவே சேமிப்பகத்தில் ஒரு தேடல் அட்டவணை உள்ளது, இது 0x000...0004 இன் SHA3 இல் தொடங்குகிறது மற்றும் ஒவ்வொரு முறையான அழைப்பு தரவு மதிப்பிற்கும் ஒரு பதிவைக் கொண்டுள்ளது (Storage[4]-இன் கீழ் மதிப்பு).
| ஆஃப்செட் | Opcode | அடுக்கு |
|---|---|---|
| 18B | SWAP1 | calldataload(4) Storage[(((0x00-0x1F இன் SHA3))) + calldataload(4)] 0xDA |
| 18C | POP | Storage[(((0x00-0x1F இன் SHA3))) + calldataload(4)] 0xDA |
| 18D | DUP2 | 0xDA Storage[(((0x00-0x1F இன் SHA3))) + calldataload(4)] 0xDA |
| 18E | JUMP | Storage[(((0x00-0x1F இன் SHA3))) + calldataload(4)] 0xDA |
offset 0xDA-வில் உள்ள குறியீடு என்ன செய்கிறது என்பதை நாம் ஏற்கனவே அறிவோம், அது ஸ்டேக்கின் மேல் மதிப்பை அழைப்பாளருக்குத் திருப்பித் தருகிறது. எனவே இந்தச் சார்பு தேடல் அட்டவணையில் இருந்து அழைப்பாளருக்கு மதிப்பைத் திருப்பித் தருகிறது.
0x1f135823
ஆஃப்செட்கள் 0xC4-0xCF-இல் உள்ள குறியீடு, நாம் splitter()-இல் 0x103-0x10E-இல் பார்த்தது போலவே உள்ளது (JUMPI இலக்கைத் தவிர), எனவே இந்த சார்பும் payable அல்ல என்பதை நாம் அறிவோம்.
| ஆஃப்செட் | Opcode | அடுக்கு |
|---|---|---|
| D0 | JUMPDEST | |
| D1 | POP | |
| D2 | PUSH2 0x00da | 0xDA |
| D5 | PUSH1 0x06 | 0x06 0xDA |
| D7 | SLOAD | Value* 0xDA |
| D8 | DUP2 | 0xDA Value* 0xDA |
| D9 | JUMP | Value* 0xDA |
offset 0xDA-வில் உள்ள குறியீடு என்ன செய்கிறது என்பதை நாம் ஏற்கனவே அறிவோம், அது ஸ்டேக்கின் மேல் மதிப்பை அழைப்பாளருக்குத் திருப்பித் தருகிறது. எனவே இந்தச் சார்பு Value*-ஐத் தருகிறது.
முறை சுருக்கம்
இந்த ஒப்பந்தத்தை இப்போது நீங்கள் புரிந்துகொண்டதாக உணர்கிறீர்களா? நான் அப்படி உணரவில்லை. இதுவரை நாம் இந்த முறைகளைக் கொண்டுள்ளோம்:
| முறை | பொருள் |
|---|---|
| மாற்றம் | அழைப்பால் வழங்கப்படும் மதிப்பை ஏற்றுக்கொண்டு, அந்தத் தொகையால் Value* ஐ அதிகரிக்கவும் |
| splitter() | Storage[3], ப்ராக்ஸி முகவரியைத் திருப்பித் தரவும் |
| currentWindow() | Storage[1] ஐ திருப்பியனுப்புக |
| merkleRoot() | Storage[0] ஐ திருப்பியனுப்புக |
| 0x81e580d3 | அளவுரு Storage[4]-ஐ விடக் குறைவாக இருந்தால், ஒரு தேடல் அட்டவணையில் இருந்து மதிப்பைத் திருப்பித் தரவும் |
| 0x1f135823 | Storage[6]-ஐத் திருப்பித் தரவும், அதாவது Value* |
ஆனால் மற்ற எந்த செயல்பாடும் Storage[3]-இல் உள்ள ஒப்பந்தத்தால் வழங்கப்படுகிறது என்பதை நாம் அறிவோம். ஒருவேளை அந்த ஒப்பந்தம் என்னவென்று நமக்குத் தெரிந்தால் அது நமக்கு ஒரு துப்பு கொடுக்கலாம். அதிர்ஷ்டவசமாக, இது பிளாக்செயின் மற்றும் அனைத்தும் அறியப்பட்டவை, குறைந்தபட்சம் கோட்பாட்டளவில். Storage[3]-ஐ அமைக்கும் எந்த முறைகளையும் நாம் பார்க்கவில்லை, எனவே அது கன்ஸ்ட்ரக்டரால் அமைக்கப்பட்டிருக்க வேண்டும்.
கன்ஸ்ட்ரக்டர்
நாம் ஒரு ஒப்பந்தத்தைப் பார்க்கும்போதுopens in a new tab, அதை உருவாக்கிய பரிவர்த்தனையையும் பார்க்கலாம்.
அந்தப் பரிவர்த்தனையைக் கிளிக் செய்து, பின்னர் State தாவலைக் கிளிக் செய்தால், அளவுருக்களின் ஆரம்ப மதிப்புகளைப் பார்க்கலாம். குறிப்பாக, Storage[3] 0x2f81e57ff4f4d83b40a9f719fd892d8e806e0761opens in a new tab-ஐக் கொண்டுள்ளது என்பதைக் காணலாம். அந்த ஒப்பந்தம் விடுபட்ட செயல்பாட்டைக் கொண்டிருக்க வேண்டும். நாம் விசாரிக்கும் ஒப்பந்தத்திற்கு நாம் பயன்படுத்திய அதே கருவிகளைப் பயன்படுத்தி அதை நாம் புரிந்துகொள்ளலாம்.
ப்ராக்ஸி ஒப்பந்தம்
மேலே உள்ள அசல் ஒப்பந்தத்திற்கு நாம் பயன்படுத்திய அதே நுட்பங்களைப் பயன்படுத்தி, ஒப்பந்தம் பின்வரும் சந்தர்ப்பங்களில் திரும்பப் பெறுகிறது என்பதைக் காணலாம்:
- அழைப்பில் ஏதேனும் ETH இணைக்கப்பட்டிருந்தால் (0x05-0x0F)
- அழைப்புத் தரவு அளவு நான்கிற்கும் குறைவாக இருந்தால் (0x10-0x19 மற்றும் 0xBE-0xC2)
அது ஆதரிக்கும் முறைகள்:
| முறை | முறை கையொப்பம் | குதிப்பதற்கான ஆஃப்செட் |
|---|---|---|
| scaleAmountByPercentage(uint256,uint256)opens in a new tab | 0x8ffb5c97 | 0x0135 |
| isClaimed(uint256,address)opens in a new tab | 0xd2ef0795 | 0x0151 |
| claim(uint256,address,uint256,bytes32[])opens in a new tab | 0x2e7ba6ef | 0x00F4 |
| incrementWindow()opens in a new tab | 0x338b1d31 | 0x0110 |
| ??? | 0x3f26479e | 0x0118 |
| ??? | 0x1e7df9d3 | 0x00C3 |
| currentWindow()opens in a new tab | 0xba0bafb4 | 0x0148 |
| merkleRoot()opens in a new tab | 0x2eb4a7ab | 0x0107 |
| ??? | 0x81e580d3 | 0x0122 |
| ??? | 0x1f135823 | 0x00D8 |
கீழே உள்ள நான்கு முறைகளை நாம் புறக்கணிக்கலாம், ஏனெனில் நாம் அவற்றை ஒருபோதும் அடைய மாட்டோம். அவற்றின் கையொப்பங்கள் நமது அசல் ஒப்பந்தம் அவற்றைத் தானாகவே கவனித்துக் கொள்ளும் வகையில் உள்ளன (விவரங்களை மேலே காண கையொப்பங்களைக் கிளிக் செய்யலாம்), எனவே அவை மீறப்பட்ட முறைகளாக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 >=′ 643 if _param1 and _param2 > -1 / _param1:4 revert with 0, 175 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 = 03 s = 04 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 continue11 ...12 s = sha3(mem[_66 + 32 len mem[_66]])13 continue14 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 wei3 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 பார்த்தால், இந்த ஒப்பந்தம் 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2opens in a new tab, ஒரு Wrapped Ether ஒப்பந்தம் அதன் மூலக் குறியீடு Etherscan-க்கு பதிவேற்றப்பட்டுள்ளதுopens in a new tab என்பதைக் காணலாம்.
எனவே ஒப்பந்தங்கள் _param2-க்கு ETH அனுப்ப முயற்சிப்பதாக தெரிகிறது. அதைச் செய்ய முடிந்தால், நல்லது. இல்லையென்றால், அது WETHopens 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 = 04 s = 05 while idx < _param3.length:6 if idx >= mem[96]:7 revert with 0, 508 _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 continue13 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 wei20 gas 30000 wei21 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 wei26 gas gas_remaining wei27 ...28 log 0xdbd5389f: addr(_param1), s, bool(ext_call.success)அனைத்தையும் காட்டுமுக்கிய வேறுபாடு என்னவென்றால், முதல் அளவுரு, திரும்பப் பெறுவதற்கான சாளரம், அங்கு இல்லை. அதற்குப் பதிலாக, கோரக்கூடிய அனைத்து சாளரங்களுக்கும் ஒரு சுழற்சி உள்ளது.
1 idx = 02 s = 03 while idx < currentWindow:4 ...5 if stor5[mem[0]]:6 if idx == -1:7 revert with 0, 178 idx = idx + 19 s = s10 continue11 ...12 stor5[idx][addr(_param1)] = 113 if idx >= unknown81e580d3.length:14 revert with 0, 5015 mem[0] = 416 if unknown81e580d3[idx] and _param2 > -1 / unknown81e580d3[idx]:17 revert with 0, 1718 if s > !(unknown81e580d3[idx] * _param2 / 100 * 10^6):19 revert with 0, 1720 if idx == -1:21 revert with 0, 1722 idx = idx + 123 s = s + (unknown81e580d3[idx] * _param2 / 100 * 10^6)24 continueஅனைத்தையும் காட்டுஎனவே இது அனைத்து சாளரங்களையும் கோரும் ஒரு claim மாறுபாடு போல் தெரிகிறது.
முடிவுரை
இப்போது மூலக் குறியீடு கிடைக்காத ஒப்பந்தங்களை, ஆப்கோட்கள் அல்லது (அது வேலை செய்யும் போது) டீகம்பைலரைப் பயன்படுத்தி எப்படிப் புரிந்துகொள்வது என்று நீங்கள் அறிந்திருக்க வேண்டும். இந்தக் கட்டுரையின் நீளத்திலிருந்து தெளிவாகத் தெரிவது போல, ஒரு ஒப்பந்தத்தை ரிவர்ஸ் இன்ஜினியரிங் செய்வது அற்பமானதல்ல, ஆனால் பாதுகாப்பு அவசியமான ஒரு அமைப்பில், ஒப்பந்தங்கள் வாக்குறுதியளித்தபடி செயல்படுகின்றன என்பதை சரிபார்க்கும் திறமை ஒரு முக்கியமான திறமையாகும்.
எனது மேலும் பணிகளை இங்கே பார்க்கவும்opens in a new tab.
பக்கத்தின் கடைசி புதுப்பிப்பு: 14 பிப்ரவரி, 2026



![Storage[6] இல் மாற்றம்](/_next/image/?url=%2Fcontent%2Fdevelopers%2Ftutorials%2Freverse-engineering-a-contract%2Fstorage6.png&w=1920&q=75)



