Hackit

Posted on Jan 08, 2022Read on Mirror.xyz

以太坊智能合约逆向分析与实战:(1)以太坊虚拟机原理简析

如果你有二进制逆向的经验,那么以太坊智能合约的逆向是比较容易上手的。但首先我们要知道以太坊智能合约是如何运行的:

以太坊虚拟机(EVM)是一种基于栈的、准图灵完备(quasi-Turing complete)的虚拟机。听起来云里雾里,hum?好吧,EVM是智能合约的执行时环境,它是完全孤立的沙盒:运行在EVM中的代码无法访问网络、文件系统或其他进程。

evm的交易可以看作是从一个帐户发送到另一个帐户的消息(这里的账户,可能是相同的或特殊的零帐户,请参阅下文)。它能包含一个二进制数据(合约负载)和以太币。

如果目标账户含有代码,此代码会被执行,并以 payload 作为入参。

如果目标账户是零账户(账户地址为 0 ),此交易将创建一个 新合约 。 如前文所述,合约的地址不是零地址,而是通过合约创建者的地址和从该地址发出过的交易数量计算得到的(所谓的“nonce”)。 这个用来创建合约的交易的 payload 会被转换为 EVM 字节码并执行。执行的输出将作为合约代码被永久存储。这意味着,为创建一个合约,你不需要发送实际的合约代码,而是发送能够产生合约代码的代码

注解

在合约创建的过程中,它的代码还是空的。所以直到构造函数执行结束,你都不应该在其中调用合约自己函数。

需要注意的是:Solidity这样的高级语言是无法在EVM中被直接执行的,代码会被编译成更加接近机器的低级语言—操作码(opcode)来执行。我们的目的,就是把操作码尽可能地还原成高级语言,以分析其逻辑。

EVM与操作码(Opcode)

EVM是基于栈的虚拟机,为了方便计算,EVM定义了以32个字节为一个词(word)进行操作,栈中最多存储1024个词。

为了方便计算,EVM定义了以32个字节为一个词(word)进行操作,栈中最多存储1024个词。

在执行上图的加法运算时,EVM的实际流程是:

  1. PUSH b, 将b推入栈

  2. PUSH a, 将a推入栈

  3. ADD, 栈顶两列相加

上述的PUSH,ADD称为操作码(opcode),是虚拟机可以直接执行的指令,目前EVM定义了大约140个操作码,几乎可以完成所有的计算(图灵完备)。

操作码可以简单地分为以下几类:

§ 栈操作码(POP, PUSH, DUP, SWAP)

§ 算术/比较/位运算操作码(ADD, SUB, GT, LT, AND, OR)

§ 环境操作码(CALLER, CALLVALUE, NUMBER)

§ 内存操作码(MLOAD, MSTORE)

§ 硬盘操作码(SLOAD, SSTORE)

§ 终止操作码(STOP, RETURN, REVERT, INVALID. SELEFDESTRUCT)

字节码(Bytecode)

为了方便存储操作码,EVM采用字节码(Bytecode)来对操作码进行标记。一个字节等于两位16进制数,因此最多存储256个操作码。

EVM字节码-操作码参照图

EVM执行时,字节码将按照字节(即每两位16进制数)划分执行。字节0x60-0x7f(PUSH1 – PUSH32)是特殊的字节,该字节后的字节数将作为输入压入栈中。

下面来看一组字节码的实际执行步骤:0x6002600101

  1. 0x60是特殊的字节码(PUSH1),它取之后的一个字节0x02作为输入。

因此0x6002在EVM中是PUSH1 0x02

  1. 同理0x60是特殊的字节码(PUSH1),它取之后的一个字节0x01作为输入。

因此0x6001在EVM中是PUSH1 0x01

  1. 0x01对应直接码ADD

因此,上述的字节码在EVM中是对2和1进行求和运算,它先后将0x02和0x01压入栈中,再将栈顶的2个数字弹出栈进行相加,最后将结果0x03压入栈中。

小结:

1、创建合约就是往空地址发一笔带合约数据的交易,然后这些数据就固定在了这个地址,变成了一个合约。

2、EVM执行时,字节码是以字节为单位(即每两位16进制数)读取并执行的。以0x6002600101 为例,执行顺序为 60 02 → 60 01 → 01

本文参考自:

https://zhuanlan.zhihu.com/p/90487072

Recommended Reading