透過 EVM puzzles 學習 EVM 的基礎

Peter Lai
Taipei Ethereum Meetup
6 min readSep 5, 2022
圖片作者:https://unsplash.com/@xps

簡介

最近熊市大家都忙著開發跟學習,剛好想了解 EVM 於是就把先前一直想玩的 EVM puzzles 翻出來,因為題目解完了,這篇文章簡述 EVM 和紀錄解某些題目時的筆記。

網路上還有很多以太坊相關的遊戲,像是 vulnerable defi 或是 ctf,有興趣也可以去找來玩。

EVM

EVM (Ethereum Virtual Machine) 是一個基於 stack 的 256 bits 虛擬機,指令會從 stack 上取變數,並將結果存入 stack 裡。大部分的指令都是佔據 1 byte,只有 PUSH 指令因為會把值放進 stack,這會改變 stack 其他指令的位置,例如 PUSH1 ff 就會增加兩個位移。

每個指令 (opcode) 介於 0 ~ 255 (00~ff) 之間,以太坊智能合約其實就是一連串的指令組成,當 EVM 執行智能合約相關的交易時,會依序讀取每個指令並執行,如果指令無法被順利執行就會 revert。

執行環境

context

EVM 執行智能合約交易時會建立一個 context。

calldata

區塊鏈交易送過來的 data,可以用這些指令讀取 CALLDATALOAD CALLDATASIZECALLDATACOPY

returndata

智能合約呼叫的回傳值,可以用 RETURNREVERT 指令去設置,可以透過 RETURNDATASIZERETURNDATACOPY 讀取。

指令

這邊介紹幾個有用到的指令

0x14 EQ a b

如果 a == b 回傳 1,其他則回傳 0。

0x34 CALLVALUE

取得目前 wei 的數量。

0x35 CALLDATALOAD i

取得從第 i 個 byte 後 32 bytes 的 calldata,若是不滿 32 為後面會補 0。假設 calldata 是 0x12345678,則 CALLDATALOAD 1 會讀取 345678 然後補 0,並放在 stack top 的位置(3456780000000000000000000000000000000000000000000000000000000000)。

0x36 CALLDATASIZE

取得 calldata size。

0x37 CALLDATACOPY destOffset offset size

從第 offset 的位置複製長度為 size 的 calldata,並從 destOffset 寫進 memory。

0x3B EXTCODESIZE address

取得地址的 code size。

0x52 MSTORE offset value

將 32 bytes 的 value 從第 offset 的 bytes 後寫進 memory。

0x56 JUMP counter

將程式跳過幾個 counter offset 執行,跳到的地方必須是 0x5B JUMPDEST。

0x57 JUMPI counter b

如果 b 不是 0 則將程式跳過幾個 counter offset 執行,跳到的地方必須是。 0x5B JUMPDEST

0x60 PUSH1 value

把 value 放在 stack top 的位置。

0x80 DUP1 value

複製一個 value 並放在 stack。

0xF0 CREATE value offset size

建立(部署)新合約,會開啟子 context 並執行初始化 code,執行完後會繼續 context。

如果部署成功,新地址的 code 會是執行初始化 code 的回傳結果。

合約地址的計算公式:

address = keccak256(rlp([sender_address,sender_nonce]))[12:]

value

發送給合約的 value。

offset|size

將 memory 裡第 offset 長度為 size 的資料作為初始化 code 傳給 EVM 執行。

0xF3 RETURN offset size

結束目前的 context,並回傳 memory 裡第 offset 長度為 size 的資料。

No 1.

要送哪個 value 才能順利執行這段 bytecode 0x3456FDFDFDFDFDFD5B00

No 1. bytecode 解析

從這段 bytecode 可以看到我們的 CALLVALUE 會是 JUMP 的 counter 參數,所以帶的 CALLVALUE 會是 JUMPDEST 的位置,也就是 8。

No 6.

要送哪個 data 才能順利執行這段 bytecode 0x60003556FDFDFDFDFDFD5B00

No 6. bytecode 解析

從這段 bytecode 可以看到要順利執行的話得跳到 0A 的位置,我們可以看到 CALLDATALOAD 會是從 0 的位置讀取 32 bytes,因為 CALLDATALOAD 會有不滿 32 bytes 自動補 0 的特性,如果帶 0x0A 會變成 0A00000000000000000000000000000000000000000000000000000000000000,這樣會跳出界,所以我們要帶 0x000000000000000000000000000000000000000000000000000000000000000A。

No 7.

要送哪個 data 才能順利執行這段 bytecode 0x36600080373660006000F03B600114601357FD5B00

No 7. bytecode 解析 1
No 7. bytecode 解析 2

從這段 bytecode 可以看到要順利執行的話得跳到 13 的位置,13 已經被 PUSH1 指令放進去了,那因為 JUMPI 的特性,我們只要確定 EQ 的條件達成就行了,也就是上面部署合約的 codesize 等於 1。

要怎麼部署一個 codesize 等於 1 的合約呢?

其實我們只要能夠 RETURN 個 byte 就行了,而 RETURN 其實是回傳 memory 裡面的資料,所以我們需要先用 MSTORE 將資料讀進 memory。

size 1 的合約 bytecode

於是回傳一個 byte 的合約就完成了 (0x60016000526001601fF3)!

參考

EVM codes

EVM puzzles

--

--