“The contract has 3 ethers, your job is to steal those ethers.”
How to Submit a Solution:
Use the below submission form to submit the solution: https://quillaudits.typeform.com/QuillCTF
Objective of CTF
Your purpose is just to call the deploy() function to recover the 3 ether.
Note: You can create POCs using Foundry/Hardhat. Without proper POC, your submissions will not be accepted.
Contract Code:
SlotPuzzle.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
import "./interface/ISlotPuzzleFactory.sol";
contract SlotPuzzle {
bytes32 public immutable ghost = 0x68747470733a2f2f6769746875622e636f6d2f61726176696e64686b6d000000;
ISlotPuzzleFactory public factory;
struct ghostStore {
bytes32[] hash;
mapping (uint256 => mapping (address => ghostStore)) map;
}
mapping(address => mapping (uint256 => ghostStore)) private ghostInfo;
error InvalidSlot();
constructor() {
ghostInfo[tx.origin][block.number]
.map[block.timestamp][msg.sender]
.map[block.prevrandao][block.coinbase]
.map[block.chainid][address(uint160(uint256(blockhash(block.number - block.basefee))))]
.hash.push(ghost);
factory = ISlotPuzzleFactory(msg.sender);
}
function ascertainSlot(Parameters calldata params) external returns (bool status) {
require(address(factory) == msg.sender);
require(params.recipients.length == params.totalRecipients);
bytes memory slotKey = params.slotKey;
bytes32 slot;
uint256 offset = params.offset;
assembly {
offset := calldataload(offset)
slot := calldataload(add(slotKey,offset))
}
getSlotValue(slot,ghost);
for(uint8 i=0;i<params.recipients.length;i++) {
factory.payout(
params.recipients[i].account,
params.recipients[i].amount
);
}
return true;
}
function getSlotValue(bytes32 slot,bytes32 validResult) internal view {
bool validOffsets;
assembly {
validOffsets := eq(
sload(slot),
validResult
)
}
if (!validOffsets) {
revert InvalidSlot();
}
}
function getGhostGit() public pure returns (string memory) {
return string(abi.encodePacked(ghost));
}
}
SlotPuzzleFactory.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
import {ReentrancyGuard} from "openzeppelin-contracts/security/ReentrancyGuard.sol";
import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol";
import {SlotPuzzle} from "./SlotPuzzle.sol";
import {EnumerableSet} from "openzeppelin-contracts/utils/structs/EnumerableSet.sol";
import "./interface/ISlotPuzzleFactory.sol";
contract SlotPuzzleFactory is ReentrancyGuard{
using EnumerableSet for EnumerableSet.AddressSet;
using SafeTransferLib for address;
EnumerableSet.AddressSet deployedAddress;
constructor() payable {
require(msg.value == 3 ether);
}
function deploy(Parameters calldata params) external nonReentrant {
SlotPuzzle newContract = new SlotPuzzle();
deployedAddress.add(address(newContract));
newContract.ascertainSlot(params);
}
function payout(address wallet,uint256 amount) external {
require(deployedAddress.contains(msg.sender));
require(amount == 1 ether);
wallet.safeTransferETH(amount);
}
}
ISlotPuzzleFactory.sol
// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;
struct Recipients{
address account;
uint256 amount;
}
struct Parameters{
uint256 totalRecipients;
uint256 offset;
Recipients[] recipients;
bytes slotKey;
}
interface ISlotPuzzleFactory {
function payout(address wallet,uint256 amount) external;
function ascertainSlot(Parameters calldata params) external returns (bool status);
}
Foundry setUp:
Download the zip and extract to locally test using foundry:
SlotPuzzle.zip2192.8KB
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "forge-std/Test.sol";
import {SlotPuzzle} from "src/SlotPuzzle.sol";
import {SlotPuzzleFactory} from "src/SlotPuzzleFactory.sol";
import {Parameters,Recipients} from "src/interface/ISlotPuzzleFactory.sol";
contract SlotPuzzleTest is Test {
SlotPuzzle public slotPuzzle;
SlotPuzzleFactory public slotPuzzleFactory;
address hacker;
function setUp() public {
slotPuzzleFactory = new SlotPuzzleFactory{value: 3 ether}();
hacker = makeAddr("hacker");
}
function testHack() public {
vm.startPrank(hacker,hacker);
assertEq(address(slotPuzzleFactory).balance, 3 ether, "weth contract should have 3 ether");
//hack time
assertEq(address(slotPuzzleFactory).balance, 0, "weth contract should have 0 ether");
assertEq(address(hacker).balance, 3 ether, "hacker should have 3 ether");
vm.stopPrank();
}
}