exnow交易所不能提现,exnow交易所可靠吗

  

     

  

  前言近年来,区块链攻防出现在各种大型CTF(Capture The Flag,中文一般翻译为Capture,指网络安全技术人员在网络安全领域进行技术比拼的一种竞赛形式)比赛中,出现问题的多为区块链智能合约攻防。本系列文章主要围绕智能合约的攻防来分析智能合约的攻防要点。在前两篇文章中,我们分享了契约反编译和反汇编的基本内容。后续文章中,我们将继续分享智能合约在CTF比赛中的常见问题(再入、整数溢出、空投、可控随机数等。)以及解决方案,相信会给读者带来不一样的收获。   

  

  在上一篇文章中,我们分享了CTF竞赛中经常测试的再入大气层漏洞问题。本文继续分享CTF竞赛中的整数溢出题,这也是一种常见的题型。当然,大多数CTF智能合约问题并不仅仅考察单个漏洞的攻击和防御,而是可能涉及多个漏洞的组合。   

  

  本文以2018年WCTF BelluminarBank话题为例,与大家分享智能合约整数溢出的问题。解决这个问题不仅需要整数溢出攻击,还需要变量覆盖、权限设置等攻击技术。   

  

  标题地址:   

  

  由于WCTF智能合约大赛不在ropsten上举行,也没有线上攻防场景,所以在GitHub:https://GitHub . com/beched/CTF/tree/master/2018/wctf-belluminar中已经给出了合约的具体标题介绍和合约源代码。   

  

  分析主题建议团队需要对字节码进行逆向工程,并使用以下攻击:   

  

  整数溢出绕过存款期限限制;溢出来盖银行老板;存储访问权限以揭示私有属性;部署自杀契约以强制将eth发送到目标契约(以解决余额差异)并不一定需要意外的以太网攻击。如果使用了withdraw()和invest()调用,它们可以得到适当的平衡。这可能是由于一个巨大的错误导致了错误的解决方案:取款()函数没有改变余额数组。但还是要提前利用整数溢出。   

  

  合同显示Belluminar银行很小,很特别。它的工作原理如下:   

  

  任何人都可以投资任何金额,并应指定存款期限(在此期限之前存款将被锁定);存款期限必须比以前的客户至少长1年;每笔存款都有一个账号;0账户包含31337魏,已被银行所有人(合同创建人)锁定多年;如果存款期限是一年(如果你不取钱),银行老板可以没收你的存款。目标是破解这家银行,清空它的余额。如果成功,机器人会把交易数据里的logo发给你。   

  

  代码实用程序可靠性^0.4.23;合同BelluminarBank {结构投资{ uint256金额;uint256存款_期限;地址所有者;}//全局可变投资余额;uint256头;私人所有者地址;bytes16私人秘密;//secret可以读取函数belluminarbank (bytes16 _ secret,uint 256 deposit _ term)public { secret=_ secret;owner=msg.senderif(msg . value 0){ balances . push(Investment(msg . value,deposit_term,msg . sender));} }函数bankBalance()公共视图returns (uint256) {返回地址(this)。平衡;}//局部变量覆盖全局变量函数Invest (uint256account,uint 256 deposit _ term)public payable { if(account=head account balances . length){ investment storage investment=balances;投资.金额=消息.价值;} else {if(balances.length 0) {//存在整数溢出require(deposit _ term=balances . deposit _ term 1 years);}//局部变量投资   

ment.amount = msg.value; investment.deposit_term = deposit_term; investment.owner = msg.sender; balances.push(investment); } } function withdraw(uint256 account) public { require(now >= balances.deposit_term); require(msg.sender == balances.owner); msg.sender.transfer(balances.amount); } function confiscate(uint256 account, bytes16 _secret) public { require(msg.sender == owner); require(secret == _secret); require(now >= balances.deposit_term + 1 years); uint256 total = 0; for (uint256 i = head; i <= account; i++) { total += balances.amount; delete balances; } head = account + 1; msg.sender.transfer(total); }}合约分析从题目提示可以得出,本次攻击的目的是拿到合约中的所有余额。并且需要多个漏洞攻击手法。

  

先来分析合约中的存在转账功能函数withdraw()和confiscate():

  

function withdraw(uint256 account) public { require(now >= balances.deposit_term); require(msg.sender == balances.owner); msg.sender.transfer(balances.amount);}withdraw()函数中,会判断现在的时间是否大于存款的期限,第二句判断调用者地址是否是存款者地址,如果条件满足,就会转出当前合约调用者的存款余额,可以得出该函数并不能转出合约所有余额。

  

继续来看第二个函数confiscate():

  

function confiscate(uint256 account, bytes16 _secret) public { require(msg.sender == owner); require(secret == _secret); require(now >= balances.deposit_term + 1 years); uint256 total = 0; for (uint256 i = head; i <= account; i++) { total += balances.amount; delete balances; } head = account + 1; msg.sender.transfer(total);}confiscate()函数中会依次判断所有者地址,secret,存款期限,如果条件满足,之后会对存款数组进行遍历,将得到的资金amount全都赋予total,最终通过transfer()将所有余额转出,也就是说合约所有者可以将之前存款记录的余额全部取出。很明显,该confiscate()函数就是我们最终需要利用的转账函数。

  

如果要使用confiscate()函数进行转账,我们需要解决该函数中的前三行代码判断条件,由于题目给出了提示-存储溢出以覆盖银行所有者。我们继续分析该合约变量覆盖问题。

  

从合约源码可看到,该合约中有四个全局变量(balances,head,owner,secret),在solidity中,全局变量存储在storage当中,对于复杂的数据类型,比如array(数组)和struct(结构体),在函数中作为局部变量时,也会默认储存在storage当中。并且solidity的状态变量存储时,都是按照状态在合约中的先后顺序进行依次存储。

  

也就是说目前合约的四个全局变量存储如下:

  

storage<0> : balancesstorage<1> : headstorage<2> : ownerstorage<3> : secret在invest()函数中,通过Investment storage investment = balances;可得到一个结构体变量investment,该变量有三个成员均存在赋值操作,如下:

  

investment.amount = msg.value; investment.deposit_term = deposit_term;investment.owner = msg.sender;在函数中作为局部变量时,也会默认储存在storage当中,由于结构体变量investment并未对三个成员进行初始化,所以当变量存储时依然会按照顺序存储在storage<0,1,2>中,那么目前storage中的存储为数据为:

  

  

这里局部变量覆盖全局变量时要特别注意一点:局部变量amount覆盖全局变量balances时,由于balances变量是数组的长度(目前数组中有合约部署者传入的一组数据,故balances为1),当其他调用者也传入的一组数据,比如传入的msg.value值为1(也就是amount的值为1时),之后变量覆盖后的balances值也为1,但是由于传入了一组数据后,数组的长度balances变为2,由于变量覆盖相互影响的关系,balances的值为2后,amount的值也变为2,也就是说虽然传入的msg.value值为1,但最终amount的值为2。

  

我们继续来分析合约漏洞,由于上图invest()函数中赋值的三个变量(msg.value,deposit_term,msg.sender)都可控:

  

第一个变量msg.value,是调用者传入的资金;

  

第二个变量deposit_term本身的含义是存款期限,调用者可根据自己情况输入需要存款的时间,在合约中发生变量覆盖后则代表head值(存款数据的索引)。并且在invest()函数中存在和deposit_term变量相关联的判断条件require(deposit_term >= balances.deposit_term + 1 years);我们将它和confiscate()函数中的判断条件require(now >= balances.deposit_term + 1 years);进行对比。

  

由于我们最终需要利用confiscate()函数中的transfer函数进行转账,如果按照正常逻辑运算,我们存钱后至少需要一年时间才能取出,所以该判断条件(require(now >= balances.deposit_term + 1 years);)必须设法绕过。同时我们还需要invest()函数中的判断条件(require(deposit_term >= balances.deposit_term + 1 years);)也正常执行。

  

可以看到这两行代码的条件判断中加减操作并没有做安全防护,这里假如我们使balances.deposit_term + 1 years值等于2^256,由于solidity的存储关系,这里会发生整数上溢出,最终结果为0,就可以绕过该判断条件。还需要注意的一点为:confiscate()函数中的for循环需要head(由于变量覆盖的关系,head值为deposit_term传入的值)从0开始才能将所有的资金取出,所以需要我们对deposit_term进行两次赋值:第一次赋值为2^256 – 1 years(solidity中默认时间单位为秒,故这里的赋值为:2^256 – 3153600 = 115792089237316195423570985008687907853269984665640564039457584007913098103936 ),第二次赋值为0(赋值为0,判断条件也恒成立)。

  

第三个变量msg.sender,从上图可以看出,该变量传入后覆盖全局变量owner,当前调用者地址就会变为合约所有者,从而就可绕过confiscate()函数中msg.sender == owner判断条件。

  

由于secret存储在storage(storage变量是指永久存储在区块链中的变量),所以我们可以调用storage索引获取里面的值。

  

至此confiscate()函数中的前三句判断条件均已满足。

  


  

解题思路通过分析BelluminarBank合约漏洞,我们可以利用整数溢出,变量覆盖,访问权限等漏洞攻击转出合约所有余额。具体解题思路如下:

  

通过调用invest()函数传入account为1,deposit_term为115792089237316195423570985008687907853269984665640564039457584007913098103936,携带的msg.value为1wei。account始终根据第一句的判断条件进行赋值。msg.value赋值amount,再进行balances变量覆盖(由于变量循环赋值的关系),最终结果balances=amount=2;deposit_term值变量覆盖也成为head值;调用者地址msg.sender最终变量覆盖后会成为合约所有者owner。调用之后balances.deposit_term + 1 years发生整数溢出,绕过判断条件。继续调用invest()函数传入account为2,deposit_term为0,携带的msg.value为2wei,msg.value赋值amount,再进行balances变量覆盖(由于变量循环赋值的关系),最终结果balances=amount=3;deposit_term值变量覆盖也成为head值为0,相当于还原head原始的值。由于balances变量的循环覆盖的关系,最终的合约余额会有差别,可通过合约自毁或者withdraw()函数调整合约余额。调用confiscate()函数传入两个参数:account为1,secret值为我们之后通过storage获取的密码值,最终取走合约所有的余额。

  

攻击演示本次攻击演示在ropsten测试网进行,使用工具为Remix+Matemask+myetherwallet

  

Remix在线编辑器:http://remix.ethereum.org/

  

MetaMask钱包插件:https://metamask.io/

  

MyEtherWallet在线钱包:https://www.myetherwallet.com/

  

1.首先部署BelluminarBank漏洞合约使用在线编辑器Remix通过Meta Mask在线钱包A地址部署BelluminarBank合约,部署时给合约传入参数为:

  

value:31337 wei,deposit_term:0x00000000000000000000000000000001,_secret:1000

  

(为了方便查看数据,我们将合约源码中的一部分内容进行了可见性修改)

  

  

部署完成后目前合约中变量值为以下

  

  

2.使用myetherwallet在线钱包调用BelluminarBank合约在remix中获取api并复制部署的合约地址,填入myetherwallet钱包中。

  

  

连接成功

  

  

3.调用invest()函数修改合约所有者owner,存款期限数值deposit_term,变量重复覆盖值amount传入参数为:

  

value:0.000000000000000001 ETH,account:1,deposit_term:115792089237316195423570985008687907853269984665640564039457584007913098103936。

  

  

完成后目前合约中变量值为以下

  

4.调用invest()函数修改存款期限数值deposit_term(修改head为0),变量重复覆盖值amount传入参数为:

  

value:0.000000000000000002 ETH,account:2,deposit_term:0。

  

  

完成后目前合约中变量值为以下

  

  

虽然上图中显示的合约全部余额为31340 wei,但调用过程中出现循环变量覆盖,导致数组中的余额为31337+2+3 =31342 wei,如下图所示:

  

  

为了使合约本身余额与数组中的amount匹配,这里我们选择强制给该转币。

  

5.通过c地址部署合约并调用taijie()函数自毁合约给BelluminarBank合约转2 wei,平衡合约数组中的余额。

  

自毁成功后,BelluminarBank合约余额变为31342 wei。

  

  

6.调用confiscate()函数最终取走合约所有的余额传入两个参数:account:2,secret:0x00000000000000000000000000000001

  

  

调用完成后,BelluminarBank合约余额变为0。

  

  

至此完成攻击演示

  


  

总结本篇文章中,我们通过2018WCTF比赛中的BelluminarBank智能合约题目,详细分析了合约存在的漏洞问题,提供了解题思路并进行了攻击演示,其中使用的相关工具已在文中给出链接,希望对智能合约初学者及爱好者有所帮助,下一篇我们会继续分享CTF智能合约经典题目,请大家持续关注。

相关文章