Skip to main content

QuillCTF - Confidential Hash

· 2 min read
Kaan Caglan

Confidential Hash is a Solidity CTF challenge from QuillCTF.

Solution

In this contract, there is only 1 solidity file. Confidential.sol. Contract is deployed on goerli test network.

Etherscan URL

To connect with this contract we can run brownie console with goerli network

kaancaglan@pop-os:~/QuillCTF/roadclosed$ brownie console --network goerli

Objective of CTF is

Find the keccak256 hash of aliceHash and bobHash. 

What we need to do to solve the question is simple, we just need to find keccak256 hash of aliceHash and bobHash and make return value of checkthehash function true.

// SPDX-License-Identifier: MIT
pragma solidity 0.8.7;

contract Confidential {
string public firstUser = "ALICE";
uint public alice_age = 24;
bytes32 private ALICE_PRIVATE_KEY; //Super Secret Key
bytes32 public ALICE_DATA = "QWxpY2UK";
bytes32 private aliceHash = hash(ALICE_PRIVATE_KEY, ALICE_DATA);

string public secondUser = "BOB";
uint public bob_age = 21;
bytes32 private BOB_PRIVATE_KEY; // Super Secret Key
bytes32 public BOB_DATA = "Qm9iCg";
bytes32 private bobHash = hash(BOB_PRIVATE_KEY, BOB_DATA);

constructor() {}

function hash(bytes32 key1, bytes32 key2) public pure returns (bytes32) {
return keccak256(abi.encodePacked(key1, key2));
}

function checkthehash(bytes32 _hash) public view returns(bool){
require (_hash == hash(aliceHash, bobHash));
return true;
}
}

There are 10 state variables in the contract. Few of them's visibility is private. However in blockchain nothing can be private. We can access all of those data from storage.

Layout of State Variables in Storage.

First element is already public but we can access to that variable with getStorageAt function

>>> web3.eth.getStorageAt(t.address, 0).strip()
b'ALICE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

For strings and bytes official document says

However, for short values (shorter than 32 bytes) the array elements are stored together with the length in the same slot.

So if we take a look, all of the fields will fit in the one unique block. So if we get storage index at 4 we will get aliceHash and if we check index at 9, we will get bobHash.

POC

First, we need a smart contract with selfdestruct feature in it.

#https://goerli.etherscan.io/address/0xf8e9327e38ceb39b1ec3d26f5fad09e426888e66
from brownie import *
from scripts.setup_general import console


def exploit():
contr = Confidential.at('0xf8E9327E38Ceb39B1Ec3D26F5Fad09E426888E66')
aliceHash = web3.eth.getStorageAt(contr.address, 4)
bobHash = web3.eth.getStorageAt(contr.address, 9)
console.yellow("Alice hash: " + str(aliceHash))
console.yellow("Bob hash: " + str(bobHash))

combineHash = contr.hash(aliceHash, bobHash)
console.green("Return value of checkthehash: " + str(contr.checkthehash(combineHash)))

And the output is

>>> run('poc', 'exploit')

Running 'scripts/poc.py::exploit'...
Alice hash: b'D\x8e]\xf1\xa6\x90\x8f\x8d\x17\xfa\xe94\xd9\xae?\x0ccTR5\xf8\xff9<gw\x19L\xae(\x14x'
Bob hash: b'\x98)\x0e\x06\xbe\xe0\rko4\tZT\xc4\x08r\x97\xe3(]E{\x14\x01(\xc1\xc2\xf3\xb6*A\xbd'
+ Return value of checkthehash: True