EVM_Puzzle
author:Thomas_Xu
EVM_puzzle是一个可以很好的EVM入门阶段的一个可以上手的题库,正好最近在研究操作码的反编译,正好巩固熟练一下。
Puzzle 1
我们只需要键入输入,使程序正常跳转到JUMPDEST
即可,这道题里出现的所有操作码都只占一个字节,所以JUMPDEST
应当位于8的位置上。
我们只需要键入8即可通关。
Puzzle 2
同上一题一样,我们只需要键入输入,使程序正常跳转到JUMPDEST
即可。
CODESIZE 获取在当前环境中运行的代码的大小,并入栈。在此示例中,我们可以通过查看序列中有多少操作码来手动检查代码的大小。此题中出现操作码都是1个字节。
在这个谜题中,我们有10个操作码,这意味着代码的大小是10个字节。注意,EVM 使用十六进制数来表示字节码。所以这里是a
SUB 会把第一个堆栈元素减去第二个堆栈元素,并将结果推送到堆栈的顶部。
根据JUMP
的条件我们这里只需要让执行JUMP
时栈顶的元素为6即可。
即 0a - input = 6
所以输入4即可通关。
Puzzle 3
根据JUMP
的条件我们这里只需要让执行JUMP
时栈顶的元素为4即可。
即CALLDATASIZE
等于4即可。
这里输入0x11223344即可通关
需要注意的是CALLDATASIZE
统计的是字节数,而一个字节可以储存2个16进制数。所以我们应该输入8位十六进制数
Puzzle 4
根据JUMP
的条件我们这里只需要让执行JUMP
时栈顶的元素为0a即可。
而此题中CODESIZE
为12,XOR
为二进制异或运算,我们只需要进行一次简单的计算即可
1 | 0000 1010 //JUMP执行时栈顶所需 0a |
因此,我们只需要输入0000 0110
即 6 就可以通关
Puzzle5
根据EQ条件,我们只需要使value等于0x0100即可(256)
所以我们输入16,解决
Puzzle6
此题考查CALLDATALOAD
,由于偏移量为0,我们只需要输入0a
来使JUMP正常跳转即可
因此,输入为0x000000000000000000000000000000000000000000000000000000000000000A
Puzzle7
这个题会稍微复杂一点,我们把这些字节码分开来看。
part1
此时的栈状态为空
1 | 00 36 CALLDATASIZE |
者四个操作码为第一部分,前三个字节码均为CALLDATACOPY
的参数,此操作码会将当前环境中的输入数据复制到内存
三个堆栈输入分别是
因此我们可以分析出,当part1执行完之后,堆栈里的数据应该是完整的calldata
part2
此时的栈状态为:calldata
1 | 05 36 CALLDATASIZE |
这里三个操作码都为CREATE
的入参,CREATE
操作会为我们创建一个创建具有关联代码的新帐户
它的堆栈输入分别是:
堆栈输出:
address
:已部署合约的地址,如果部署失败,则为 0。
有关于create创建合约地址的更多信息可以看这篇文章,写的比较明白。
当执行 theopcode 时,只有 theopcode 返回的代码才是将来调用已部署合约时将执行的“运行时代码”。字节码的另一部分只使用一次,仅用于该部分。CREATE``RETURN``constructor
因此,我们可以在里面拥有我们想要的所有代码,但我们需要确保返回的代码(运行时代码)只有 1 条指令,因此将返回 1(字节)。calldata``EXTCODESIZE
让我们看看RETURN操作码是如何工作的:它从堆栈中弹出 2 个值以将它们用作以下操作的输入:
- 从开始读取的位置开始的内存偏移
- 要读取和返回的内存大小(以字节为单位)
因此,无论内存中有什么,我们都只想返回 1 条 1 字节的指令。我们的目标是执行。RETURN(offset=0, size=1)
用字节码翻译是。600060005360016000F3
因此,如果我们传递拼图的调用数据,它将使用该调用数据来创建和部署一个新合约,该合约的运行时代码仅为:theopcode!600160005360016000F3``00``STOP
其实只要让RETURN为一个字节就行,也就是以60016000F3结尾的字节码
最终输入0x60ff60005360016000F3通关
Puzzle 8
这个题的操作码也比较多,我们仍然把他们拆分着看
part1
1 | 00 36 CALLDATASIZE |
这一部分和上一题一样,返回栈顶的是calldata的值
part2
1 | 05 36 CALLDATASIZE |
这里也是一样,创建一个合约
part3
1 | 0B 6000 PUSH1 00 |
这里调用了之前创建出来的合约
等价于:CALL(gas=ALL_THE_GAS_AVAILABLE, address=ADDRESS_FROM_CREATE, value: 0, argsOffset=0, argsSize=0, retOffset=0, retSize=0)
在这里,我们只是调用部署的合约,没有任何 calldata 参数,也没有从返回值中读取任何内容。
执行堆栈后将只有一个值。如果已还原,则为 0,否则为 1。CALL
part4
1 | 14 6000 PUSH1 00 |
这里是通关条件,很显然,我们需要使Call调用的入栈值为00
因此我们只需要构造一个这样的合约:
1 | // store in memory the REVERT opcode as the only “code” of the contract |
字节码为:600160005360016000F3
那么这就将成为我们calldata的传入内容
true
...
...
This is copyright.