ఒక కాంట్రాక్ట్ను రివర్స్ ఇంజనీరింగ్ చేయడం
పరిచయం
బ్లాక్ చైనులో రహస్యాలు ఏవీ ఉండవు, జరిగే ప్రతిదీ స్థిరంగా, ధృవీకరించదగినదిగా మరియు బహిరంగంగా అందుబాటులో ఉంటుంది. ఆదర్శవంతంగా, కాంట్రాక్టులు వాటి సోర్స్ కోడ్ను ప్రచురించి Etherscanలో ధృవీకరించాలిopens in a new tab. అయితే, అది ఎప్పుడూ అలా ఉండదుopens in a new tab. ఈ వ్యాసంలో మీరు సోర్స్ కోడ్ లేని కాంట్రాక్ట్ 0x2510c039cc3b061d79e564b38836da87e31b342fopens in a new tab ను చూడటం ద్వారా కాంట్రాక్ట్లను ఎలా రివర్స్ ఇంజనీరింగ్ చేయాలో నేర్చుకుంటారు.
రివర్స్ కంపైలర్లు ఉన్నాయి, కానీ అవి ఎల్లప్పుడూ ఉపయోగపడే ఫలితాలనుopens in a new tab ఇవ్వవు. ఈ వ్యాసంలో మీరు ఆప్కోడ్లopens in a new tab నుండి ఒక కాంట్రాక్ట్ను మాన్యువల్గా ఎలా రివర్స్ ఇంజనీరింగ్ చేసి అర్థం చేసుకోవాలో, అలాగే డీకంపైలర్ ఫలితాలను ఎలా అర్థం చేసుకోవాలో నేర్చుకుంటారు.
ఈ వ్యాసాన్ని అర్థం చేసుకోవడానికి, మీరు ఇప్పటికే EVM యొక్క ప్రాథమికాలను తెలుసుకోవాలి, మరియు కనీసం EVM అసెంబ్లర్తో కొంతవరకు పరిచయం కలిగి ఉండాలి. మీరు ఈ అంశాల గురించి ఇక్కడ చదవవచ్చుopens in a new tab.
ఎగ్జిక్యూటబుల్ కోడ్ను సిద్ధం చేయండి
మీరు కాంట్రాక్ట్ కోసం Etherscanకు వెళ్లి, కాంట్రాక్ట్ ట్యాబ్పై క్లిక్ చేసి, ఆపై ఆప్కోడ్స్ వీక్షణకు మారండి పై క్లిక్ చేయడం ద్వారా ఆప్కోడ్లను పొందవచ్చు. మీరు ఒక లైన్కు ఒక ఆప్కోడ్ ఉన్న వీక్షణను పొందుతారు.
జంప్లను అర్థం చేసుకోవడానికి, అయితే, ప్రతి ఆప్కోడ్ కోడ్లో ఎక్కడ ఉందో మీరు తెలుసుకోవాలి. అది చేయడానికి, ఒక మార్గం ఏమిటంటే 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)
కాంట్రాక్టులు ఎల్లప్పుడూ మొదటి బైట్ నుండి అమలు చేయబడతాయి. ఇది కోడ్ యొక్క ప్రారంభ భాగం:
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ (ఆప్కోడ్ తర్వాత) |
|---|---|---|
| 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 |
| C | JUMPI | ఖాళీ |
ఈ కోడ్ రెండు పనులు చేస్తుంది:
- 0x80 ను 32 బైట్ల విలువగా మెమరీ స్థానాలు 0x40-0x5F కు వ్రాయండి (0x80 0x5F లో నిల్వ చేయబడింది, మరియు 0x40-0x5E అన్నీ సున్నాలు).
- కాల్డేటా పరిమాణాన్ని చదవండి. సాధారణంగా ఒక ఎథేరియం కాంట్రాక్ట్ కోసం కాల్ డేటా ABI (అప్లికేషన్ బైనరీ ఇంటర్ఫేస్)opens in a new tabను అనుసరిస్తుంది, దీనికి ఫంక్షన్ సెలెక్టర్ కోసం కనీసం నాలుగు బైట్లు అవసరం. కాల్ డేటా పరిమాణం నాలుగు కన్నా తక్కువ ఉంటే, 0x5E కు జంప్ చేయండి.
0x5E వద్ద హ్యాండ్లర్ (నాన్-ABI కాల్ డేటా కోసం)
| ఆఫ్సెట్ | ఆప్కోడ్ |
|---|---|
| 5E | JUMPDEST |
| 5F | CALLDATASIZE |
| 60 | PUSH2 0x007c |
| 63 | JUMPI |
ఈ స్నిప్పెట్ JUMPDESTతో మొదలవుతుంది. JUMPDEST కాని ఆప్కోడ్కు మీరు జంప్ చేస్తే EVM (ఎథేరియం వర్చువల్ మషీన్) ప్రోగ్రామ్లు ఒక ఎక్సెప్షన్ను త్రో చేస్తాయి. ఆ తర్వాత అది CALLDATASIZEను చూస్తుంది, మరియు అది "నిజం" (అంటే, సున్నా కాదు) అయితే 0x7Cకు జంప్ చేస్తుంది. మేము దాని గురించి క్రింద చర్చిస్తాము.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ (ఆప్కోడ్ తర్వాత) |
|---|---|---|
| 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 | స్టోరేజ్[6] CALLVALUE 0 6 CALLVALUE |
కాబట్టి కాల్ డేటా లేనప్పుడు మేము స్టోరేజ్[6] విలువను చదువుతాము. ఈ విలువ ఏమిటో మాకు ఇంకా తెలియదు, కానీ కాల్ డేటా లేకుండా కాంట్రాక్ట్ అందుకున్న లావాదేవీల కోసం మేము చూడవచ్చు. కేవలం ఎలాంటి కాల్ డేటా లేకుండా (అందువల్ల పద్ధతి లేకుండా) ETHని బదిలీ చేసే లావాదేవీలు Etherscanలో Transfer పద్ధతిని కలిగి ఉంటాయి. నిజానికి, కాంట్రాక్ట్ అందుకున్న మొదటి లావాదేవీopens in a new tab ఒక బదిలీ.
మేము ఆ లావాదేవీని చూసి మరింత చూడటానికి క్లిక్ చేయండి పై క్లిక్ చేస్తే, ఇన్పుట్ డేటా అని పిలువబడే కాల్ డేటా నిజానికి ఖాళీగా (0x) ఉందని చూస్తాము. విలువ 1.559 ETH అని కూడా గమనించండి, అది తర్వాత సంబంధితంగా ఉంటుంది.
తరువాత, స్టేట్ ట్యాబ్పై క్లిక్ చేసి, మనం రివర్స్ ఇంజనీరింగ్ చేస్తున్న కాంట్రాక్ట్ను (0x2510...) విస్తరించండి. లావాదేవీ సమయంలో స్టోరేజ్[6] మారిందని మీరు చూడవచ్చు, మరియు మీరు హెక్స్ను సంఖ్యకు మార్చినట్లయితే, అది 1,559,000,000,000,000,000గా మారిందని మీరు చూస్తారు, వీలో బదిలీ చేయబడిన విలువ (స్పష్టత కోసం నేను కామాలను జోడించాను), ఇది తదుపరి కాంట్రాక్ట్ విలువకు అనుగుణంగా ఉంటుంది.
అదే కాలం నుండి ఇతర Transfer లావాదేవీలopens in a new tab వల్ల కలిగే స్టేట్ మార్పులలో మనం చూస్తే, స్టోరేజ్[6] కొంతకాలం కాంట్రాక్ట్ విలువను ట్రాక్ చేసిందని చూస్తాము. ఇప్పటికి దాన్ని Value* అని పిలుద్దాం. నక్షత్రం (*) మాకు గుర్తు చేస్తుంది, ఈ వేరియబుల్ ఇంకా ఏమి చేస్తుందో మాకు తెలియదు, కానీ ఇది కేవలం కాంట్రాక్ట్ విలువను ట్రాక్ చేయడానికి మాత్రమే ఉండదు, ఎందుకంటే ADDRESS BALANCE ఉపయోగించి మీరు మీ ఖాతాల బ్యాలెన్స్ను పొందగలిగినప్పుడు, చాలా ఖరీదైన స్టోరేజ్ను ఉపయోగించాల్సిన అవసరం లేదు. మొదటి ఆప్కోడ్ కాంట్రాక్ట్ యొక్క సొంత చిరునామాను పుష్ చేస్తుంది. రెండవది స్టాక్ పైభాగంలో ఉన్న చిరునామాను చదివి, దానిని ఆ చిరునామా బ్యాలెన్స్తో భర్తీ చేస్తుంది.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 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 |
మేము జంప్ గమ్యస్థానంలో ఈ కోడ్ను ట్రేస్ చేయడం కొనసాగిస్తాము.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 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 బిట్వైస్, కాబట్టి ఇది కాల్ విలువలోని ప్రతి బిట్ విలువను రివర్స్ చేస్తుంది.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 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 వీ, అంటే సుమారు 10^59 ETHతో పోల్చదగినదిగా ఉండాలి. రాసే సమయానికి మొత్తం ETH సరఫరా రెండు వందల మిలియన్ల కన్నా తక్కువopens in a new tab.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 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 ఆఫ్సెట్కు జంప్ చేయండి.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 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*కు కాల్ విలువను జోడిస్తాము. ఇది ట్రాన్స్ఫర్ లావాదేవీలు ఏమి చేస్తాయో దానితో స్థిరంగా ఉంటుంది.
| ఆఫ్సెట్ | ఆప్కోడ్ |
|---|---|
| 79 | POP |
| 7A | POP |
| 7B | STOP |
చివరగా, స్టాక్ను క్లియర్ చేసి (అది అవసరం లేదు) మరియు లావాదేవీ విజయవంతంగా ముగిసినట్లు సిగ్నల్ చేయండి.
అన్నింటినీ కలిపి, ప్రారంభ కోడ్ కోసం ఇక్కడ ఒక ఫ్లోచార్ట్ ఉంది.
0x7C వద్ద హ్యాండ్లర్
ఈ హ్యాండ్లర్ ఏమి చేస్తుందో నేను ఉద్దేశపూర్వకంగా హెడ్డింగ్లో పెట్టలేదు. అంశం ఈ ప్రత్యేక కాంట్రాక్ట్ ఎలా పనిచేస్తుందో మీకు నేర్పించడం కాదు, కానీ కాంట్రాక్టులను ఎలా రివర్స్ ఇంజనీరింగ్ చేయాలో నేర్పించడం. కోడ్ను అనుసరించడం ద్వారా నేను చేసినట్లే ఇది ఏమి చేస్తుందో మీరు నేర్చుకుంటారు.
మేము ఇక్కడికి అనేక ప్రదేశాల నుండి వస్తాము:
- 1, 2, లేదా 3 బైట్ల కాల్ డేటా ఉంటే (0x63 ఆఫ్సెట్ నుండి)
- పద్ధతి సంతకం తెలియకపోతే (0x42 మరియు 0x5D ఆఫ్సెట్ల నుండి)
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 7C | JUMPDEST | |
| 7D | PUSH1 0x00 | 0x00 |
| 7F | PUSH2 0x009d | 0x9D 0x00 |
| 82 | PUSH1 0x03 | 0x03 0x9D 0x00 |
| 84 | SLOAD | స్టోరేజ్[3] 0x9D 0x00 |
ఇది మరో స్టోరేజ్ సెల్, ఏ లావాదేవీలలోనూ నేను కనుగొనలేకపోయాను, కాబట్టి దాని అర్థం ఏమిటో తెలుసుకోవడం కష్టం. క్రింది కోడ్ దానిని స్పష్టత చేస్తుంది.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 85 | PUSH20 0xffffffffffffffffffffffffffffffffffffffff | 0xff....ff స్టోరేజ్[3] 0x9D 0x00 |
| 9A | AND | స్టోరేజ్[3]-గా-చిరునామా 0x9D 0x00 |
ఈ ఆప్కోడ్లు మేము స్టోరేజ్[3] నుండి చదివిన విలువను 160 బిట్లకు కట్ చేస్తాయి, ఇది ఒక ఎథేరియం చిరునామా పొడవు.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 9B | SWAP1 | 0x9D స్టోరేజ్[3]-గా-చిరునామా 0x00 |
| 9C | JUMP | స్టోరేజ్[3]-గా-చిరునామా 0x00 |
ఈ జంప్ అదనపుది, ఎందుకంటే మేము తదుపరి ఆప్కోడ్కు వెళ్తున్నాము. ఈ కోడ్ గ్యాస్-సామర్థ్యం ఉన్నంతగా లేదు.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 9D | JUMPDEST | స్టోరేజ్[3]-గా-చిరునామా 0x00 |
| 9E | SWAP1 | 0x00 స్టోరేజ్[3]-గా-చిరునామా |
| 9F | POP | స్టోరేజ్[3]-గా-చిరునామా |
| A0 | PUSH1 0x40 | 0x40 స్టోరేజ్[3]-గా-చిరునామా |
| A2 | MLOAD | Mem[0x40] స్టోరేజ్[3]-గా-చిరునామా |
కోడ్ ప్రారంభంలోనే మేము Mem[0x40]ను 0x80కి సెట్ చేసాము. తర్వాత మనం 0x40 కోసం చూస్తే, మనం దానిని మార్చలేదని చూస్తాము - కాబట్టి మనం దానిని 0x80 అని ఊహించవచ్చు.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| A3 | CALLDATASIZE | CALLDATASIZE 0x80 స్టోరేజ్[3]-గా-చిరునామా |
| A4 | PUSH1 0x00 | 0x00 CALLDATASIZE 0x80 స్టోరేజ్[3]-గా-చిరునామా |
| A6 | DUP3 | 0x80 0x00 CALLDATASIZE 0x80 స్టోరేజ్[3]-గా-చిరునామా |
| A7 | CALLDATACOPY | 0x80 స్టోరేజ్[3]-గా-చిరునామా |
0x80 నుండి ప్రారంభించి, అన్ని కాల్ డేటాను మెమరీకి కాపీ చేయండి.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| A8 | PUSH1 0x00 | 0x00 0x80 స్టోరేజ్[3]-గా-చిరునామా |
| AA | DUP1 | 0x00 0x00 0x80 స్టోరేజ్[3]-గా-చిరునామా |
| AB | CALLDATASIZE | CALLDATASIZE 0x00 0x00 0x80 స్టోరేజ్[3]-గా-చిరునామా |
| AC | DUP4 | 0x80 CALLDATASIZE 0x00 0x00 0x80 స్టోరేజ్[3]-గా-చిరునామా |
| AD | DUP6 | స్టోరేజ్[3]-గా-చిరునామా 0x80 CALLDATASIZE 0x00 0x00 0x80 స్టోరేజ్[3]-గా-చిరునామా |
| AE | GAS | GAS స్టోరేజ్[3]-గా-చిరునామా 0x80 CALLDATASIZE 0x00 0x00 0x80 స్టోరేజ్[3]-గా-చిరునామా |
| AF | DELEGATE_CALL |
ఇప్పుడు విషయాలు చాలా స్పష్టంగా ఉన్నాయి. ఈ కాంట్రాక్ట్ ఒక ప్రాక్సీopens in a new tabగా పనిచేయగలదు, నిజమైన పని చేయడానికి స్టోరేజ్[3]లోని చిరునామాను పిలుస్తుంది. DELEGATE_CALL వేరే కాంట్రాక్ట్ను పిలుస్తుంది, కానీ అదే స్టోరేజ్లో ఉంటుంది. అంటే, ప్రతినిధి కాంట్రాక్ట్, మనం ప్రాక్సీగా ఉన్నది, అదే స్టోరేజ్ స్థలాన్ని యాక్సెస్ చేస్తుంది. కాల్ కోసం పారామీటర్లు:
- గ్యాస్: మిగిలిన అన్ని గ్యాస్
- పిలువబడిన చిరునామా: స్టోరేజ్[3]-గా-చిరునామా
- కాల్ డేటా: 0x80 వద్ద ప్రారంభమయ్యే CALLDATASIZE బైట్లు, ఇది మనం అసలు కాల్ డేటాను ఉంచిన చోట.
- తిరిగి వచ్చే డేటా: ఏదీ లేదు (0x00 - 0x00) మనం ఇతర మార్గాల ద్వారా తిరిగి వచ్చే డేటాను పొందుతాము (క్రింద చూడండి)
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| B0 | RETURNDATASIZE | RETURNDATASIZE (((కాల్ సక్సెస్/ఫెయిల్యూర్))) 0x80 స్టోరేజ్[3]-గా-చిరునామా |
| B1 | DUP1 | RETURNDATASIZE RETURNDATASIZE (((కాల్ సక్సెస్/ఫెయిల్యూర్))) 0x80 స్టోరేజ్[3]-గా-చిరునామా |
| B2 | PUSH1 0x00 | 0x00 RETURNDATASIZE RETURNDATASIZE (((కాల్ సక్సెస్/ఫెయిల్యూర్))) 0x80 స్టోరేజ్[3]-గా-చిరునామా |
| B4 | DUP5 | 0x80 0x00 RETURNDATASIZE RETURNDATASIZE (((కాల్ సక్సెస్/ఫెయిల్యూర్))) 0x80 స్టోరేజ్[3]-గా-చిరునామా |
| B5 | RETURNDATACOPY | RETURNDATASIZE (((కాల్ సక్సెస్/ఫెయిల్యూర్))) 0x80 స్టోరేజ్[3]-గా-చిరునామా |
ఇక్కడ మేము అన్ని రిటర్న్ డేటాను 0x80 నుండి ప్రారంభమయ్యే మెమరీ బఫర్కు కాపీ చేస్తాము.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| B6 | DUP2 | (((కాల్ సక్సెస్/ఫెయిల్యూర్))) RETURNDATASIZE (((కాల్ సక్సెస్/ఫెయిల్యూర్))) 0x80 స్టోరేజ్[3]-గా-చిరునామా |
| B7 | DUP1 | (((కాల్ సక్సెస్/ఫెయిల్యూర్))) (((కాల్ సక్సెస్/ఫెయిల్యూర్))) RETURNDATASIZE (((కాల్ సక్సెస్/ఫెయిల్యూర్))) 0x80 స్టోరేజ్[3]-గా-చిరునామా |
| B8 | ISZERO | (((కాల్ విఫలమైందా))) (((కాల్ సక్సెస్/ఫెయిల్యూర్))) RETURNDATASIZE (((కాల్ సక్సెస్/ఫెయిల్యూర్))) 0x80 స్టోరేజ్[3]-గా-చిరునామా |
| B9 | PUSH2 0x00c0 | 0xC0 (((కాల్ విఫలమైందా))) (((కాల్ సక్సెస్/ఫెయిల్యూర్))) RETURNDATASIZE (((కాల్ సక్సెస్/ఫెయిల్యూర్))) 0x80 స్టోరేజ్[3]-గా-చిరునామా |
| BC | JUMPI | (((కాల్ సక్సెస్/ఫెయిల్యూర్))) RETURNDATASIZE (((కాల్ సక్సెస్/ఫెయిల్యూర్))) 0x80 స్టోరేజ్[3]-గా-చిరునామా |
| BD | DUP2 | RETURNDATASIZE (((కాల్ సక్సెస్/ఫెయిల్యూర్))) RETURNDATASIZE (((కాల్ సక్సెస్/ఫెయిల్యూర్))) 0x80 స్టోరేజ్[3]-గా-చిరునామా |
| BE | DUP5 | 0x80 RETURNDATASIZE (((కాల్ సక్సెస్/ఫెయిల్యూర్))) RETURNDATASIZE (((కాల్ సక్సెస్/ఫెయిల్యూర్))) 0x80 స్టోరేజ్[3]-గా-చిరునామా |
| BF | RETURN |
కాబట్టి కాల్ తర్వాత మేము రిటర్న్ డేటాను బఫర్ 0x80 - 0x80+RETURNDATASIZEకు కాపీ చేస్తాము, మరియు కాల్ విజయవంతమైతే మేము అదే బఫర్తో RETURN చేస్తాము.
DELEGATECALL విఫలమైంది
మేము ఇక్కడకు వస్తే, 0xC0కి, దాని అర్థం మనం పిలిచిన కాంట్రాక్ట్ రివర్ట్ అయ్యిందని. మనం ఆ కాంట్రాక్ట్కు కేవలం ఒక ప్రాక్సీ మాత్రమే కాబట్టి, మనం అదే డేటాను తిరిగి ఇవ్వాలనుకుంటున్నాము మరియు రివర్ట్ చేయాలనుకుంటున్నాము.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| C0 | JUMPDEST | (((కాల్ సక్సెస్/ఫెయిల్యూర్))) RETURNDATASIZE (((కాల్ సక్సెస్/ఫెయిల్యూర్))) 0x80 స్టోరేజ్[3]-గా-చిరునామా |
| C1 | DUP2 | RETURNDATASIZE (((కాల్ సక్సెస్/ఫెయిల్యూర్))) RETURNDATASIZE (((కాల్ సక్సెస్/ఫెయిల్యూర్))) 0x80 స్టోరేజ్[3]-గా-చిరునామా |
| C2 | DUP5 | 0x80 RETURNDATASIZE (((కాల్ సక్సెస్/ఫెయిల్యూర్))) RETURNDATASIZE (((కాల్ సక్సెస్/ఫెయిల్యూర్))) 0x80 స్టోరేజ్[3]-గా-చిరునామా |
| C3 | REVERT |
కాబట్టి మనం RETURN కోసం ముందు ఉపయోగించిన అదే బఫర్తో REVERT చేస్తాము: 0x80 - 0x80+RETURNDATASIZE
ఎబిఐ కాల్స్
కాల్ డేటా పరిమాణం నాలుగు బైట్లు లేదా అంతకంటే ఎక్కువ ఉంటే ఇది ఒక చెల్లుబాటు అయ్యే ఎబిఐ కాల్ కావచ్చు.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| D | PUSH1 0x00 | 0x00 |
| F | CALLDATALOAD | (((కాల్ డేటా యొక్క మొదటి పదం (256 బిట్లు)))) |
| 10 | PUSH1 0xe0 | 0xE0 (((కాల్ డేటా యొక్క మొదటి పదం (256 బిట్లు)))) |
| 12 | SHR | (((కాల్ డేటా యొక్క మొదటి 32 బిట్లు (4 బైట్లు)))) |
Etherscan మనకు 1C ఒక తెలియని ఆప్కోడ్ అని చెబుతుంది, ఎందుకంటే Etherscan ఈ ఫీచర్ను రాసిన తర్వాత ఇది జోడించబడిందిopens in a new tab మరియు వారు దానిని నవీకరించలేదు. నవీకరించబడిన ఆప్కోడ్ పట్టికopens in a new tab మనకు ఇది షిఫ్ట్ రైట్ అని చూపిస్తుంది
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 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()
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 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 చేయాలనుకుంటున్నాము.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 10F | JUMPDEST | |
| 110 | POP | |
| 111 | PUSH1 0x03 | 0x03 |
| 113 | SLOAD | (((స్టోరేజ్[3] అకా మనం ప్రాక్సీగా ఉన్న కాంట్రాక్ట్))) |
| 114 | PUSH1 0x40 | 0x40 (((స్టోరేజ్[3] అకా మనం ప్రాక్సీగా ఉన్న కాంట్రాక్ట్))) |
| 116 | MLOAD | 0x80 (((స్టోరేజ్[3] అకా మనం ప్రాక్సీగా ఉన్న కాంట్రాక్ట్))) |
| 117 | PUSH20 0xffffffffffffffffffffffffffffffffffffffff | 0xFF...FF 0x80 (((స్టోరేజ్[3] అకా మనం ప్రాక్సీగా ఉన్న కాంట్రాక్ట్))) |
| 12C | SWAP1 | 0x80 0xFF...FF (((స్టోరేజ్[3] అకా మనం ప్రాక్సీగా ఉన్న కాంట్రాక్ట్))) |
| 12D | SWAP2 | (((స్టోరేజ్[3] అకా మనం ప్రాక్సీగా ఉన్న కాంట్రాక్ట్))) 0xFF...FF 0x80 |
| 12E | AND | ProxyAddr 0x80 |
| 12F | DUP2 | 0x80 ProxyAddr 0x80 |
| 130 | MSTORE | 0x80 |
మరియు 0x80 ఇప్పుడు ప్రాక్సీ చిరునామాను కలిగి ఉంది
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 131 | PUSH1 0x20 | 0x20 0x80 |
| 133 | ADD | 0xA0 |
| 134 | PUSH2 0x00e4 | 0xE4 0xA0 |
| 137 | JUMP | 0xA0 |
E4 కోడ్
ఇవి మేము మొదటిసారి చూస్తున్న లైన్లు, కానీ అవి ఇతర పద్ధతులతో పంచుకోబడ్డాయి (క్రింద చూడండి). కాబట్టి మనం స్టాక్లోని విలువను X అని పిలుస్తాము, మరియు splitter()లో ఈ X విలువ 0xA0 అని గుర్తుంచుకోండి.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 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 కాదని మనకు తెలుసు.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 164 | JUMPDEST | |
| 165 | POP | |
| 166 | PUSH2 0x00da | 0xDA |
| 169 | PUSH1 0x01 | 0x01 0xDA |
| 16B | SLOAD | స్టోరేజ్[1] 0xDA |
| 16C | DUP2 | 0xDA స్టోరేజ్[1] 0xDA |
| 16D | JUMP | స్టోరేజ్[1] 0xDA |
DA కోడ్
ఈ కోడ్ ఇతర పద్ధతులతో కూడా పంచుకోబడింది. కాబట్టి మనం స్టాక్లోని విలువను Y అని పిలుస్తాము, మరియు currentWindow()లో ఈ Y విలువ స్టోరేజ్[1] అని గుర్తుంచుకోండి.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 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కు వ్రాయండి.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| E1 | PUSH1 0x20 | 0x20 0x80 0xDA |
| E3 | ADD | 0xA0 0xDA |
మరియు మిగిలినవి ఇప్పటికే పైన వివరించబడ్డాయి. కాబట్టి 0xDAకు జంప్లు స్టాక్ టాప్ (Y)ని 0x80-0x9Fకు వ్రాసి, ఆ విలువను తిరిగి ఇస్తాయి. currentWindow() విషయంలో, ఇది స్టోరేజ్[1]ను తిరిగి ఇస్తుంది.
merkleRoot()
0xED-0xF8 ఆఫ్సెట్లలోని కోడ్ splitter()లో 0x103-0x10Eలో చూసిన దానికి సమానంగా ఉంటుంది (JUMPI గమ్యస్థానం మినహా), కాబట్టి merkleRoot() కూడా payable కాదని మనకు తెలుసు.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| F9 | JUMPDEST | |
| FA | POP | |
| FB | PUSH2 0x00da | 0xDA |
| FE | PUSH1 0x00 | 0x00 0xDA |
| 100 | SLOAD | స్టోరేజ్[0] 0xDA |
| 101 | DUP2 | 0xDA స్టోరేజ్[0] 0xDA |
| 102 | JUMP | స్టోరేజ్[0] 0xDA |
జంప్ తర్వాత ఏమి జరుగుతుందో మేము ఇప్పటికే కనుగొన్నాము. కాబట్టి merkleRoot() స్టోరేజ్[0]ను తిరిగి ఇస్తుంది.
0x81e580d3
0x138-0x143 ఆఫ్సెట్లలోని కోడ్ splitter()లో 0x103-0x10Eలో చూసిన దానికి సమానంగా ఉంటుంది (JUMPI గమ్యస్థానం మినహా), కాబట్టి ఈ ఫంక్షన్ కూడా payable కాదని మనకు తెలుసు.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 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 బైట్ల (ఒక పదం) కాల్ డేటాను తీసుకుంటుందని అనిపిస్తుంది.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 19D | DUP1 | 0x00 0x00 0x04 CALLDATASIZE 0x0153 0xDA |
| 19E | DUP2 | 0x00 0x00 0x00 0x04 CALLDATASIZE 0x0153 0xDA |
| 19F | REVERT |
దానికి కాల్ డేటా లభించకపోతే, లావాదేవీ ఏ రిటర్న్ డేటా లేకుండా రివర్ట్ చేయబడుతుంది.
ఫంక్షన్ నిజంగా తనకు అవసరమైన కాల్ డేటాను పొందితే ఏమి జరుగుతుందో చూద్దాం.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 1A0 | JUMPDEST | 0x00 0x04 CALLDATASIZE 0x0153 0xDA |
| 1A1 | POP | 0x04 CALLDATASIZE 0x0153 0xDA |
| 1A2 | CALLDATALOAD | calldataload(4) CALLDATASIZE 0x0153 0xDA |
calldataload(4) అనేది పద్ధతి సంతకం తర్వాత కాల్ డేటా యొక్క మొదటి పదం
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 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 | స్టోరేజ్[4] calldataload(4) 0x04 calldataload(4) 0xDA |
| 174 | DUP2 | calldataload(4) స్టోరేజ్[4] calldataload(4) 0x04 calldataload(4) 0xDA |
| 175 | LT | calldataload(4)<స్టోరేజ్[4] calldataload(4) 0x04 calldataload(4) 0xDA |
| 176 | PUSH2 0x017e | 0x017EC calldataload(4)<స్టోరేజ్[4] calldataload(4) 0x04 calldataload(4) 0xDA |
| 179 | JUMPI | calldataload(4) 0x04 calldataload(4) 0xDA |
మొదటి పదం స్టోరేజ్[4] కంటే తక్కువ కాకపోతే, ఫంక్షన్ విఫలమవుతుంది. ఇది ఏ రిటర్న్ విలువ లేకుండా రివర్ట్ అవుతుంది:
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 17A | PUSH1 0x00 | 0x00 ... |
| 17C | DUP1 | 0x00 0x00 ... |
| 17D | REVERT |
calldataload(4) స్టోరేజ్[4] కంటే తక్కువగా ఉంటే, మనకు ఈ కోడ్ వస్తుంది:
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 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 నాలుగు)
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 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 | స్టోరేజ్[(((0x00-0x1F యొక్క SHA3))) + calldataload(4)] calldataload(4) 0xDA |
కాబట్టి స్టోరేజ్లో ఒక లుకప్ టేబుల్ ఉంది, ఇది 0x000...0004 యొక్క SHA3 వద్ద మొదలవుతుంది మరియు ప్రతి చట్టబద్ధమైన కాల్ డేటా విలువ కోసం ఒక ఎంట్రీని కలిగి ఉంటుంది (స్టోరేజ్[4] కంటే తక్కువ విలువ).
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 18B | SWAP1 | calldataload(4) స్టోరేజ్[(((0x00-0x1F యొక్క SHA3))) + calldataload(4)] 0xDA |
| 18C | POP | స్టోరేజ్[(((0x00-0x1F యొక్క SHA3))) + calldataload(4)] 0xDA |
| 18D | DUP2 | 0xDA స్టోరేజ్[(((0x00-0x1F యొక్క SHA3))) + calldataload(4)] 0xDA |
| 18E | JUMP | స్టోరేజ్[(((0x00-0x1F యొక్క SHA3))) + calldataload(4)] 0xDA |
0xDA ఆఫ్సెట్ వద్ద ఉన్న కోడ్ ఏమి చేస్తుందో మాకు ఇప్పటికే తెలుసు, అది స్టాక్ టాప్ విలువను కాలర్కు తిరిగి ఇస్తుంది. కాబట్టి ఈ ఫంక్షన్ లుకప్ టేబుల్ నుండి విలువను కాలర్కు తిరిగి ఇస్తుంది.
0x1f135823
0xC4-0xCF ఆఫ్సెట్లలోని కోడ్ splitter()లో 0x103-0x10Eలో చూసిన దానికి సమానంగా ఉంటుంది (JUMPI గమ్యస్థానం మినహా), కాబట్టి ఈ ఫంక్షన్ కూడా payable కాదని మనకు తెలుసు.
| ఆఫ్సెట్ | ఆప్కోడ్ | స్టాక్ |
|---|---|---|
| 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 |
0xDA ఆఫ్సెట్ వద్ద ఉన్న కోడ్ ఏమి చేస్తుందో మాకు ఇప్పటికే తెలుసు, అది స్టాక్ టాప్ విలువను కాలర్కు తిరిగి ఇస్తుంది. కాబట్టి ఈ ఫంక్షన్ Value*ను తిరిగి ఇస్తుంది.
పద్ధతి సారాంశం
ఈ సమయంలో మీరు కాంట్రాక్ట్ను అర్థం చేసుకున్నారని మీకు అనిపిస్తుందా? నాకు అలా అనిపించడం లేదు. ఇప్పటివరకు మాకు ఈ పద్ధతులు ఉన్నాయి:
| పద్ధతి | అర్థం |
|---|---|
| బదిలీ | కాల్ ద్వారా అందించిన విలువను అంగీకరించి, Value*ను ఆ మొత్తంతో పెంచండి |
| splitter() | స్టోరేజ్[3], ప్రాక్సీ చిరునామాను తిరిగి ఇవ్వండి |
| currentWindow() | స్టోరేజ్[1]ను తిరిగి ఇవ్వండి |
| merkleRoot() | స్టోరేజ్[0]ను తిరిగి ఇవ్వండి |
| 0x81e580d3 | పారామీటర్ స్టోరేజ్[4] కంటే తక్కువగా ఉంటే, ఒక లుకప్ టేబుల్ నుండి విలువను తిరిగి ఇవ్వండి |
| 0x1f135823 | స్టోరేజ్[6]ను తిరిగి ఇవ్వండి, అకా విలువ* |
కానీ మాకు తెలుసు, ఇతర ఏ కార్యాచరణ అయినా స్టోరేజ్[3]లోని కాంట్రాక్ట్ ద్వారా అందించబడుతుంది. ఆ కాంట్రాక్ట్ ఏమిటో మాకు తెలిస్తే అది మాకు ఒక క్లూ ఇస్తుంది. అదృష్టవశాత్తూ, ఇది బ్లాక్ చైను మరియు ప్రతిదీ సిద్ధాంతపరంగా అయినా తెలుసు. మేము స్టోరేజ్[3]ను సెట్ చేసే ఏ పద్ధతులను చూడలేదు, కాబట్టి అది కన్స్ట్రక్టర్ ద్వారా సెట్ చేయబడి ఉండాలి.
నిర్మాణకర్త
మేము ఒక కాంట్రాక్ట్ను చూసినప్పుడుopens in a new tab దాన్ని సృష్టించిన లావాదేవీని కూడా చూడవచ్చు.
మేము ఆ లావాదేవీని క్లిక్ చేసి, ఆపై స్టేట్ ట్యాబ్ను క్లిక్ చేస్తే, పారామీటర్ల యొక్క ప్రారంభ విలువలను చూడవచ్చు. నిర్దిష్టంగా, స్టోరేజ్[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 ప్రతికూలంగా లేదని తనిఖీ చేస్తున్నట్లు అనిపిస్తుంది. ఇది బహుశా వ్రాప్ అరౌండ్ కేసులను నివారించడానికి కావచ్చు.
చివరగా, ఫంక్షన్ ఒక స్కేల్ చేయబడిన విలువను తిరిగి ఇస్తుంది.
claim
డీకంపైలర్ సృష్టించే కోడ్ సంక్లిష్టంగా ఉంటుంది, మరియు దానిలో అన్నీ మాకు సంబంధితంగా ఉండవు. ఉపయోగకరమైన సమాచారాన్ని అందించే లైన్లపై దృష్టి పెట్టడానికి నేను దానిలో కొంత భాగాన్ని వదిలివేయబోతున్నాను
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'కాబట్టి ఇప్పుడు మనకు తెలుసు, స్టోరేజ్[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కింది రెండు లైన్లు మాకు చెబుతున్నాయి, స్టోరేజ్[2] కూడా మనం పిలిచే ఒక కాంట్రాక్ట్ అని. మనం కన్స్ట్రక్టర్ లావాదేవీని చూస్తేopens in a new tab ఈ కాంట్రాక్ట్ 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2opens in a new tab అని చూస్తాము, ఇది ఒక వ్రాప్డ్ ఈథర్ కాంట్రాక్ట్, దీని సోర్స్ కోడ్ 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.
పేజీ చివరి అప్డేట్: 22 ఆగస్టు, 2025



![స్టోరేజ్[6]లో మార్పు](/_next/image/?url=%2Fcontent%2Fdevelopers%2Ftutorials%2Freverse-engineering-a-contract%2Fstorage6.png&w=1920&q=75)



