加密日记【011】EVM谜题集PLUS

https://github.com/daltyboy11/more-evm-puzzles

这是难度加深的EVM谜题集 more-evm-puzzles

EVM Puzzle 1

post image
  • EXP:a**b

  • PC:程序计数器


00      36      CALLDATASIZE
01      34      CALLVALUE
02      0A      EXP //CALLVALUE**CALLDATASIZE=0x40=64 //CALLVALUE=2
03      56      JUMP
04      FE      INVALID
........................
3F      FE      INVALID
40      5B      JUMPDEST
41      58      PC //41
42      36      CALLDATASIZE // 6
43      01      ADD //PC +CALLDATASIZE=47
44      56      JUMP
45      FE      INVALID
46      FE      INVALID
47      5B      JUMPDEST
48      00      STOP

EVM Puzzle 2

post image
00      36        CALLDATASIZE
01      6000      PUSH1 00
03      6000      PUSH1 00
05      37        CALLDATACOPY //将calldata写入memory
06      36        CALLDATASIZE
07      6000      PUSH1 00
09      6000      PUSH1 00
0B      F0        CREATE //将calldata写入合约
0C      6000      PUSH1 00
0E      80        DUP1
0F      80        DUP1
10      80        DUP1
11      80        DUP1
12      94        SWAP5
13      5A        GAS
14      F1        CALL //call(gas,address,0,0,0,0,0)
15      3D        RETURNDATASIZE //return 返回数据的大小=0A
16      600A      PUSH1 0A //
18      14        EQ //
19      601F      PUSH1 1F
1B      57        JUMPI
1C      FE        INVALID
1D      FE        INVALID
1E      FE        INVALID
1F      5B        JUMPDEST
20      00        STOP

理解CREATE和CALL

  • create操作码执行结束后就只剩下return返回的结果。

  • call是对合约的调用,也就是对运行时代码的执行

这道题是有点绕的:

call要返回10(0A)个字节个数据,下面的代码是调call要执行的

00      600A      PUSH1 0A        [0x0A]
02      6000      PUSH1 00        [0x00, 0x0A] (offset, size)
04      F3        RETURN          []

上面的代码是create之后返回的,所以create return的内容是操作码 600A600F3

所以create合约的操作码:

00      64600A6000F3      PUSH5 600A6000F3        [0x600A6000F3]
06      6000              PUSH1 00                [0x0, 0x600A6000F3] (offset, value)
08      52                MSTORE                  []
00      600A              PUSH1 0A                [0x0A]
02      601B              PUSH1 1B                [0x1B, 0x0A] (offset, size)
04      F3                RETURN                  []

0x1B尽管我们将运行时字节码存储在0x0- 那是因为它看起来像是 memory: 0x000000000000000000000000000000000000000000000000000000600A6000F3

我们必须跳过值开头的 27 个零才能到达字节码。我们本可以阻止使用PUSH32 0x600A6000F3000000000000000000000000000000000000000000000000000000,但这会显着增加构造字节码的大小。

这段构造字节码:0x64600A6000F3600052600A601BF3

用CODECOPY优化:

00      600A      PUSH1 0A        [0x0A]
02      6000      PUSH1 00        [0x00, 0x0A]
04      6000      PUSH1 00        [0x00, 0x00, 0x0A] (destOffset, offset, size)
06      39        CODECOPY        []
07      600A      PUSH1 0A        [0x0A]
09      6000      PUSH1 00        [0x00, 0x0A] (offset, size)
0B      F3        RETURN          []

再优化,用DUP1替代重复push

00      600A      PUSH1 0A        [0x0A]
02      80        DUP1            [0x0A, 0x0A]
03      6000      PUSH1 00        [0x00, 0x0A, 0x0A]
05      80        DUP1            [0x00, 0x00, 0x0A, 0x0A] (destOffset, offset, size), size
06      39        CODECOPY        [0x0A]
07      6000      PUSH1 00        [0x00, 0x0A] (offset, size)
09      F3        RETURN          []

最终字节码:0x600A80600080396000F3

EVM Puzzle 3

post image

补充CALL和DELEGATECALL的区别,为什么CALL可以发value,DELEGATECALL不能发送value

CALL 指令是用于在当前合约和另一个地址之间进行消息传递的,可以发送以太币(value),并且可以选择在接收方合约执行的函数和参数。当执行 CALL 指令时,可以指定发送的以太币数量,这个值可以为0。

相比之下,DELEGATECALL 指令也是用于在当前合约和另一个地址之间进行消息传递的,但不同之处在于它不会创建新的执行环境,而是在当前合约的执行环境中执行被调用合约的代码。换句话说,DELEGATECALL 可以将当前合约的执行上下文传递给被调用的合约,并在其中执行被调用合约的代码。

因为 DELEGATECALL 不会创建新的执行环境,所以它不能像 CALL 一样发送以太币。如果在 DELEGATECALL 中发送以太币,那么这些以太币会被发送到被调用的合约地址,而不是当前合约的地址,这可能会导致意外的结果和安全问题。因此,DELEGATECALL 指令没有提供发送以太币的选项。

00      36        CALLDATASIZE
01      6000      PUSH1 00
03      6000      PUSH1 00
05      37        CALLDATACOPY
06      36        CALLDATASIZE
07      6000      PUSH1 00
09      6000      PUSH1 00
0B      F0        CREATE
0C      6000      PUSH1 00
0E      80        DUP1
0F      80        DUP1
10      80        DUP1
11      93        SWAP4
12      5A        GAS
13      F4        DELEGATECALL
14      6005      PUSH1 05
16      54        SLOAD
17      60AA      PUSH1 AA
19      14        EQ
1A      601E      PUSH1 1E
1C      57        JUMPI
1D      FE        INVALID
1E      5B        JUMPDEST
1F      00        STOP

通过DELEGATECALL调用,将值存储在Storage上,SLOAD从storage再取出存入的值与AA相同,则DELEGATECALL实现的操作码:

00      60AA      PUSH1 AA        [0xAA]
02      6005      PUSH1 05        [0x05, 0x0A] (key, value)
04      55        SSTORE          []

字节码为60AA600555 那create构造字节码:

00      6460AA600555      PUSH5 60AA600555        [0x60AA600555]
06      6000              PUSH1 00                [0x0, 0x60AA600555] (offset, value)
08      52                MSTORE                  []
00      600A              PUSH1 0A                [0x0A]
02      601B              PUSH1 1B                [0x1B, 0x0A] (offset, size)
04      F3                RETURN                  []

我们将运行时字节码写入内存,这样我们就可以RETURN在启动时使用它

构造字节码:0x6460AA600555600052600A601BF3