以太坊智能合约 OPCODE 逆向之调试器篇
时间:2018/09/04
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栈类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
<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内存类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
<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
内存相关设置的指令分为三类
- MSTORE, 储存0x20字节长度的数据到内存中
- MSTORE8, 储存1字节长度的数据到内存中
- CALLDATACOPY(或者其他类似指令),储存指定字节长度的数据到内存中
所以对应的设置了3个不同的储存数据到内存中的函数。获取内存数据的类似。
STORAGE
EVM的STORAGE的数据结构和计算机的磁盘储存结构相差就很大了,STORAGE是用来储存全局变量的,全局变量的数据结构我在上一篇文章中分析过,所以在用Python实现中,我把STORAGE定义为了字典,相关代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<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属于系统操作指令)
设计可扩展的解释器
首先,设计一个字节和指令的映射表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
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) |
然后就是设计一个解释器类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<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,可以直接使用
1 2 |
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.
把日志信息添加到交易的回执单中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
> 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漏洞等进行攻击和入侵,给区块链项目的管理运营团队及用户造成巨大的经济损失。知道创宇十余年安全经验,凭借多重防护+云端大数据技术,为区块链应用提供专属安全解决方案。
欢迎扫码咨询:
本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/693/