Weak Randomness Exploit Generator
Sigvex exploit generator that validates randomness vulnerabilities by testing whether contract outcomes change predictably with controlled block environment variables.
Weak Randomness Exploit Generator
Overview
The weak randomness exploit generator validates findings from the weak-randomness detector by executing the target contract under four distinct block environment configurations and checking whether lottery or selection outcomes change in a predictable, controllable way. If a pre-computed or miner-controllable block.timestamp value deterministically selects a specific winner index, the randomness is exploitable.
Weak randomness has produced numerous lottery and gambling contract exploits. The SmartBillions lottery (2017, $400K) used blockhash for winner selection; TheRun ($1M+) used block variables directly. Miners and validators with advance knowledge of the next block’s hash can selectively include or exclude transactions to control outcomes.
Note: Exploit generation in Sigvex is for vulnerability validation purposes only.
Attack Scenario
Timestamp manipulation (15-second window):
- A lottery contract selects a winner with
uint256 winnerIndex = block.timestamp % players.length. - The attacker calculates which timestamp value maps to their position in the players array.
- As a miner (or by bribing one), the attacker includes the
drawWinner()call in a block whose timestamp is set to the target value. - The attacker wins the prize pool with certainty.
Blockhash prediction:
- A lottery commits to drawing at
block.number + 1. - The attacker (as miner/validator) sees the candidate blockhash before the block is finalized.
- If the candidate hash maps to an unfavorable winner, the miner withholds the block and mines a replacement.
- This continues until a favorable hash is found — costing at most the block reward in foregone income.
Block number prediction:
block.numberis publicly readable before transaction submission.- An attacker can calculate the outcome of any
block.number-based draw and choose when to submit thedrawWinner()transaction.
Exploit Mechanics
The generator runs four execution scenarios with storage slot 2 representing the randomness source type (1=timestamp, 2=blockhash, 3=VRF):
| Scenario | block.timestamp | Source (slot 2) | Expected winner (slot 3) |
|---|---|---|---|
| 1 — Normal | 1700000000 | 1 (timestamp) | 1700000000 % 10 = 0 |
| 2 — Manipulated | 1700000005 | 1 (timestamp) | 1700000005 % 10 = 5 |
| 3 — Blockhash | 1700000000 | 2 (blockhash) | 123456 % 10 = 6 |
| 4 — VRF (secure) | 1700000000 | 3 (VRF) | 0 (not yet fulfilled) |
Verdict: If Scenario 1 and Scenario 2 both complete without revert, the attacker can control the winner index by adjusting timestamp — confirmed vulnerable. If Scenario 3 also succeeds, blockhash is an additional attack vector. Confidence is 0.95 for this finding.
The calldata targets drawWinner() (fallback selector 0x9a2cdc8a), or the specific function selector from the finding location.
The generator records which weak randomness sources were detected — block.timestamp (manipulable by miners within ±15 seconds), blockhash (only valid for the last 256 blocks and known to miners), and combinations of both.
The generated PoC demonstrates three vulnerable patterns and a Chainlink VRF secure pattern:
// VULNERABLE: block.timestamp as randomness
function drawWinner() external {
uint256 winnerIndex = block.timestamp % players.length; // predictable!
address winner = players[winnerIndex];
payable(winner).transfer(prizePool);
}
// SECURE: Chainlink VRF
function drawWinner() external {
requestId = COORDINATOR.requestRandomWords(keyHash, subscriptionId,
requestConfirmations, callbackGasLimit, numWords);
}
function fulfillRandomWords(uint256 _requestId, uint256[] memory randomWords)
internal override {
uint256 winnerIndex = randomWords[0] % players.length; // unpredictable
payable(players[winnerIndex]).transfer(prizePool);
}
Remediation
- Detector: Weak Randomness Detector
- Remediation Guide: Weak Randomness Remediation
For any application where randomness affects economic outcomes, use Chainlink VRF v2:
import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol";
contract SecureLottery is VRFConsumerBaseV2 {
// requestRandomWords() initiates off-chain randomness generation
// fulfillRandomWords() is called back with verifiable randomness
// Cannot be predicted before the VRF response arrives
}
For lower-stakes applications where VRF cost is prohibitive, implement a commit-reveal scheme where players each submit a secret hash, then reveal it after all commitments are collected. Combining all revealed secrets provides entropy that no single party can control.
Sources to avoid: block.timestamp, blockhash(), block.number, block.difficulty / block.prevrandao, msg.sender, simple counters.
References
- SWC-120: Weak Sources of Randomness from Chain Attributes
- SmartBillions Exploit Analysis (2017, $400K via blockhash prediction)
- Chainlink VRF v2 Documentation
- drand: Publicly Verifiable Randomness Beacon
- Ethereum: Commit-Reveal Scheme Pattern