Ruka kwenda kwenye maudhui makuu

Uhandisi wa Nyuma wa Mkataba

evm
opcodes
Advanced
Ori Pomerantz
30 Desemba 2021
30 minute read

Utangulizi

Hakuna siri kwenye blockchain, kila kitu kinachotokea ni thabiti, kinaweza kuthibitishwa na kinapatikana kwa umma. Kimsingi, mikataba inapaswa kuwa na msimbo wake chanzo uliochapishwa na kuthibitishwa kwenye Etherscanopens in a new tab. Hata hivyo, sio wakati wote ndivyo ilivyoopens in a new tab. Katika makala hii utajifunza jinsi ya kufanya uhandisi wa nyuma wa mikataba kwa kuangalia mkataba bila msimbo chanzo, 0x2510c039cc3b061d79e564b38836da87e31b342fopens in a new tab.

Kuna vikompaila vya kinyume, lakini sio kila wakati vinatoa matokeo yanayoweza kutumikaopens in a new tab. Katika makala hii unajifunza jinsi ya kufanya uhandisi wa nyuma na kuelewa mkataba kutoka kwa opcodesopens in a new tab, pamoja na jinsi ya kutafsiri matokeo ya decompiler.

Ili kuweza kuelewa makala hii unapaswa kuwa unajua misingi ya EVM, na angalau uwe unafahamu kwa kiasi fulani mkusanyaji wa EVM. Unaweza kusoma kuhusu mada hizi hapaopens in a new tab.

Andaa Msimbo Unaoweza Kutekelezwa

Unaweza kupata opcodes kwa kwenda Etherscan kwa ajili ya mkataba, kubofya kichupo cha Contract na kisha Switch to Opcodes View. Unapata mwonekano ambao ni opcode moja kwa kila mstari.

Mwonekano wa Opcode kutoka Etherscan

Ili kuweza kuelewa miruko, hata hivyo, unahitaji kujua ni wapi katika msimbo kila opcode iko. Ili kufanya hivyo, njia moja ni kufungua Google Spreadsheet na kubandika opcodes katika safu C. Unaweza kuruka hatua zifuatazo kwa kutengeneza nakala ya jedwali hili lililoandaliwa tayariopens in a new tab.

Hatua inayofuata ni kupata maeneo sahihi ya msimbo ili tuweze kuelewa miruko. Tutaweka ukubwa wa opcode katika safu B, na eneo (katika heksadesimali) katika safu A. Andika chaguo hili la kukokotoa katika seli B1 na kisha nakili na ubandike kwa safu B iliyobaki, hadi mwisho wa msimbo. Baada ya kufanya hivi unaweza kuficha safu B.

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

Kwanza chaguo hili la kukokotoa linaongeza baiti moja kwa opcode yenyewe, na kisha hutafuta PUSH. Opcodes za kusukuma ni maalum kwa sababu zinahitaji kuwa na baiti za ziada kwa thamani inayosukumwa. Ikiwa opcode ni PUSH, tunatoa idadi ya baiti na kuiongeza.

Katika A1 weka kukabiliana kwanza, sifuri. Kisha, katika A2, weka chaguo hili la kukokotoa na tena nakili na ubandike kwa safu A iliyobaki:

1=dec2hex(hex2dec(A1)+B1)

Tunahitaji chaguo hili la kukokotoa ili kutupa thamani ya heksadesimali kwa sababu thamani zinazosukumwa kabla ya miruko (JUMP na JUMPI) tunapewa katika heksadesimali.

Mahali pa Kuingilia (0x00)

Mikataba daima hutekelezwa kutoka kwa baiti ya kwanza. Hii ni sehemu ya awali ya msimbo:

KukabilianaOpcodeRafu (baada ya opcode)
0PUSH1 0x800x80
2PUSH1 0x400x40, 0x80
4MSTORETupu
5PUSH1 0x040x04
7CALLDATASIZECALLDATASIZE 0x04
8LTCALLDATASIZE<4
9PUSH2 0x005e0x5E CALLDATASIZE<4
CJUMPITupu

Msimbo huu hufanya mambo mawili:

  1. Andika 0x80 kama thamani ya baiti 32 kwenye maeneo ya kumbukumbu 0x40-0x5F (0x80 huhifadhiwa katika 0x5F, na 0x40-0x5E zote ni sifuri).
  2. Soma ukubwa wa calldata. Kwa kawaida data ya simu kwa ajili ya mkataba wa Ethereum hufuata ABI (kiolesura cha binary cha mfumo)opens in a new tab, ambayo kwa kiwango cha chini inahitaji baiti nne kwa kiteuzi cha chaguo la kukokotoa. Ikiwa ukubwa wa data ya simu ni chini ya nne, ruka hadi 0x5E.

Chati ya mtiririko kwa sehemu hii

Mshughulikiaji katika 0x5E (kwa data ya simu isiyo ya ABI)

KukabilianaOpcode
5EJUMPDEST
5FCALLDATASIZE
60PUSH2 0x007c
63JUMPI

Kipande hiki kinaanza na JUMPDEST. Programu za EVM (mashine halisi ya ethereum) hutupa ubaguzi ikiwa utaruka hadi opcode ambayo sio JUMPDEST. Kisha inaangalia CALLDATASIZE, na ikiwa ni "kweli" (yaani, sio sifuri) inaruka hadi 0x7C. Tutafikia hilo hapo chini.

KukabilianaOpcodeRafu (baada ya opcode)
64CALLVALUE iliyotolewa na simu. Inaitwa msg.value katika Solidity
65PUSH1 0x066 CALLVALUE
67PUSH1 0x000 6 CALLVALUE
69DUP3CALLVALUE 0 6 CALLVALUE
6ADUP36 CALLVALUE 0 6 CALLVALUE
6BSLOADGhala[6] CALLVALUE 0 6 CALLVALUE

Kwa hivyo wakati hakuna data ya simu tunasoma thamani ya Ghala[6]. Bado hatujui thamani hii ni nini, lakini tunaweza kutafuta miamala ambayo mkataba ulipokea bila data ya simu. Miamala ambayo huhamisha ETH tu bila data yoyote ya simu (na kwa hivyo hakuna njia) ina njia ya Transfer katika Etherscan. Kwa kweli, muamala wa kwanza kabisa ambao mkataba ulipokeaopens in a new tab ni uhamisho.

Tukiangalia katika muamala huo na kubofya Click to see More, tunaona kwamba data ya simu, inayoitwa data ya ingizo, kwa kweli haina kitu (0x). Angalia pia kwamba thamani ni ETH 1.559, hiyo itakuwa muhimu baadaye.

Data ya simu haina kitu

Ifuatayo, bofya kichupo cha Hali na upanue mkataba tunaofanyia uhandisi wa nyuma (0x2510...). Unaweza kuona kwamba Ghala[6] ilibadilika wakati wa muamala, na ukibadilisha Hex kuwa Nambari, unaona ikawa 1,559,000,000,000,000,000, thamani iliyohamishwa katika wei (nimeongeza koma kwa uwazi), inayolingana na thamani ya mkataba unaofuata.

Mabadiliko katika Ghala[6]

Tukiangalia katika mabadiliko ya hali yaliyosababishwa na miamala mingine ya Transfer kutoka kipindi hichoopens in a new tab tunaona kwamba Ghala[6] ilifuatilia thamani ya mkataba kwa muda. Kwa sasa tutaiita Value*. Nyota (*) inatukumbusha kwamba bado hatu_jui_ kigezo hiki hufanya nini, lakini haiwezi kuwa tu kufuatilia thamani ya mkataba kwa sababu hakuna haja ya kutumia ghala, ambayo ni ghali sana, wakati unaweza kupata salio la akaunti yako ukitumia ADDRESS BALANCE. Opcode ya kwanza inasukuma anwani ya mkataba yenyewe. Ya pili inasoma anwani iliyo juu ya rafu na kuibadilisha na salio la anwani hiyo.

KukabilianaOpcodeRafu
6CPUSH2 0x00750x75 Thamani* CALLVALUE 0 6 CALLVALUE
6FSWAP2CALLVALUE Thamani* 0x75 0 6 CALLVALUE
70SWAP1Thamani* CALLVALUE 0x75 0 6 CALLVALUE
71PUSH2 0x01a70x01A7 Thamani* CALLVALUE 0x75 0 6 CALLVALUE
74JUMP

Tutaendelea kufuatilia msimbo huu katika marudio ya mruko.

KukabilianaOpcodeRafu
1A7JUMPDESTThamani* CALLVALUE 0x75 0 6 CALLVALUE
1A8PUSH1 0x000x00 Thamani* CALLVALUE 0x75 0 6 CALLVALUE
1AADUP3CALLVALUE 0x00 Thamani* CALLVALUE 0x75 0 6 CALLVALUE
1ABNOT2^256-CALLVALUE-1 0x00 Thamani* CALLVALUE 0x75 0 6 CALLVALUE

NOT ni bitwise, kwa hivyo inabadilisha thamani ya kila biti katika thamani ya simu.

KukabilianaOpcodeRafu
1ACDUP3Thamani* 2^256-CALLVALUE-1 0x00 Thamani* CALLVALUE 0x75 0 6 CALLVALUE
1ADGTThamani*>2^256-CALLVALUE-1 0x00 Thamani* CALLVALUE 0x75 0 6 CALLVALUE
1AEISZEROThamani*<=2^256-CALLVALUE-1 0x00 Thamani* CALLVALUE 0x75 0 6 CALLVALUE
1AFPUSH2 0x01df0x01DF Thamani*<=2^256-CALLVALUE-1 0x00 Thamani* CALLVALUE 0x75 0 6 CALLVALUE
1B2JUMPI

Tunaruka ikiwa Value* ni ndogo kuliko 2^256-CALLVALUE-1 au sawa nayo. Hii inaonekana kama mantiki ya kuzuia kufurika. Na kwa hakika, tunaona kwamba baada ya shughuli chache zisizo na maana (kuandika kwa kumbukumbu kunakaribia kufutwa, kwa mfano) kwa kukabiliana 0x01DE mkataba unarudi nyuma ikiwa kufurika kutagunduliwa, ambayo ni tabia ya kawaida.

Kumbuka kuwa kufurika kama hivyo hakuna uwezekano mkubwa, kwa sababu kungehitaji thamani ya simu pamoja na Thamani* kulinganishwa na wei 2^256, takriban ETH 10^59. Jumla ya usambazaji wa ETH, wakati wa kuandika, ni chini ya milioni mia mbiliopens in a new tab.

KukabilianaOpcodeRafu
1DFJUMPDEST0x00 Thamani* CALLVALUE 0x75 0 6 CALLVALUE
1E0POPThamani* CALLVALUE 0x75 0 6 CALLVALUE
1E1ADDThamani*+CALLVALUE 0x75 0 6 CALLVALUE
1E2SWAP10x75 Thamani*+CALLVALUE 0 6 CALLVALUE
1E3JUMP

Ikiwa tulifika hapa, pata Thamani* + CALLVALUE na uruke ili kukabiliana na 0x75.

KukabilianaOpcodeRafu
75JUMPDESTThamani*+CALLVALUE 0 6 CALLVALUE
76SWAP10 Thamani*+CALLVALUE 6 CALLVALUE
77SWAP26 Thamani*+CALLVALUE 0 CALLVALUE
78SSTORE0 CALLVALUE

Tukifika hapa (ambayo inahitaji data ya simu kuwa tupu) tunaongeza kwa Thamani* thamani ya simu. Hii inaambatana na kile tunachosema miamala ya Transfer hufanya.

KukabilianaOpcode
79POP
7APOP
7BSTOP

Hatimaye, futa rafu (ambayo si lazima) na uashiria mwisho wa mafanikio wa muamala.

Ili kujumlisha yote, hii hapa chati ya mtiririko kwa msimbo wa awali.

Chati ya mtiririko ya mahali pa kuingilia

Mshughulikiaji katika 0x7C

Sikuweka kichwa cha habari kwa makusudi nini mhudumu huyu anafanya. Hoja sio kukufundisha jinsi mkataba huu maalum unavyofanya kazi, lakini jinsi ya kufanya uhandisi wa nyuma wa mikataba. Utajifunza inachofanya kwa njia ile ile niliyoifanya, kwa kufuata msimbo.

Tunafika hapa kutoka sehemu kadhaa:

  • Ikiwa kuna data ya simu ya baiti 1, 2, au 3 (kutoka kwa kukabiliana 0x63)
  • Ikiwa saini ya mbinu haijulikani (kutoka kwa makabiliano 0x42 na 0x5D)
KukabilianaOpcodeRafu
7CJUMPDEST
7DPUSH1 0x000x00
7FPUSH2 0x009d0x9D 0x00
82PUSH1 0x030x03 0x9D 0x00
84SLOADGhala[3] 0x9D 0x00

Hii ni seli nyingine ya ghala, ambayo sikuweza kupata katika miamala yoyote kwa hivyo ni ngumu kujua inamaanisha nini. Msimbo ulio hapa chini utaiweka wazi zaidi.

KukabilianaOpcodeRafu
85PUSH20 0xffffffffffffffffffffffffffffffffffffffff0xff....ff Ghala[3] 0x9D 0x00
9AANDGhala[3]-kama-anwani 0x9D 0x00

Opcode hizi hupunguza thamani tunayosoma kutoka kwa Ghala[3] hadi biti 160, urefu wa anwani ya Ethereum.

KukabilianaOpcodeRafu
9BSWAP10x9D Ghala[3]-kama-anwani 0x00
9CJUMPGhala[3]-kama-anwani 0x00

Mruko huu ni wa ziada, kwani tunaenda kwa opcode inayofuata. Msimbo huu hauna ufanisi wa gesi kama unavyoweza kuwa.

KukabilianaOpcodeRafu
9DJUMPDESTGhala[3]-kama-anwani 0x00
9ESWAP10x00 Ghala[3]-kama-anwani
9FPOPGhala[3]-kama-anwani
A0PUSH1 0x400x40 Ghala[3]-kama-anwani
A2MLOADMem[0x40] Ghala[3]-kama-anwani

Mwanzoni kabisa mwa msimbo tuliweka Mem[0x40] kuwa 0x80. Tukiangalia 0x40 baadaye, tunaona kwamba hatuibadilishi - kwa hivyo tunaweza kudhani ni 0x80.

KukabilianaOpcodeRafu
A3CALLDATASIZECALLDATASIZE 0x80 Ghala[3]-kama-anwani
A4PUSH1 0x000x00 CALLDATASIZE 0x80 Ghala[3]-kama-anwani
A6DUP30x80 0x00 CALLDATASIZE 0x80 Ghala[3]-kama-anwani
A7CALLDATACOPY0x80 Ghala[3]-kama-anwani

Nakili data yote ya simu kwenye kumbukumbu, kuanzia 0x80.

KukabilianaOpcodeRafu
A8PUSH1 0x000x00 0x80 Ghala[3]-kama-anwani
AADUP10x00 0x00 0x80 Ghala[3]-kama-anwani
ABCALLDATASIZECALLDATASIZE 0x00 0x00 0x80 Ghala[3]-kama-anwani
ACDUP40x80 CALLDATASIZE 0x00 0x00 0x80 Ghala[3]-kama-anwani
ADDUP6Ghala[3]-kama-anwani 0x80 CALLDATASIZE 0x00 0x00 0x80 Ghala[3]-kama-anwani
AEGASGesi Ghala[3]-kama-anwani 0x80 CALLDATASIZE 0x00 0x00 0x80 Ghala[3]-kama-anwani
AFDELEGATE_CALL

Sasa mambo ni wazi zaidi. Mkataba huu unaweza kufanya kazi kama proksiopens in a new tab, ukiita anwani katika Ghala[3] kufanya kazi halisi. DELEGATE_CALL huita mkataba tofauti, lakini hubaki katika ghala moja. Hii inamaanisha kuwa mkataba uliokabidhiwa, ambao sisi ni proksi wake, hufikia nafasi sawa ya ghala. Vigezo vya simu ni:

  • Gesi: Gesi yote iliyobaki
  • Anwani iliyopigiwa simu: Ghala[3]-kama-anwani
  • Data ya simu: baiti za CALLDATASIZE kuanzia 0x80, ambayo ndipo tulipo weka data ya awali ya simu
  • Data ya kurudisha: Hakuna (0x00 - 0x00) Tutapata data ya kurudisha kwa njia zingine (tazama hapa chini)
KukabilianaOpcodeRafu
B0RETURNDATASIZERETURNDATASIZE (((mafanikio/kutofaulu kwa simu))) 0x80 Ghala[3]-kama-anwani
B1DUP1RETURNDATASIZE RETURNDATASIZE (((mafanikio/kutofaulu kwa simu))) 0x80 Ghala[3]-kama-anwani
B2PUSH1 0x000x00 RETURNDATASIZE RETURNDATASIZE (((mafanikio/kutofaulu kwa simu))) 0x80 Ghala[3]-kama-anwani
B4DUP50x80 0x00 RETURNDATASIZE RETURNDATASIZE (((mafanikio/kutofaulu kwa simu))) 0x80 Ghala[3]-kama-anwani
B5RETURNDATACOPYRETURNDATASIZE (((mafanikio/kutofaulu kwa simu))) 0x80 Ghala[3]-kama-anwani

Hapa tunakili data yote ya kurudi kwenye bafa ya kumbukumbu kuanzia 0x80.

KukabilianaOpcodeRafu
B6DUP2(((mafanikio/kutofaulu kwa simu))) RETURNDATASIZE (((mafanikio/kutofaulu kwa simu))) 0x80 Ghala[3]-kama-anwani
B7DUP1(((mafanikio/kutofaulu kwa simu))) (((mafanikio/kutofaulu kwa simu))) RETURNDATASIZE (((mafanikio/kutofaulu kwa simu))) 0x80 Ghala[3]-kama-anwani
B8ISZERO(((simu ilishindwa))) (((mafanikio/kutofaulu kwa simu))) RETURNDATASIZE (((mafanikio/kutofaulu kwa simu))) 0x80 Ghala[3]-kama-anwani
B9PUSH2 0x00c00xC0 (((simu ilishindwa))) (((mafanikio/kutofaulu kwa simu))) RETURNDATASIZE (((mafanikio/kutofaulu kwa simu))) 0x80 Ghala[3]-kama-anwani
BCJUMPI(((mafanikio/kutofaulu kwa simu))) RETURNDATASIZE (((mafanikio/kutofaulu kwa simu))) 0x80 Ghala[3]-kama-anwani
BDDUP2RETURNDATASIZE (((mafanikio/kutofaulu kwa simu))) RETURNDATASIZE (((mafanikio/kutofaulu kwa simu))) 0x80 Ghala[3]-kama-anwani
BEDUP50x80 RETURNDATASIZE (((mafanikio/kutofaulu kwa simu))) RETURNDATASIZE (((mafanikio/kutofaulu kwa simu))) 0x80 Ghala[3]-kama-anwani
BFRETURN

Kwa hivyo baada ya simu tunakili data ya kurudi kwenye bafa 0x80 - 0x80+RETURNDATASIZE, na ikiwa simu imefanikiwa basi RETURN na bafa hiyo haswa.

DELEGATECALL Imeshindwa

Tukifika hapa, hadi 0xC0, inamaanisha kwamba mkataba tuliopiga simu ulirudishwa nyuma. Kwa vile sisi ni proksi tu wa mkataba huo, tunataka kurudisha data sawa na pia kurudi nyuma.

KukabilianaOpcodeRafu
C0JUMPDEST(((mafanikio/kutofaulu kwa simu))) RETURNDATASIZE (((mafanikio/kutofaulu kwa simu))) 0x80 Ghala[3]-kama-anwani
C1DUP2RETURNDATASIZE (((mafanikio/kutofaulu kwa simu))) RETURNDATASIZE (((mafanikio/kutofaulu kwa simu))) 0x80 Ghala[3]-kama-anwani
C2DUP50x80 RETURNDATASIZE (((mafanikio/kutofaulu kwa simu))) RETURNDATASIZE (((mafanikio/kutofaulu kwa simu))) 0x80 Ghala[3]-kama-anwani
C3REVERT

Kwa hivyo sisi REVERT na bafa ile ile tuliyotumia kwa RETURN hapo awali: 0x80 - 0x80+RETURNDATASIZE

Chati ya mtiririko ya simu kwa proksi

Simu za ABI

Ikiwa ukubwa wa data ya simu ni baiti nne au zaidi hii inaweza kuwa simu halali ya ABI.

KukabilianaOpcodeRafu
DPUSH1 0x000x00
FCALLDATALOAD(((Neno la kwanza (biti 256) la data ya simu)))
10PUSH1 0xe00xE0 (((Neno la kwanza (biti 256) la data ya simu)))
12SHR(((biti 32 za kwanza (baiti 4) za data ya simu)))

Etherscan inatuambia kwamba 1C ni opcode isiyojulikana, kwa sababu iliongezwa baada ya Etherscan kuandika kipengele hikiopens in a new tab na hawajaisasisha. Jedwali la opcode lililosasishwaopens in a new tab linatuonyesha kuwa hii ni shift right

KukabilianaOpcodeRafu
13DUP1(((biti 32 za kwanza (baiti 4) za data ya simu))) (((biti 32 za kwanza (baiti 4) za data ya simu)))
14PUSH4 0x3cd8045e0x3CD8045E (((biti 32 za kwanza (baiti 4) za data ya simu))) (((biti 32 za kwanza (baiti 4) za data ya simu)))
19GT0x3CD8045E>biti-32-za-kwanza-za-data-ya-simu (((biti 32 za kwanza (baiti 4) za data ya simu)))
1APUSH2 0x00430x43 0x3CD8045E>biti-32-za-kwanza-za-data-ya-simu (((biti 32 za kwanza (baiti 4) za data ya simu)))
1DJUMPI(((biti 32 za kwanza (baiti 4) za data ya simu)))

Kwa kugawanya majaribio ya ulinganishaji wa saini ya mbinu katika mbili kama hii huokoa nusu ya majaribio kwa wastani. Msimbo unaofuata mara moja hii na msimbo katika 0x43 hufuata muundo sawa: DUP1 biti 32 za kwanza za data ya simu, PUSH4 (((saini ya mbinu>, endesha EQ ili kuangalia usawa, na kisha JUMPI ikiwa saini ya mbinu inalingana. Hapa kuna saini za mbinu, anwani zao, na ikiwa inajulikana ufafanuzi wa mbinu unaolinganaopens in a new tab:

NjiaSahihi ya njiaKukabiliana na kuruka ndani
splitter()opens in a new tab0x3cd8045e0x0103
???0x81e580d30x0138
currentWindow()opens in a new tab0xba0bafb40x0158
???0x1f1358230x00C4
merkleRoot()opens in a new tab0x2eb4a7ab0x00ED

Ikiwa hakuna mechi inayopatikana, msimbo huruka hadi kwa mshughulikiaji wa proksi saa 0x7C, kwa matumaini kwamba mkataba ambao sisi ni proksi wake una mechi.

Chati ya mtiririko ya simu za ABI

splitter()

KukabilianaOpcodeRafu
103JUMPDEST
104CALLVALUECALLVALUE
105DUP1CALLVALUE CALLVALUE
106ISZEROCALLVALUE==0 CALLVALUE
107PUSH2 0x010f0x010F CALLVALUE==0 CALLVALUE
10AJUMPICALLVALUE
10BPUSH1 0x000x00 CALLVALUE
10DDUP10x00 0x00 CALLVALUE
10EREVERT

Jambo la kwanza ambalo chaguo hili la kukokotoa hufanya ni kuangalia kuwa simu haikutuma ETH yoyote. Chaguo hili la kukokotoa sio payableopens in a new tab. Ikiwa mtu alitutumia ETH hiyo lazima iwe kosa na tunataka REVERT ili kuepuka kuwa na ETH hiyo ambapo hawawezi kuirudisha.

KukabilianaOpcodeRafu
10FJUMPDEST
110POP
111PUSH1 0x030x03
113SLOAD(((Ghala[3] a.k.a mkataba ambao sisi ni proksi)))
114PUSH1 0x400x40 (((Ghala[3] a.k.a mkataba ambao sisi ni proksi)))
116MLOAD0x80 (((Ghala[3] a.k.a mkataba ambao sisi ni proksi)))
117PUSH20 0xffffffffffffffffffffffffffffffffffffffff0xFF...FF 0x80 (((Ghala[3] a.k.a mkataba ambao sisi ni proksi)))
12CSWAP10x80 0xFF...FF (((Ghala[3] a.k.a mkataba ambao sisi ni proksi)))
12DSWAP2(((Ghala[3] a.k.a mkataba ambao sisi ni proksi))) 0xFF...FF 0x80
12EANDProxyAddr 0x80
12FDUP20x80 ProxyAddr 0x80
130MSTORE0x80

Na 0x80 sasa ina anwani ya proksi

KukabilianaOpcodeRafu
131PUSH1 0x200x20 0x80
133ADD0xA0
134PUSH2 0x00e40xE4 0xA0
137JUMP0xA0

Msimbo wa E4

Hii ni mara ya kwanza tunaona mistari hii, lakini inashirikiwa na njia zingine (tazama hapa chini). Kwa hivyo tutaita thamani katika rafu X, na kumbuka tu kwamba katika splitter() thamani ya X hii ni 0xA0.

KukabilianaOpcodeRafu
E4JUMPDESTX
E5PUSH1 0x400x40 X
E7MLOAD0x80 X
E8DUP10x80 0x80 X
E9SWAP2X 0x80 0x80
EASUBX-0x80 0x80
EBSWAP10x80 X-0x80
ECRETURN

Kwa hivyo msimbo huu hupokea kielekezi cha kumbukumbu kwenye rafu (X), na husababisha mkataba RETURN na bafa ambayo ni 0x80 - X.

Katika kesi ya splitter(), hii inarudisha anwani ambayo sisi ni proksi. RETURN inarudisha bafa katika 0x80-0x9F, ambayo ndipo tuliandika data hii (kukabiliana 0x130 hapo juu).

currentWindow()

Msimbo katika makabiliano 0x158-0x163 ni sawa na tulivyoona katika 0x103-0x10E katika splitter() (isipokuwa marudio ya JUMPI), kwa hivyo tunajua currentWindow() pia haiwezi kulipwa.

KukabilianaOpcodeRafu
164JUMPDEST
165POP
166PUSH2 0x00da0xDA
169PUSH1 0x010x01 0xDA
16BSLOADGhala[1] 0xDA
16CDUP20xDA Ghala[1] 0xDA
16DJUMPGhala[1] 0xDA

Msimbo wa DA

Msimbo huu pia unashirikiwa na njia zingine. Kwa hivyo tutaita thamani katika rafu Y, na kumbuka tu kwamba katika currentWindow() thamani ya Y hii ni Ghala[1].

KukabilianaOpcodeRafu
DAJUMPDESTY 0xDA
DBPUSH1 0x400x40 Y 0xDA
DDMLOAD0x80 Y 0xDA
DESWAP1Y 0x80 0xDA
DFDUP20x80 Y 0x80 0xDA
E0MSTORE0x80 0xDA

Andika Y hadi 0x80-0x9F.

KukabilianaOpcodeRafu
E1PUSH1 0x200x20 0x80 0xDA
E3ADD0xA0 0xDA

Na mengine yameelezwa hapo juu. Kwa hivyo miruko hadi 0xDA andika sehemu ya juu ya rafu (Y) hadi 0x80-0x9F, na urudishe thamani hiyo. Katika kesi ya currentWindow(), inarudisha Ghala[1].

merkleRoot()

Msimbo katika makabiliano 0xED-0xF8 ni sawa na tulivyoona katika 0x103-0x10E katika splitter() (isipokuwa marudio ya JUMPI), kwa hivyo tunajua merkleRoot() pia haiwezi kulipwa.

KukabilianaOpcodeRafu
F9JUMPDEST
FAPOP
FBPUSH2 0x00da0xDA
FEPUSH1 0x000x00 0xDA
100SLOADGhala[0] 0xDA
101DUP20xDA Ghala[0] 0xDA
102JUMPGhala[0] 0xDA

Kinachotokea baada ya mruko tayari tumegundua. Kwa hivyo merkleRoot() inarudisha Ghala[0].

0x81e580d3

Msimbo katika makabiliano 0x138-0x143 ni sawa na tulivyoona katika 0x103-0x10E katika splitter() (isipokuwa marudio ya JUMPI), kwa hivyo tunajua chaguo hili la kukokotoa pia haliwezi kulipwa.

KukabilianaOpcodeRafu
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

Inaonekana kama chaguo hili la kukokotoa huchukua angalau baiti 32 (neno moja) la data ya simu.

KukabilianaOpcodeRafu
19DDUP10x00 0x00 0x04 CALLDATASIZE 0x0153 0xDA
19EDUP20x00 0x00 0x00 0x04 CALLDATASIZE 0x0153 0xDA
19FREVERT

Ikiwa haipati data ya simu, muamala unarudishwa bila data yoyote ya kurudi.

Hebu tuone nini kinatokea ikiwa chaguo la kukokotoa linapata data ya simu inayohitaji.

KukabilianaOpcodeRafu
1A0JUMPDEST0x00 0x04 CALLDATASIZE 0x0153 0xDA
1A1POP0x04 CALLDATASIZE 0x0153 0xDA
1A2CALLDATALOADcalldataload(4) CALLDATASIZE 0x0153 0xDA

calldataload(4) ni neno la kwanza la data ya simu baada ya saini ya mbinu

KukabilianaOpcodeRafu
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
173SLOADGhala[4] calldataload(4) 0x04 calldataload(4) 0xDA
174DUP2calldataload(4) Ghala[4] calldataload(4) 0x04 calldataload(4) 0xDA
175LTcalldataload(4)<Ghala[4] calldataload(4) 0x04 calldataload(4) 0xDA
176PUSH2 0x017e0x017EC calldataload(4)<Ghala[4] calldataload(4) 0x04 calldataload(4) 0xDA
179JUMPIcalldataload(4) 0x04 calldataload(4) 0xDA

Ikiwa neno la kwanza si chini ya Ghala[4], chaguo la kukokotoa hushindwa. Inarudi nyuma bila thamani yoyote iliyorejeshwa:

KukabilianaOpcodeRafu
17APUSH1 0x000x00 ...
17CDUP10x00 0x00 ...
17DREVERT

Ikiwa calldataload(4) ni chini ya Ghala[4], tunapata msimbo huu:

KukabilianaOpcodeRafu
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

Na maeneo ya kumbukumbu 0x00-0x1F sasa yana data 0x04 (0x00-0x1E zote ni sifuri, 0x1F ni nne)

KukabilianaOpcodeRafu
184PUSH1 0x200x20 calldataload(4) 0x00 calldataload(4) 0xDA
186SWAP1calldataload(4) 0x20 0x00 calldataload(4) 0xDA
187SWAP20x00 0x20 calldataload(4) calldataload(4) 0xDA
188SHA3(((SHA3 ya 0x00-0x1F))) calldataload(4) calldataload(4) 0xDA
189ADD(((SHA3 ya 0x00-0x1F)))+calldataload(4) calldataload(4) 0xDA
18ASLOADGhala[(((SHA3 ya 0x00-0x1F))) + calldataload(4)] calldataload(4) 0xDA

Kwa hivyo kuna jedwali la kutafuta katika ghala, ambalo huanza kwenye SHA3 ya 0x000...0004 na lina ingizo kwa kila thamani halali ya data ya simu (thamani iliyo chini ya Ghala[4]).

KukabilianaOpcodeRafu
18BSWAP1calldataload(4) Ghala[(((SHA3 ya 0x00-0x1F))) + calldataload(4)] 0xDA
18CPOPGhala[(((SHA3 ya 0x00-0x1F))) + calldataload(4)] 0xDA
18DDUP20xDA Ghala[(((SHA3 ya 0x00-0x1F))) + calldataload(4)] 0xDA
18EJUMPGhala[(((SHA3 ya 0x00-0x1F))) + calldataload(4)] 0xDA

Tayari tunajua msimbo katika kukabiliana na 0xDA unafanya nini, unarudisha thamani ya juu ya rafu kwa mpigaji simu. Kwa hivyo chaguo hili la kukokotoa hurudisha thamani kutoka kwa jedwali la utafutaji hadi kwa mpigaji simu.

0x1f135823

Msimbo katika makabiliano 0xC4-0xCF ni sawa na tulivyoona katika 0x103-0x10E katika splitter() (isipokuwa marudio ya JUMPI), kwa hivyo tunajua chaguo hili la kukokotoa pia haliwezi kulipwa.

KukabilianaOpcodeRafu
D0JUMPDEST
D1POP
D2PUSH2 0x00da0xDA
D5PUSH1 0x060x06 0xDA
D7SLOADThamani* 0xDA
D8DUP20xDA Thamani* 0xDA
D9JUMPThamani* 0xDA

Tayari tunajua msimbo katika kukabiliana na 0xDA unafanya nini, unarudisha thamani ya juu ya rafu kwa mpigaji simu. Kwa hivyo chaguo hili la kukokotoa hurudisha Thamani*.

Muhtasari wa Njia

Je, unahisi unaelewa mkataba katika hatua hii? Mimi sielewi. Kufikia sasa tuna njia hizi:

NjiaMaana
HamishaKubali thamani iliyotolewa na simu na uongeze Thamani* kwa kiasi hicho
splitter()Rudisha Ghala[3], anwani ya proksi
currentWindow()Rudisha Ghala[1]
merkleRoot()Rudisha Ghala[0]
0x81e580d3Rudisha thamani kutoka kwa jedwali la utafutaji, mradi kigezo ni chini ya Ghala[4]
0x1f135823Rudisha Ghala[6], a.k.a. Thamani*

Lakini tunajua utendakazi mwingine wowote hutolewa na mkataba katika Ghala[3]. Labda ikiwa tungejua mkataba huo ni nini utatupa kidokezo. Kwa bahati nzuri, hii ni blockchain na kila kitu kinajulikana, angalau kwa nadharia. Hatukuona njia zozote ambazo zinaweka Ghala[3], kwa hivyo lazima iwe imewekwa na mjenzi.

Mjenzi

Tunapoangalia mkatabaopens in a new tab tunaweza pia kuona muamala ulioiunda.

Bofya muamala wa kuunda

Ikiwa tutabofya muamala huo, na kisha kichupo cha Hali, tunaweza kuona thamani za awali za vigezo. Hasa, tunaweza kuona kwamba Ghala[3] ina 0x2f81e57ff4f4d83b40a9f719fd892d8e806e0761opens in a new tab. Mkataba huo lazima uwe na utendakazi unaokosekana. Tunaweza kuielewa kwa kutumia zana zile zile tulizotumia kwa mkataba tunaouchunguza.

Mkataba wa Proksi

Kwa kutumia mbinu zile zile tulizotumia kwa mkataba wa asili hapo juu tunaweza kuona kwamba mkataba unarudi nyuma ikiwa:

  • Kuna ETH yoyote iliyoambatishwa kwenye simu (0x05-0x0F)
  • Ukubwa wa data ya simu ni chini ya nne (0x10-0x19 na 0xBE-0xC2)

Na kwamba mbinu inazotumia ni:

Tunaweza kupuuza njia nne za chini kwa sababu hatutawahi kuzifikia. Sahihi zao ni kwamba mkataba wetu wa asili unazishughulikia wenyewe (unaweza kubofya sahihi ili kuona maelezo hapo juu), kwa hivyo lazima ziwe mbinu ambazo zimebatilishwaopens in a new tab.

Moja ya njia zilizobaki ni claim(<params>), na nyingine ni isClaimed(<params>), kwa hivyo inaonekana kama mkataba wa airdrop. Badala ya kupitia opcode zilizobaki kwa opcode, tunaweza kujaribu decompileropens in a new tab, ambayo hutoa matokeo yanayoweza kutumika kwa chaguo tatu za kukokotoa kutoka kwa mkataba huu. Uhandisi wa nyuma wa zingine zimeachwa kama zoezi kwa msomaji.

scaleAmountByPercentage

Hivi ndivyo decompiler inatupa kwa chaguo hili la kukokotoa:

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)

Kwanza require hujaribu kuwa data ya simu ina, pamoja na baiti nne za saini ya chaguo la kukokotoa, angalau baiti 64, za kutosha kwa vigezo viwili. Ikiwa sivyo basi ni wazi kuna kitu kibaya.

Taarifa ya if inaonekana kuangalia kwamba _param1 sio sifuri, na kwamba _param1 * _param2 si hasi. Pengine ni kuzuia kesi za kuzunguka.

Hatimaye, chaguo la kukokotoa hurudisha thamani iliyopimwa.

dai

Msimbo ambao decompiler hutengeneza ni mgumu, na sio wote unaofaa kwetu. Nitaruka baadhi yake ili kuzingatia mistari ambayo ninaamini inatoa habari muhimu

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'

Tunaona hapa mambo mawili muhimu:

  • _param2, ingawa imetangazwa kama uint256, kwa kweli ni anwani
  • _param1 ni dirisha linalodaiwa, ambalo linapaswa kuwa currentWindow au mapema zaidi.
1 ...
2 if stor5[_claimWindow][addr(_claimFor)]:
3 revert with 0, 'Akaunti tayari imedai dirisha lililopewa'

Kwa hivyo sasa tunajua kuwa Ghala[5] ni safu ya madirisha na anwani, na ikiwa anwani ilidai zawadi kwa dirisha hilo.

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, 'Uthibitisho batili'
Onyesha yote

Tunajua kwamba unknown2eb4a7ab kwa kweli ni chaguo la kukokotoa merkleRoot(), kwa hivyo msimbo huu unaonekana kana kwamba unathibitisha uthibitisho wa merkleopens in a new tab. Hii inamaanisha kuwa _param4 ni uthibitisho wa merkle.

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

Hivi ndivyo mkataba unavyohamisha ETH yake kwa anwani nyingine (mkataba au inayomilikiwa nje). Inaiita kwa thamani ambayo ni kiasi cha kuhamishwa. Kwa hivyo inaonekana kama hii ni airdrop ya 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

Mistari miwili ya chini inatuambia kwamba Ghala[2] pia ni mkataba tunaopiga simu. Tukiangalia muamala wa mjenziopens in a new tab tunaona kwamba mkataba huu ni 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2opens in a new tab, mkataba wa Wrapped Ether ambao msimbo wake chanzo umepakiwa kwenye Etherscanopens in a new tab.

Kwa hivyo inaonekana kama mikataba inajaribu kutuma ETH kwa _param2. Ikiwa inaweza kufanya hivyo, vizuri. Ikiwa sivyo, inajaribu kutuma WETHopens in a new tab. Ikiwa _param2 ni akaunti inayomilikiwa nje (EOA) basi inaweza kupokea ETH kila wakati, lakini mikataba inaweza kukataa kupokea ETH. Hata hivyo, WETH ni ERC-20 na mikataba haiwezi kukataa kuikubali.

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

Mwishoni mwa chaguo la kukokotoa tunaona ingizo la logi likitengenezwa. Angalia maingizo ya kumbukumbu yaliyotokanaopens in a new tab na uchuje mada inayoanza na 0xdbd5.... Ikiwa tutabofya moja ya miamala iliyozalisha ingizo kama hiloopens in a new tab tunaona kwamba kwa hakika inaonekana kama dai - akaunti ilituma ujumbe kwa mkataba tunaoufanyia uhandisi wa nyuma, na kwa malipo ilipata ETH.

Muamala wa dai

1e7df9d3

Chaguo hili la kukokotoa linafanana sana na claim hapo juu. Pia hukagua uthibitisho wa merkle, hujaribu kuhamisha ETH hadi ya kwanza, na hutoa aina sawa ya ingizo la logi.

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, 'Uthibitisho batili'
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)
Onyesha yote

Tofauti kuu ni kwamba kigezo cha kwanza, dirisha la kutoa, halipo. Badala yake, kuna kitanzi juu ya madirisha yote ambayo yanaweza kudaiwa.

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
Onyesha yote

Kwa hivyo inaonekana kama lahaja ya claim inayodai madirisha yote.

Hitimisho

Kufikia sasa unapaswa kujua jinsi ya kuelewa mikataba ambayo msimbo wake chanzo haupatikani, kwa kutumia opcodes au (inapofanya kazi) decompiler. Kama inavyodhihirika kutokana na urefu wa makala haya, uhandisi wa nyuma wa mkataba si jambo dogo, lakini katika mfumo ambapo usalama ni muhimu ni ujuzi muhimu kuweza kuthibitisha mikataba inavyofanya kazi kama ilivyoahidiwa.

Tazama hapa kwa kazi zangu zaidiopens in a new tab.

Ukurasa ulihaririwa mwisho: 14 Februari 2026

Umesaidika na mafunzo haya?