首页
社区
课程
招聘
EVM-Puzzles 通关笔记
2023-6-3 14:51 14350

EVM-Puzzles 通关笔记

2023-6-3 14:51
14350

evm-puzzles 通关笔记

项目简介

  • 下载链接: https://github.com/fvictorio/evm-puzzles
  • EVM 难题的集合。每个谜题都包含向合约发送成功的交易。提供了合约的字节码,您需要填写不会执行回滚的交易数据。

Puzzle 1

Untitled

 

这里我们就需要从 JUMP 去跳转到 JUMPDEST 的位置,也就是08的地方。否则就是 REVERT

 

我们来分析这个EVM字节码

  • CALLVALUE:把当前调用的wei压入栈
  • JUMP:是从栈顶取第一个值作为自己的参数来进行跳转。

这里我们要跳转到08的位置,使用https://www.evm.codes/playground 模拟一下

 

Untitled

 

这里JUMP之后就会跳转到JUMPDEST位置

 

Untitled

 

成功走入这个位置。

 

Untitled

Puzzle 2

Untitled

 

这一关和第一关很像,也是需要我们利用JUMP去跳转到JUMPDEST的位置,但是在CALLVALUE之后又有一个CODESIZE,它的意思是将当前合约的代码大小压入栈中,这里很明显会压入一个 a 。然后调用SUB去做减法。

 

我们这里是要跳转到 06 的位置,现在已经确定了: a - ? = 6 ,那么我们只需要CALLVALUE传入4 wei即可。

 

Untitled

 

减完之后就等于6,然后JUMP就会跳转成功

 

Untitled

Puzzle 3

Untitled

 

和第一关如出一辙,只是这里不是 CALLVALUE 而是 CALLDATASIZE ,它是会将CALLDATA的字节大小压入栈中

calldata 是一个编码的十六进制数字块,其中包含有关我们要调用的合约函数及其参数或数据的信息。简而言之,它包含一个“函数 id”,它是通过散列函数签名(截断为前四个字节)后跟打包参数数据生成的。

 

这里我们随意的传入一个4 bytes数据即可

 

Untitled

Puzzle 4

Untitled

 

这里用到的几个EVM ByteCode我们再来回顾一下:

1
2
3
CALLVALUE : 将msg.value的wei压入栈中
CODESIZE : 将合约的字节大小压入栈中
XOR : 将栈中的第一 第二元素进行按位异或并且结果压入栈中

因为栈是LIFO队列,所以异或的时候是这样的: XOR(CODESIZE,CALLVALUE)

 

我们已知CODESIZE = 0xC ,结果是0xA,所以我们这里就需要将0xc ^ 0xa = 0x6

 

Untitled

Puzzle 5

Untitled

 

这次的几个字节码有几个很陌生,我们来解释一下:

1
2
3
4
5
6
7
CALLVALUE : 将msg.value的wei压入栈中
DUP1 : 复制栈中的第一个值并将其压入栈的第一个位置
MUL : 将栈内前两个值弹出后相乘,结果压入栈
PUSH2 0100 : 将两个字节压入栈中,也就是100
EQ :从栈中弹出两个值,如果相等则将1压入栈,不相等则将0压入栈
PUSH1 0C : 将一个字节压入栈中,也就是0C
JUMPI : 它会从堆栈中弹出 2 个值。第一个值将是要跳转到的新程序计数器(一如既往,它必须是有效JUMPDEST指令)。第二个值是一个布尔标志(0 1),用于评估是否必须跳转。如果值为 1 则跳转;否则它将继续执行下一条指令。

解题思路

 

我们的msg.value 的值的平方需要等于0x100,这样才能保证后面eq之后结果为1,让后面的JUMPI可以成功跳转到0xc的位置,所以这里0x100开根号的结果是16

 

Untitled

Puzzle 6

Untitled

 

介绍一下新引入的字节码:

1
CALLDATALOAD : 从栈中弹出一个值作为偏移,并将其作为从CALLDATA中读取的字节集。从CALLDATA中读取的结果被推到栈中,作为一个32字节的值

这里假设我们的calldata为0x1234,如果在calldataload之前我们去push1 00 ,这就意味着偏移为0. 那么我们在栈中就会得到:

 

1234000000000000000000000000000000000000000000000000000000000000

 

如果PUSH1 01的话,偏移就是1.我们在栈中就会得到

 

3400000000000000000000000000000000000000000000000000000000000000

 

所以这一关,我们就需要给它传入一个:

 

0x000000000000000000000000000000000000000000000000000000000000000A

 

Untitled

Puzzle 7

Untitled

 

这一关的字节码明显比之前的复杂很多,我们在这里将这一关所用到的字节码都再次回顾一遍:

1
2
3
4
CALLDATASIZE : 它是会将CALLDATA的字节大小压入栈中
PUSH1 : 压入栈中一个字节数据
DUP1 : 复制栈中的第一个值并将其压入栈的第一个位置
CALLDATACOPY : 从栈中弹出 3 个值作为输入,并将 calldata 值从交易数据复制到内存
  • input1: destOffset:内存中的字节偏移量,复制操作的结果将被复制到此。
  • input2: offset:Calldata中的字节偏移量
  • input3: size : 复制到内存中的calldata数据的字节大小。
1
CREATE:部署一个新的合约。它从栈中弹出3个值,作为部署操作的输入。该操作的结果是被推送到栈中的部署合约的地址。
  • input1: value : 发送到新账户的value
  • input2: offset:你想从哪里开始复制新合约的代码的字节偏移。
  • input3: size : 从内存偏移量开始复制指令的字节大小。

    EXTCODESIZE :从栈中弹出一个值,作为20字节的地址使用。这个地址将被用来 "查询 "目标合约,并得到合约代码的字节大小作为结果。结果被推回堆栈

我们先随便传入一些数据看一下执行到后面是什么情况。

 

Untitled

 

也就是说我们只需要满足 EXTCODESIZE 的结果是1就可以了。这样就需要保证我们返回的运行时代码只有一条指令。

RETURN指令

 

从栈中获取两个值作为输入

 

第一个参数:为运行时代码在内存的起始位置

 

第二个参数:截取长度

1
2
3
4
5
6
[00]    PUSH1    01
[02]    PUSH1    00
[04]    MSTORE8   
[05]    PUSH1    01
[07]    PUSH1    00
[09]    RETURN

在0内存写入1,然后return的内容为内存0的位置,长度为1

 

这样返回的就是1了,这段代码的字节码为: 600160005360016000f3 ,那我们msg.data = 0x600160005360016000f3 尝试传入

 

Untitled

 

成功解题

 

Untitled

Puzzle 8

Untitled

 

这次引入了几个新的字节码,我们来看一下是什么意思

  • SWAP5 : SWAP1 的意思是:栈顶[a,b]变成[b,a]。那么SWAP5的意思就是[a,x,x,x,x,b]变成[b,x,x,x,x,a]。
  • GAS:计算gas费写入栈顶
  • CALL:调用合约,有七个参数:

    1. GAS
    2. Address
    3. msg.value
    4. argOst
    5. argLen
    6. retOst
    7. retLen

      若交易成功 将 1 写入栈顶,若失败,将0写入栈顶。

分析一下逻辑

 

一、 先部署合约

1
2
3
4
5
6
7
8
[00]    CALLDATASIZE   
[01]    PUSH1    00
[03]    DUP1   
[04]    CALLDATACOPY   
[05]    CALLDATASIZE   
[06]    PUSH1    00
[08]    PUSH1    00
[0a]    CREATE

二、调用合约

1
2
3
4
5
6
7
8
[0b]    PUSH1    00
[0d]    DUP1   
[0e]    DUP1   
[0f]    DUP1   
[10]    DUP1   
[11]    SWAP5   
[12]    GAS   
[13]    CALL

三、对比结果调用

1
2
3
4
[14]    PUSH1    00
[16]    EQ   
[17]    PUSH1    1B
[19]    JUMPI

通过这些信息我们可以看出来,第二步的CALL合约交易需要失败才可以对比成功。如何让交易失败呢?

 

REVERT → 0xfd

 

我们返回一个异常给他即可,还是利用我们第七关的代码

1
2
3
4
5
6
7
[00]    PUSH1    fd
[02]    PUSH1    00
[04]    MSTORE8   
[05]    PUSH1    01
[07]    PUSH1    00
[09]    RETURN
// 60fd60005360016000f3

这里可以看到call之后返回了0

 

Untitled

 

Untitled

Puzzle 9

Untitled

 

又有几个新的字节码引入:

  • LT:从栈中弹出2个值,并将value0 < value1的结果压入栈中。如果结果为真,则压入1,否则压入0。

分析程序

1
2
3
4
5
[00]    CALLDATASIZE   
[01]    PUSH1    03
[03]    LT   
[04]    PUSH1    09
[06]    JUMPI

这里只需要保证我们传入大于3个字节的数据即可。

1
2
3
4
5
6
7
8
[09]    JUMPDEST   
[0a]    CALLVALUE   
[0b]    CALLDATASIZE   
[0c]    MUL   
[0d]    PUSH1    08
[0f]    EQ   
[10]    PUSH1    14
[12]    JUMPI

这里是检查我们传入的wei与我们传入的字节相乘是否等于8,假设我们刚才传入的是 0x12345678 也就是四个字节,这里我们再传入的是2wei即可。

 

Untitled

Puzzle 10

Untitled

 

又有几个新的字节码引入:

  • GT:与LT相反,从栈中弹出2个值,并将value0 > value1的结果压入栈中。如果结果为真,则压入1,否则压入0。
  • MOD:从栈中弹出2个值,并将value0 % value1的结果压入栈。注意,分母(value1)是0,结果将是0。
  • ISZERO:从栈中弹出一个值,并将value0==0的结果推到堆栈中。

分析

1
2
3
4
5
6
[00]    CODESIZE    -> 1b
[01]    CALLVALUE   
[02]    SWAP1   
[03]    GT   
[04]    PUSH1    08
[06]    JUMPI

这里要求我们传入的wei小于1b才可以通过

1
2
3
4
5
6
7
8
9
10
11
12
[08]    JUMPDEST   
[09]    CALLDATASIZE   
[0a]    PUSH2    0003
[0d]    SWAP1   
[0e]    MOD   
[0f]    ISZERO   
[10]    CALLVALUE   
[11]    PUSH1    0A
[13]    ADD   
[14]    JUMPI
// ....
[19]    JUMPDEST

这里满足跳转的条件是跳转到19,也就是ADD(0xA,CALLVALUE) == 19,我们就需要传入15(十六进制0xF)

 

前面的判断CALLDATA为空就可以过,或者CALLDATASIZE是3的倍数。

 

Untitled

撒花完结~


[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。

收藏
点赞3
打赏
分享
最新回复 (1)
雪    币: 19299
活跃值: (28933)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
秋狝 2023-6-3 17:37
2
1
感谢分享
游客
登录 | 注册 方可回帖
返回