以太坊智能合约OPCODE逆向之调试器篇
上一篇《以太坊智能合约OPCODE逆向之理论基础篇》,对智能合约的OPCODE的基础数据结构进行了研究分析,本篇将继续深入研究OPCODE,编写一个智能合约的调试器。
Remix调试器
Remix带有一个非常强大的Debugger
,当我的调试器写到一半的时候,才发现了Remix自带调试器的强大之处,本文首先,对Remix的调试器进行介绍。
能调试的范围:
- 在Remix上进行每一个操作(创建合约/调用合约/获取变量值)时,在执行成功后,都能在下方的控制界面点击
DEBUG
按钮进行调试
- Debugger能对任意交易进行调试,只需要在调试窗口输入对应交易地址
- 能对公链,测试链,私链上的任意交易进行调试
点击Environment
可以对区块链环境进行设置,选择Injected Web3
,环境取决去浏览器安装的插件
比如我,使用的浏览器是Chrome
,安装的插件是MetaMask
通过MetaMask
插件,我能选择环境为公链或者是测试链,或者是私链
当Environment
设置为Web3 Provider
可以自行添加以太坊区块链的RPC节点,一般是用于设置环境为私链
- 在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 | class STACK(Base): |
和计算机的栈比较,我觉得EVM的栈结构更像Python的List结构
计算机的栈是一个地址储存一个字节的数据,取值可以精确到一个字节,而EVM的栈是分块储存,每次PUSH占用一块,每次POP取出一块,每块最大能储存32字节的数据,也就是2^256-1
,所以上述代码中,对每一个存入栈中的数据进行取余计算,保证栈中的数据小于2^256-1
MEM
EVM的内存的数据结构几乎和计算机内存的一样,一个地址储存一字节的数据。在EVM中,因为栈的结构,每块储存的数据最大为256bits
,所以当OPCODE指令需要的参数长度可以大于256bits
时,将会使用到内存
如下所示,是我使用Python
实现的MEM内存类:
1 | class MEM(Base): |
使用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 | class STORAGE(Base): |
因为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指