第1节:汇编介绍

Reversing and debugging EVM Smart contracts: First steps in assembly-part1

合约代码

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
contract Test {  

   function test() external {      } 

   function test2() external {      }  

   function test3() external {      }  
}

bytecode

0x6080604052348015600f57600080fd5b5060043610603c5760003560e01c80630a8e8e0114604157806366e41cb7146049578063f8a8fd6d146051575b600080fd5b60476059565b005b604f605b565b005b6057605d565b005b565b565b56fea2646970667358221220d28f98515dc0855e1c6f5aa3747ff775f1b8ab6545f14c70641ff9af67c2465164736f6c63430008070033

执行test函数

在真正执行test之前,有很多前置操作

检查msg.value

005 CALLVALUE load msg.value
006 DUP1      duplicate msg.value
007 ISZERO    verify if msg.value is equal to 0
008 PUSH1 0f  push 0f in the Stack (the byte location after the REVERT byte location)
010 JUMPI     jump to this location if msg.value is not equal to 0
011 PUSH1 00  push 00 in the Stack
013 DUP1      duplicate 00 in the Stack
014 REVERT    revert the execution 

In solidity, this is equivalent to:
if (msg.value > 0) { 
   revert(); 
} else {
   // Jump to byte 15
}

检查CALLDATASIZE

我们调用的是test,没有参数,因此calldata就是test的signatrue,具体为4字节的数据:

015 JUMPDEST     | 0x00 |
016 POP          ||
017 PUSH1 04     | 0x04 |
019 CALLDATASIZE | msg.data.size | 0x04 |
020 LT           | msg.data.size > 0x04 |
021 PUSH1 3c    | 0x3c | msg.data.size > 0x04 |
023 JUMPI        || (JUMPI takes 2 arguments)
060 JUMPDEST     ||
061 PUSH1 00     |0x00|
063 DUP1         |0x00|0x00|
064 REVERT       ||

if (msg.data.size < 4) { revert(); }

检查SELECTOR

上面已经确定selector的size,接下来要寻找目标函数,包括calldataload获取数据,偏移量去除填充的0字节,对比等,其中三个函数的selector分别为:

  • 0xf8a8fd6d:test
  • 0x0a8e8e01:test2
  • 0x66e41cb7:test3
024 PUSH1 00 |0x00| (the stack was previously empty in byte 23)
026 CALLDATALOAD |0xf8a8fd6d0000000.60zeros.000000000|
027 PUSH1 e0 |0xe0|0xf8a8fd6d0000000.60zeros.000000000|
029 SHR |0xf8a8fd6d|
030 DUP1 |0xf8a8fd6d|0xf8a8fd6d|
031 PUSH4 0a8e8e01 |0x0a8e8e01|0xf8a8fd6d|0xf8a8fd6d|  《=== 通过对比calldata,发现不是test2
036 EQ |0x0|0xf8a8fd6d|0xf8a8fd6d| 
037 PUSH1 41 |0x41|0x1|0xf8a8fd6d|
039 JUMPI |0xf8a8fd6d|
040 DUP1 |0xf8a8fd6d|0xf8a8fd6d|
041 PUSH4 66e41cb7 ||0xf8a8fd6d|0xf8a8fd6d| 《=== 通过对比calldata,发现不是test3
046 EQ |0x0|0xf8a8fd6d|
047 PUSH1 49 |0x49|0x1|0xf8a8fd6d|
049 JUMPI |0xf8a8fd6d|
050 DUP1 |0xf8a8fd6d|0xf8a8fd6d|
051 PUSH4 f8a8fd6d |0xf8a8fd6d|0xf8a8fd6d|0xf8a8fd6d| 《=== 通过对比calldata,发现确实是test,找到了!!
056 EQ |0x1|0xf8a8fd6d|
057 PUSH1 51 |0x51|0x1|0xf8a8fd6d|
059 JUMPI |0xf8a8fd6d|

最终反编译

mstore(0x40,0x80)                              
if (msg.value > 0) { revert(); }                              
if (msg.data.size < 4) { revert(); }                              
byte4 selector = msg.data[0x00:0x04]                                
switch (selector) {                               
   case 0x0a8e8e01:   // JUMP to 41 (65 in dec)   stop()
   case 0x66e41cb7:   // JUMP to 49 (73 in dec)   stop()
   case 0xf8a8fd6d:   // JUMP to 51 (85 in dec)   stop()
   default: revert();
stop()