以太坊智能合約 OPCODE 逆向之調試器篇

作者:Hcamael@知道創宇404區塊鏈安全研究團隊
時間:2018/09/04
上一篇 《以太坊智能合約 OPCODE 逆向之理論基礎篇》 ,對智能合約的OPCODE的基礎數據結構進行了研究分析,本篇將繼續深入研究OPCODE,編寫一個智能合約的調試器。

Remix調試器

Remix帶有一個非常強大的 Debugger ,當我的調試器寫到一半的時候,才發現了Remix自帶調試器的強大之處,本文首先,對Remix的調試器進行介紹。

能調試的範圍:

1. 在Remix上進行每一個操作(創建合約/調用合約/獲取變量值)時,在執行成功後,都能在下方的控制界面點擊 DEBUG 按鈕進行調試

2. Debugger能對任意交易進行調試,只需要在調試窗口輸入對應交易地址

3. 能對公鏈,測試鏈,私鏈上的任意交易進行調試

點擊 Environment 可以對區塊鏈環境進行設置,選擇 Injected Web3 ,環境取決去瀏覽器安裝的插件

比如我,使用的瀏覽器是 Chrome ,安裝的插件是 MetaMask

通過 MetaMask 插件,我能選擇環境爲公鏈或者是測試鏈,或者是私鏈

Environment 設置爲 Web3 Provider 可以自行添加以太坊區塊鏈的RPC節點,一般是用於設置環境爲私鏈

4. 在JavaScript的EVM環境中進行調試

見3中的圖,把 Environment 設置爲 JavaScript VM 則表示使用本地虛擬環境進行調試測試

在調試的過程中能做什麼?

Remix的調試器只提供了詳細的數據查看功能,沒法在特定的指令對 STACK/MEM/STORAGE 進行操作

在瞭解清楚Remix的調試器的功能後,感覺我進行了一半的工作好像是在重複造輪子。

之後仔細思考了我寫調試器的初衷,今天的WCTF有一道以太坊智能合約的題目,因爲第一次認真的逆向EVM的OPCODE,不熟練,一個下午還差一個函數沒有逆向出來,然後比賽結束了,感覺有點遺憾,如果當時能動態調試,可能逆向的速度能更快。

Remix的調試器只能對已經發生的行爲(交易)進行調試,所以並不能滿足我打CTF的需求,所以對於我寫的調試器,我轉換了一下定位:調試沒有源碼,只有OPCODE的智能合約的邏輯,或者可以稱爲離線調試。

調試器的編寫

智能合約調試器的編寫,我認爲最核心的部分是實現一個OPCODE解釋器,或者說是自己實現一個EVM。

實現OPCODE解釋器又分爲兩部分,1. 設計和實現數據儲存器(把STACK/MEM/STORAGE統稱爲數據儲存器),2. 解析OPCODE指令

數據儲存器

STACK

根據OPCODE指令的情況,EVM的棧和計算機的棧數據結構是一個樣的,先入先出,都有 PUSH POP 操作。不過EVM的棧還多了 SWAP DUP 操作,棧交換和棧複製,如下所示,是我使用 Python 實現的EVM棧類:

<span class="token keyword">class</span> STACK<span class="token punctuation">(</span>Base<span class="token punctuation">)</span><span class="token punctuation">:</span>
    <span class="token string">""</span>"
    evm stack
    <span class="token string">""</span>"
    stack<span class="token punctuation">:</span> <span class="token punctuation">[</span>int<span class="token punctuation">]</span>
    max_value<span class="token punctuation">:</span> int
    <span class="token keyword">def</span> __init__<span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span>
        self<span class="token punctuation">.</span>stack <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span>
        self<span class="token punctuation">.</span>max_value <span class="token operator">=</span> <span class="token number">2</span><span class="token operator">*</span><span class="token operator">*</span><span class="token number">256</span>
    <span class="token keyword">def</span> push<span class="token punctuation">(</span>self<span class="token punctuation">,</span> data<span class="token punctuation">:</span> int<span class="token punctuation">)</span><span class="token punctuation">:</span>
        <span class="token string">""</span>"
        OPCODE<span class="token punctuation">:</span> PUSH
        <span class="token string">""</span>"
        self<span class="token punctuation">.</span>stack<span class="token punctuation">.</span>append<span class="token punctuation">(</span>data <span class="token operator">%</span> self<span class="token punctuation">.</span>max_value<span class="token punctuation">)</span>
    <span class="token keyword">def</span> pop<span class="token punctuation">(</span>self<span class="token punctuation">)</span> <span class="token operator">-</span>> <span class="token punctuation">(</span>int<span class="token punctuation">)</span><span class="token punctuation">:</span>
        <span class="token string">""</span>"
        OPCODE POP
        <span class="token string">""</span>"
        <span class="token keyword">return</span> self<span class="token punctuation">.</span>stack<span class="token punctuation">.</span>pop<span class="token punctuation">(</span><span class="token punctuation">)</span>
    @Base<span class="token punctuation">.</span>stackcheck
    <span class="token keyword">def</span> swap<span class="token punctuation">(</span>self<span class="token punctuation">,</span> n<span class="token punctuation">)</span><span class="token punctuation">:</span>
        <span class="token string">""</span>"
        OPCODE<span class="token punctuation">:</span> SWAPn<span class="token punctuation">(</span><span class="token number">1</span><span class="token operator">-</span><span class="token number">16</span><span class="token punctuation">)</span>
        <span class="token string">""</span>"
        tmp <span class="token operator">=</span> self<span class="token punctuation">.</span>stack<span class="token punctuation">[</span><span class="token operator">-</span>n<span class="token number">-1</span><span class="token punctuation">]</span>
        self<span class="token punctuation">.</span>stack<span class="token punctuation">[</span><span class="token operator">-</span>n<span class="token number">-1</span><span class="token punctuation">]</span> <span class="token operator">=</span> self<span class="token punctuation">.</span>stack<span class="token punctuation">[</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">]</span>
        self<span class="token punctuation">.</span>stack<span class="token punctuation">[</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">]</span> <span class="token operator">=</span> tmp
    @Base<span class="token punctuation">.</span>stackcheck
    <span class="token keyword">def</span> dup<span class="token punctuation">(</span>self<span class="token punctuation">,</span> n<span class="token punctuation">)</span><span class="token punctuation">:</span>
        <span class="token string">""</span>"
        OPCODE<span class="token punctuation">:</span> DUPn<span class="token punctuation">(</span><span class="token number">1</span><span class="token operator">-</span><span class="token number">16</span><span class="token punctuation">)</span>
        <span class="token string">""</span>"
        self<span class="token punctuation">.</span>stack<span class="token punctuation">.</span>append<span class="token punctuation">(</span>self<span class="token punctuation">.</span>stack<span class="token punctuation">[</span><span class="token operator">-</span>n<span class="token punctuation">]</span><span class="token punctuation">)</span>

和計算機的棧比較,我覺得EVM的棧結構更像Python的List結構

計算機的棧是一個地址儲存一個字節的數據,取值可以精確到一個字節,而EVM的棧是分塊儲存,每次PUSH佔用一塊,每次POP取出一塊,每塊最大能儲存32字節的數據,也就是 2^256-1 ,所以上述代碼中,對每一個存入棧中的數據進行取餘計算,保證棧中的數據小於 2^256-1

MEM

EVM的內存的數據結構幾乎和計算機內存的一樣,一個地址儲存一字節的數據。在EVM中,因爲棧的結構,每塊儲存的數據最大爲 256bits ,所以當OPCODE指令需要的參數長度可以大於 256bits 時,將會使用到內存

如下所示,是我使用 Python 實現的MEM內存類:

<span class="token keyword">class</span> MEM<span class="token punctuation">(</span>Base<span class="token punctuation">)</span><span class="token punctuation">:</span>
    <span class="token string">""</span>"
    EVM memory
    <span class="token string">""</span>"
    mem<span class="token punctuation">:</span> bytearray
    max_value<span class="token punctuation">:</span> int
    length<span class="token punctuation">:</span> int
    <span class="token keyword">def</span> __init__<span class="token punctuation">(</span>self<span class="token punctuation">)</span><span class="token punctuation">:</span>
        self<span class="token punctuation">.</span>mem <span class="token operator">=</span> bytearray<span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">)</span>
        self<span class="token punctuation">.</span>max_value <span class="token operator">=</span> <span class="token number">2</span><span class="token operator">*</span><span class="token operator">*</span><span class="token number">256</span>
        self<span class="token punctuation">.</span>length <span class="token operator">=</span> <span class="token number">0</span>
        self<span class="token punctuation">.</span>extend<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span>
    @Base<span class="token punctuation">.</span>memcheck
    <span class="token keyword">def</span> set<span class="token punctuation">(</span>self<span class="token punctuation">,</span> key<span class="token punctuation">:</span> int<span class="token punctuation">,</span> value<span class="token punctuation">:</span> int<span class="token punctuation">)</span><span class="token punctuation">:</span>
        <span class="token string">""</span>"
        OPCODE<span class="token punctuation">:</span> MSTORE
        <span class="token string">""</span>"
        value <span class="token operator">%</span><span class="token operator">=</span> self<span class="token punctuation">.</span>max
        self<span class="token punctuation">.</span>mem<span class="token punctuation">[</span>key<span class="token punctuation">:</span> key<span class="token operator">+</span><span class="token number">0x20</span><span class="token punctuation">]</span> <span class="token operator">=</span> value<span class="token punctuation">.</span>to_bytes<span class="token punctuation">(</span><span class="token number">0x20</span><span class="token punctuation">,</span> <span class="token string">"big"</span><span class="token punctuation">)</span>
        self<span class="token punctuation">.</span>length <span class="token operator">+</span><span class="token operator">=</span> <span class="token number">0x20</span>
    @Base<span class="token punctuation">.</span>memcheck
    <span class="token keyword">def</span> set_byte<span class="token punctuation">(</span>self<span class="token punctuation">,</span> key<span class="token punctuation">:</span> int<span class="token punctuation">,</span> value<span class="token punctuation">:</span> int<span class="token punctuation">)</span><span class="token punctuation">:</span>
        <span class="token string">""</span>"
        OPCODE<span class="token punctuation">:</span> MSTORE8
        <span class="token string">""</span>"
        self<span class="token punctuation">.</span>mem<span class="token punctuation">[</span>key<span class="token punctuation">]</span> <span class="token operator">=</span> value  <span class="token operator">&</span>amp<span class="token punctuation">;</span> <span class="token number">0xff</span>
        self<span class="token punctuation">.</span>length <span class="token operator">+</span><span class="token operator">=</span> length
    @Base<span class="token punctuation">.</span>memcheck
    <span class="token keyword">def</span> set_length<span class="token punctuation">(</span>self<span class="token punctuation">,</span> key<span class="token punctuation">:</span> int<span class="token punctuation">,</span> value<span class="token punctuation">:</span> int<span class="token punctuation">,</span> length<span class="token punctuation">:</span> int<span class="token punctuation">)</span><span class="token punctuation">:</span>
        <span class="token string">""</span>"
        OPCODE<span class="token punctuation">:</span> XXXXCOPY
        <span class="token string">""</span>"
        value <span class="token operator">%</span><span class="token operator">=</span> <span class="token punctuation">(</span><span class="token number">2</span><span class="token operator">*</span><span class="token operator">*</span><span class="token punctuation">(</span><span class="token number">8</span><span class="token operator">*</span>length<span class="token punctuation">)</span><span class="token punctuation">)</span>
        data <span class="token operator">=</span> value<span class="token punctuation">.</span>to_bytes<span class="token punctuation">(</span>length<span class="token punctuation">,</span> <span class="token string">"big"</span><span class="token punctuation">)</span>
        self<span class="token punctuation">.</span>mem<span class="token punctuation">[</span>key<span class="token punctuation">:</span> key<span class="token operator">+</span>length<span class="token punctuation">]</span> <span class="token operator">=</span> data
        self<span class="token punctuation">.</span>length <span class="token operator">+</span><span class="token operator">=</span> length
    @Base<span class="token punctuation">.</span>memcheck
    <span class="token keyword">def</span> get<span class="token punctuation">(</span>self<span class="token punctuation">,</span> key<span class="token punctuation">:</span> int<span class="token punctuation">)</span> <span class="token operator">-</span>> <span class="token punctuation">(</span>int<span class="token punctuation">)</span><span class="token punctuation">:</span>
        <span class="token string">""</span>"
        OPCODE<span class="token punctuation">:</span> MLOAD
        <span class="token keyword">return</span> uint256
        <span class="token string">""</span>"
        <span class="token keyword">return</span> int<span class="token punctuation">.</span>from_bytes<span class="token punctuation">(</span>self<span class="token punctuation">.</span>mem<span class="token punctuation">[</span>key<span class="token punctuation">:</span> key<span class="token operator">+</span><span class="token number">0x20</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token string">"big"</span><span class="token punctuation">,</span> signed<span class="token operator">=</span><span class="token boolean">False</span><span class="token punctuation">)</span>
    @Base<span class="token punctuation">.</span>memcheck
    <span class="token keyword">def</span> get_bytearray<span class="token punctuation">(</span>self<span class="token punctuation">,</span> key<span class="token punctuation">:</span> int<span class="token punctuation">)</span> <span class="token operator">-</span>> <span class="token punctuation">(</span>bytearray<span class="token punctuation">)</span><span class="token punctuation">:</span>
        <span class="token string">""</span>"
        OPCODE<span class="token punctuation">:</span> MLOAD
        <span class="token keyword">return</span> <span class="token number">32</span> byte array
        <span class="token string">""</span>"
        <span class="token keyword">return</span> self<span class="token punctuation">.</span>mem<span class="token punctuation">[</span>key<span class="token punctuation">:</span> key<span class="token operator">+</span><span class="token number">0x20</span><span class="token punctuation">]</span>
    @Base<span class="token punctuation">.</span>memcheck
    <span class="token keyword">def</span> get_bytes<span class="token punctuation">(</span>self<span class="token punctuation">,</span> key<span class="token punctuation">:</span> int<span class="token punctuation">)</span> <span class="token operator">-</span>> <span class="token punctuation">(</span>bytes<span class="token punctuation">)</span><span class="token punctuation">:</span>
        <span class="token string">""</span>"
        OPCODE<span class="token punctuation">:</span> MLOAD
        <span class="token keyword">return</span> <span class="token number">32</span> bytes
        <span class="token string">""</span>"
        <span class="token keyword">return</span> bytes<span class="token punctuation">(</span>self<span class="token punctuation">.</span>mem<span class="token punctuation">[</span>key<span class="token punctuation">:</span> key<span class="token operator">+</span><span class="token number">0x20</span><span class="token punctuation">]</span><span class="token punctuation">)</span>
    @Base<span class="token punctuation">.</span>memcheck
    <span class="token keyword">def</span> get_length<span class="token punctuation">(</span>self<span class="token punctuation">,</span> key<span class="token punctuation">:</span>int <span class="token punctuation">,</span> length<span class="token punctuation">:</span> int<span class="token punctuation">)</span> <span class="token operator">-</span>> <span class="token punctuation">(</span>int<span class="token punctuation">)</span><span class="token punctuation">:</span>
        <span class="token string">""</span>"
        <span class="token keyword">return</span> mem int value
        <span class="token string">""</span>"
        <span class="token keyword">return</span> int<span class="token punctuation">.</span>from_bytes<span class="token punctuation">(</span>self<span class="token punctuation">.</span>mem<span class="token punctuation">[</span>key<span class="token punctuation">:</span> key<span class="token operator">+</span>length<span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token string">"big"</span><span class="token punctuation">,</span> signed<span class="token operator">=</span><span class="token boolean">False</span><span class="token punctuation">)</span>
    @Base<span class="token punctuation">.</span>memcheck
    <span class="token keyword">def</span> get_length_bytes<span class="token punctuation">(</span>self<span class="token punctuation">,</span> key<span class="token punctuation">:</span>int <span class="token punctuation">,</span> length<span class="token punctuation">:</span> int<span class="token punctuation">)</span> <span class="token operator">-</span>> <span class="token punctuation">(</span>bytes<span class="token punctuation">)</span><span class="token punctuation">:</span>
        <span class="token string">""</span>"
        <span class="token keyword">return</span> mem bytes value
        <span class="token string">""</span>"
        <span class="token keyword">return</span> bytes<span class="token punctuation">(</span>self<span class="token punctuation">.</span>mem<span class="token punctuation">[</span>key<span class="token punctuation">:</span> key<span class="token operator">+</span>length<span class="token punctuation">]</span><span class="token punctuation">)</span>
    @Base<span class="token punctuation">.</span>memcheck
    <span class="token keyword">def</span> get_length_bytearray<span class="token punctuation">(</span>self<span class="token punctuation">,</span> key<span class="token punctuation">:</span>int <span class="token punctuation">,</span> length<span class="token punctuation">:</span> int<span class="token punctuation">)</span> <span class="token operator">-</span>> <span class="token punctuation">(</span>bytearray<span class="token punctuation">)</span><span class="token punctuation">:</span>
        <span class="token string">""</span>"
        <span class="token keyword">return</span> mem int value
        <span class="token string">""</span>"
        <span class="token keyword">return</span> self<span class="token punctuation">.</span>mem<span class="token punctuation">[</span>key<span class="token punctuation">:</span> key<span class="token operator">+</span>length<span class="token punctuation">]</span>
    <span class="token keyword">def</span> extend<span class="token punctuation">(</span>self<span class="token punctuation">,</span> num<span class="token punctuation">:</span> int<span class="token punctuation">)</span><span class="token punctuation">:</span>
        <span class="token string">""</span>"
        extend mem space
        <span class="token string">""</span>"
        self<span class="token punctuation">.</span>mem<span class="token punctuation">.</span>extend<span class="token punctuation">(</span>bytearray<span class="token punctuation">(</span><span class="token number">256</span><span class="token operator">*</span>num<span class="token punctuation">)</span><span class="token punctuation">)</span>

使用python3中的 bytearray 類型作爲MEM的結構,默認初始化256B的內存空間,因爲有一個OPCODE是 MSIZE :

Get the size of active memory in bytes.

所以每次設置內存值時,都要計算 active memory 的size

內存相關設置的指令分爲三類

  1. MSTORE, 儲存0x20字節長度的數據到內存中
  2. MSTORE8, 儲存1字節長度的數據到內存中
  3. CALLDATACOPY(或者其他類似指令),儲存指定字節長度的數據到內存中

所以對應的設置了3個不同的儲存數據到內存中的函數。獲取內存數據的類似。

STORAGE

EVM的STORAGE的數據結構和計算機的磁盤儲存結構相差就很大了,STORAGE是用來儲存全局變量的,全局變量的數據結構我在上一篇文章中分析過,所以在用Python實現中,我把STORAGE定義爲了字典,相關代碼如下:

<span class="token keyword">class</span> STORAGE<span class="token punctuation">(</span>Base<span class="token punctuation">)</span><span class="token punctuation">:</span>
    <span class="token string">""</span>"
    EVM storage
    <span class="token string">""</span>"
    storage<span class="token punctuation">:</span> <span class="token punctuation">{</span>str<span class="token punctuation">:</span> int<span class="token punctuation">}</span>
    max<span class="token punctuation">:</span> int
    <span class="token keyword">def</span> __init__<span class="token punctuation">(</span>self<span class="token punctuation">,</span> data<span class="token punctuation">)</span><span class="token punctuation">:</span>
        self<span class="token punctuation">.</span>storage <span class="token operator">=</span> data
        self<span class="token punctuation">.</span>max <span class="token operator">=</span> <span class="token number">2</span><span class="token operator">*</span><span class="token operator">*</span><span class="token number">256</span>
    @Base<span class="token punctuation">.</span>storagecheck
    <span class="token keyword">def</span> set<span class="token punctuation">(</span>self<span class="token punctuation">,</span> key<span class="token punctuation">:</span> str<span class="token punctuation">,</span> value<span class="token punctuation">:</span> int<span class="token punctuation">)</span><span class="token punctuation">:</span>
        self<span class="token punctuation">.</span>storage<span class="token punctuation">[</span>key<span class="token punctuation">]</span> <span class="token operator">=</span> value <span class="token operator">%</span> self<span class="token punctuation">.</span>max
    @Base<span class="token punctuation">.</span>storagecheck
    <span class="token keyword">def</span> get<span class="token punctuation">(</span>self<span class="token punctuation">,</span> key<span class="token punctuation">:</span> str<span class="token punctuation">)</span> <span class="token operator">-</span>> <span class="token punctuation">(</span>int<span class="token punctuation">)</span><span class="token punctuation">:</span>
        <span class="token keyword">return</span> self<span class="token punctuation">.</span>storage<span class="token punctuation">[</span>key<span class="token punctuation">]</span>

因爲EVM中操作STORAGE的相關指令只有 SSTORE SLOAD ,所以使用python的dict類型作爲STORAGE的結構最爲合適

解析OPCODE指令

對於OPCODE指令的解析難度不是很大,指令只佔一個字節,所以EVM的指令最多也就256個指令( 0x00-0xff ),但是有很多都是處於 UNUSE ,所以以後智能合約增加新指令後,調試器也要進行更新,因此現在寫的代碼需要具備可擴展性。雖然解析指令的難度不大,但是仍然是個體力活,下面先來看看OPCODE的分類

OPCODE分類

在以太坊官方黃皮書中,對OPCODE進行了相應的分類:

0s: Stop and Arithmetic Operations (從0x00-0x0f的指令類型是STOP指令加上算術指令)

10s: Comparison & Bitwise Logic Operations (0x10-0x1f的指令是比較指令和比特位邏輯指令)

20s: SHA3 (目前0x20-0x2f只有一個SHA3指令)

30s: Environmental Information (0x30-0x3f是獲取環境信息的指令)

40s: Block Information (0x40-0x4f是獲取區塊信息的指令)

50s: Stack, Memory, Storage and Flow Operations (0x40-0x4f是獲取棧、內存、儲存信息的指令和流指令(跳轉指令))

60s & 70s: Push Operations (0x60-0x7f是32個PUSH指令,PUSH1-PUSH32)

80s: Duplication Operations (0x80-0x8f屬於DUP1-DUP16指令)

90s: Exchange Operations (0x90-0x9f屬於SWAP1-SWAP16指令)

a0s: Logging Operations (0xa0-0xa4屬於LOG0-LOG4指令)

f0s: System operations (0xf0-0xff屬於系統操作指令)

設計可擴展的解釋器

首先,設計一個字節和指令的映射表:

import typing

class OpCode(typing.NamedTuple):
    name: str
    removed: int            # 參數個數
    args: int               # PUSH根據該參數獲取opcode之後args字節的值作爲PUSH的參數

_OPCODES = {
    '00': OpCode(name = 'STOP', removed = 0, args = 0),
    ......
}

for i in range(96, 128):
    _OPCODES[hex(i)[2:]] = OpCode(name='PUSH' + str(i - 95), removed=0, args=i-95)
......

# 因爲編譯器優化的問題,OPCODE中會出現許多執行不到的,UNUSE的指令,爲防止解析失敗,還要對UNUSE的進行處理
for i in range(0, 256):
    if not _OPCODES.get(hex(i)[2:].zfill(2)):
            _OPCODES[hex(i)[2:].zfill(2)] = OpCode('UNUSE', 0, 0)

然後就是設計一個解釋器類:

<span class="token keyword">class</span> Interpreter<span class="token punctuation">:</span>
    <span class="token string">""</span>"
    EVM Interpreter
    <span class="token string">""</span>"
    MAX <span class="token operator">=</span> <span class="token number">2</span><span class="token operator">*</span><span class="token operator">*</span><span class="token number">256</span>
    over <span class="token operator">=</span> <span class="token number">1</span>
    store<span class="token punctuation">:</span> EVMIO
    <span class="token comment">#############
</span>    <span class="token comment">#  0s: Stop and Arithmetic Operations
</span>    <span class="token comment">#############
</span>    @staticmethod
    <span class="token keyword">def</span> STOP<span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span>
        <span class="token string">""</span>"
        OPCODE<span class="token punctuation">:</span> <span class="token number">0x00</span>
        <span class="token string">""</span>"
        Interpreter<span class="token punctuation">.</span>over <span class="token operator">=</span> <span class="token number">1</span>
        <span class="token keyword">print</span><span class="token punctuation">(</span><span class="token string">"========Program STOP========="</span><span class="token punctuation">)</span>
    @staticmethod
    <span class="token keyword">def</span> ADD<span class="token punctuation">(</span>x<span class="token punctuation">:</span>int<span class="token punctuation">,</span> y<span class="token punctuation">:</span>int<span class="token punctuation">)</span><span class="token punctuation">:</span>
        <span class="token string">""</span>"
        OPCODE<span class="token punctuation">:</span> <span class="token number">0x01</span>
        <span class="token string">""</span>"
        r <span class="token operator">=</span> <span class="token punctuation">(</span>x <span class="token operator">+</span> y<span class="token punctuation">)</span> <span class="token operator">%</span> Interpreter<span class="token punctuation">.</span>MAX
        Interpreter<span class="token punctuation">.</span>store<span class="token punctuation">.</span>stack<span class="token punctuation">.</span>push<span class="token punctuation">(</span>r<span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span><span class="token punctuation">.</span>
  • MAX變量用來控制計算的結果在256bits的範圍內
  • over變量用來標識程序是否執行結束
  • store用來訪問runtime變量: STACK, MEM, STORAGE

在這種設計模式下,當解釋響應的OPCODE,可以直接使用

args = [stack.pop() for _ in OpCode.removed]
getattr(Interpreter, OpCode.name)(*args)

特殊指令的處理思路

在OPCODE中有幾類特殊的指令:

1. 獲取區塊信息的指令,比如:

NUMBER: Get the block’s number

該指令是獲取當前交易打包進的區塊的區塊數(區塊高度),解決這個指令有幾種方案:

  • 設置默認值
  • 設置一個配置文件,在配置文件中設置該指令的返回值
  • 調試者手動利用調試器設置該值
  • 設置RPC地址,從區塊鏈中獲取該值

文章的開頭提過了對我編寫的調試器的定位問題,也正是因爲遇到該類的指令,纔去思考調試器的定位。既然已經打包進了區塊,說明是有交易地址的,既然有交易地址,那完全可以使用Remix的調試器進行調試。

所以對我編寫的調試器有了離線調試器的定位,採用上述方法中的前三個方法,優先級由高到低分別是,手動設置>配置文件設置>默認設置

2. 獲取環境信息指令,比如:

ADDRESS: Get address of currently executing account.

獲取當前合約的地址,解決方案如下:

  • 設置默認值
  • 設置一個配置文件,在配置文件中設置該指令的返回值
  • 調試者手動利用調試器設置該值

獲取環境信息的指令,因爲調試的是OPCODE,沒有源碼,不需要部署,所以是沒法通過RPC獲取到的,只能由調試者手動設置

3. 日誌指令

LOG0-LOG4: Append log record with no topics.

把日誌信息添加到交易的回執單中

> eth.getTransactionReceipt("0xe32b3751a3016e6fa5644e59cd3b5072f33f27f10242c74980409b637dbb3bdc")
{
  blockHash: "0x04b838576b0c3e44ece7279b3b709e336a58be5786a83a6cf27b4173ce317ad3",
  blockNumber: 6068600,
  contractAddress: null,
  cumulativeGasUsed: 7171992,
  from: "0x915d631d71efb2b20ad1773728f12f76eeeeee23",
  gasUsed: 81100,
  logs: [],
  logsBloom: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  status: "0x1",
  to: "0xd1ceeeefa68a6af0a5f6046132d986066c7f9426",
  transactionHash: "0xe32b3751a3016e6fa5644e59cd3b5072f33f27f10242c74980409b637dbb3bdc",
  transactionIndex: 150
}

上述就是獲取一個交易的回執單,其中有一個 logs 列表,就是用來儲存日誌信息

既然是在調試OPCODE,那麼記錄日誌的操作就是沒有必要的,因爲調試的過程中能看到儲存器/參數的情況,所以對於這類指令的操作,完全可以直接輸出,或者不做任何處理(直接pass)

4. 系統操作指令

這類指令主要是外部調用相關的指令,比如可以創建合約的 CREATE , 比如能調用其他合約的 CALL , 比如銷燬自身,並把餘額全部轉給別人的 SELFDESTRUCT

這類的指令我認爲的解決辦法只有: 調試者手動利用調試器設置該指令的返回值

調用這類函數的時候,我們完全能看到詳細的參數值,所以完全可以手動的進行創建合約,調用合約等操作

總結

在完成一個OPCODE的解釋器後,一個調試器就算完成了 3/4 , 剩下的工作就是實現自己想實現的調試器功能,比如下斷點,查看棧內存儲存數據等

下面放一個接近成品的演示gif圖:


智能合約審計服務

針對目前主流的以太坊應用,知道創宇提供專業權威的智能合約審計服務,規避因合約安全問題導致的財產損失,爲各類以太坊應用安全保駕護航。

知道創宇404智能合約安全審計團隊: https://www.scanv.com/lca/index.html

聯繫電話:(086) 136 8133 5016(沈經理,工作日:10:00-18:00)

歡迎掃碼諮詢:

區塊鏈行業安全解決方案

黑客通過DDoS攻擊、CC攻擊、系統漏洞、代碼漏洞、業務流程漏洞、API-Key漏洞等進行攻擊和入侵,給區塊鏈項目的管理運營團隊及用戶造成巨大的經濟損失。知道創宇十餘年安全經驗,憑藉多重防護+雲端大數據技術,爲區塊鏈應用提供專屬安全解決方案。

歡迎掃碼諮詢:


Paper 本文由 Seebug Paper 發佈,如需轉載請註明來源。本文地址: https://paper.seebug.org/693/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章