2

EVM 自产生程序(quine)

 1 year ago
source link: https://liaoph.com/evm-quine/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

EVM 自产生程序(quine)

Posted on August 27, 2022

这是来自 paradigm CTF 的一道题,题目要求是给出一段 evm bytecode,要求这段 bytecode 在 deploy 之后被调用的输出,正好和它原始的 bytecode 一摸一样。同时题目还限制了这段 bytecode 不可以使用 CODECOPY, CALL, DELEGATECALL 等指令。

通常这类程序被称为 quine program,在 wiki 中有很多其他语言的实现,我们只需要把其他语言的思路运用到 evm opcode 中即可。

首先,我们需要定义一段常量 C,然后将 C PUSH 到栈上,接下来我们需要运行一段 OPCODE,这些 OPCODE 组合起来正好等于我们刚才所定义的常量 C

C 需要做的事情就是将 PUSHN 指令保存到内存中,并将 C 自身复制两份保存在内存中,最后将内存中的内容返回。

这样我们最终返回的内容就是 PUSHN + C + C,这里的 PUSHN 表示一个 PUSH 指令,因为我们还不知道 C 的长度,所以用 PUSHN 代替。

接下来我们开始拼凑 C 的内容。

C 的指令运行之前,我们将 C PUSH 到了栈上,所以在 C 中我们首先需要将 PUSHN 这个指令也保存到内存中,我们先将它 PUSH 到栈上:

PUSH1 X

因为我们还不知道 C 的长度,这里先使用未知数 X 表示 PUSHN 指令的 OPCODE。执行完成后栈中内容应为:

0: X
1: C

接下来我们可以把栈中的内容保存到内存中,但是由于栈中只有一个字节的内容,为了更高效一些我们可以晚一些和其他内容一起来保存。

因为我们需要在内存中保存两次 C,所以需要再复制一份 C,然后我们把 XC 连接到一起,并将 XC 一起保存到内存中,这段 OPCODE 为:

PUSH1 Y
SHL                   // 将 X 向左平移 Y
DUP2                  // 复制一份 C
OR                    // 将 X 和 C 连接起来
MSIZE                 // 获取内存长度,作为保存新内容的起始地址
MSTORE                // 将 XC 保存到内存中

这里我们再次引入了一个未知数 Y,即 X 需要向左平移的距离。这段 OPCODE 执行结束后,栈中内容为:

0: C

内存中内容为

0~32:        000...0XC

接下来我们将 C 再次保存到内存中,这样我们在内存中的内容就成为了 XCC,最后我们将内存中的这段内容返回即可:

PUSH1 Z
SHL                    // 需要将 C 向左平移 Z,消除栈中填充的前缀 0
MSIZE                  // 获取内存长度,作为保存新内容的起始地址
MSTORE                 // 将 C 保存到内存中
PUSH1 A
PUSH1 B
RETURN                 // 将内存中的内容返回,offset 为 A,lens 为 B

上面的指令执行完成后,内存中内容应为:

0~32:        000...0XC
32~64:       C000...00

最终我们将内存中的 XCC 返回即可。

将上面所有内容组合起来,即为 C 所包含的 OPCODEs:

PUSH1 X
PUSH1 Y
SHL
DUP2
OR
MSIZE
MSTORE
PUSH1 Z
SHL
MSIZE
MSTORE
PUSH1 A
PUSH1 B
RETURN

将其转为 bytecode:

60 X 60 Y 1B 81 17 59 52 60 Z 1B 59 52 60 A 60 B F3

这样我们知道了 C 的长度为 19 字节。那么 X 就应该是 PUSH19 的 OPCODE,即 X=72

Y 表示我们要将 X 向左平移的距离,平移后使用 OR 将其连接到 C 的前端,因此 Y=19*8=98(bits, in hex)。

Z 表示将 C 左移,消除栈中填充的前缀 0(栈中的内容默认是右对齐的)。因此 Z=(32-19)*8=68 (bits, in hex).

A 表示内存需要返回内容的起始地址,B 表示返回内容总长度,可以算出 A=27, B=0C.

所以最后 bytecode 的内容 XCC 即为:

72(607260981B8117595260681B59526027600CF3)(607260981B8117595260681B59526027600CF3)

这里我用括号括起的两段即为 C 的内容。

我们可以使用 evmcodes playgroud 来验证这段 bytecode 运行的结果:

playgroud-result

可以看到最后输出的结果和输入的 bytecode 一致,题目完成。



About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK