Challenge 1 : Positive
This challenge proved to be fairly straightforward as we were provided with two smart contracts, namely Setup.sol
and Positive.sol
. Within the setup contract’s constructor, a new instance of the Positive
contract is created and stored in a state variable called TARGET
.
1 | pragma solidity =0.7.6; |
The goal of the challenge is to make the function isSolved()
return true. Let’s explore the Positive contract.
1 | // SPDX-License-Identifier: MIT |
The stayPositive
function takes an int64 value as input, and in order to set the state variable solved
to true
, the input must meet certain conditions. By utilizing the minimum value for int64, which is -9223372036854775808
, all of these conditions are satisfied, resulting in the state variable solved being set to true
. Now, let’s craft an exploit using forge.
1 | // SPDX-License-Identifier: UNLICENSED |
In order to get the flag, we follow the following steps:
- Launch a new instance by connecting to the provided server.
- Save the
rpc endpoint
,private key
and the address of thesetup contract
, provided by the server. - Retrieve the address of the Positive contract using the following command:
1
cast call <> "TARGET()" --rpc-url <your_rpc_url> --private-key <your_private_key>
- Execute the
stayPositive
function within the Positive contract by executing the following command:1
cast send <positiveContract> "stayPositive(int64)" --private-key <yourPrivateKey> --rpc-url <your_rpc_url> -- -9223372036854775808
- Connect to the server and read the flag!
Challenge 2: Infinite
This was an interesting challenge involving ERC-20 tokens. We’re given 6 files: candyToken.sol
, crewToken.sol
, fancyStore.sol
, localGang.sol
,respectToken.sol
,Setup.sol
. In order to solve the challenge, we need to satisfy the following condition:
1 | function isSolved() public view returns (bool) { |
Source Code Analysis
The tokens
crewToken
,candyToken
, andrespectToken
are simpleERC-20
tokens.The
localGang
contract comprises a constructor and two functions:gainRespect
andloseRespect
.
gainRespect: Transfers candyTokens from
msg.sender
tolocalGang
and mints an equivalent number of respectTokens formsg.sender
.loseRespect: Burns a specified number of respectTokens provided as a function argument and transfers an equal amount of candyTokens to the caller of the function.
- The
fancyStore
contract consists of a constructor and four functions:verification
,buyCandies
,respectIncreasesWithTime
, andsellCandies
.
verification: Takes 1 crew token and mints 10 candyTokens for
msg.sender
.buyCandies: Transfers
requestTokens
from the caller to the fancyStore and mints the same number ofcandyTokens
for the caller. Additionally, it increments therespectCount
for the caller.respectIncreasesWithTime: This function is irrelevant and can be disregarded.
sellCandies: Burns candyTokens and transfers an equal number of respectTokens to the caller. Additionally, it reduces the
respectCount
for the caller.
Plan of Attack
To augment the respectCount
, we need to invoke the buyCandies
function multiple times. However, we encounter a limitation as we only possess 10 candyTokens
initially (with the ability to mint 10 candyTokens using 1 crew token). Nevertheless, we observe that unlike the sellCandies
function, the gainRespect
function does not diminish the respectCount
. Consequently, we can execute the gainRespect
function (to boost the number of respectTokens) followed by the buyCandies
function to convert those candyTokens
into respectTokens
, thereby amplifying respectCount[msg.sender]
. This process can be repeated in a cycle until respectCount[msg.sender]
reaches a sufficient level to meet the condition required to solve the challenge.
Forge: test exploit
1 | // SPDX-License-Identifier: UNLICENSED |
Let’s run this exploit:
Great! The exploit is functioning smoothly, which means it’s time to retrieve the flag. Now, let’s proceed with modifying our test exploit:
1 | // SPDX-License-Identifier: UNLICENSED |
- Launch a new instance by connecting to the provided server.
- Save the
rpc endpoint
,private key
and the address of thesetup contract
, provided by the server. - Deploy the exploit contract
1
forge create ./test/Exploit.sol:Exploit --private-key <your_private_key> --rpc-url <your_rpc_url>
- Call the
testExploit
function (present in the exploit contract)1
cast send <address_of_exploit_contract> "testExploit()" --rpc-url <your_rpc_url> --private-key <your_private_key>
- Connect to the server and get the flag.
Challenge 3: Deception
Just as the name suggests, there was a deception over here. Upon analyzing the solve
function in the provided file Deception.sol
, we discover that if the keccak256
hash of our input evaluates to 0x65462b0520ef7d3df61b9992ed3bea0c56ead753be7c8b3614e0ce01e4cac41b
, the variable solved
is set to true
. Through a Google search, we ascertain that this hash corresponds to the string secret
.
1 | function solve(string memory secret) public { |
However, a deception is present in the remote instance, as the program does not compare the hash of our input with the aforementioned hash. Instead, it compares it with something else. To successfully solve this challenge, we need to follow the steps outlined below:
- Launch a new instance by connecting to the provided server.
- Save the
rpc endpoint
,private key
and the address of thesetup contract
, provided by the server. - Retrieve the address of the
deception
contract using the following command:1
cast call <your_setup_contract> "TARGET()" --rpc-url <your_rpc_url> --private-key <your_private_key>
- Get the runtime bytecode of the
deception
contract1
cast code <your_deception_contract> --rpc-url <your_rpc_url>
- Decompile the bytecode here
The decompiled code is pretty weird but we quickly spot that the keccak256 hash of the input is being compared with some different hash.
1 | function 0x76fe1e92(uint256 varg0) public payable { |
Conducting Google searches about this hash does not yield any valuable results . However, there’s an interesting line present in the function password()
1 | v4 = _SafeAdd(0x616263, stor_3); |
0x616263
means abc
but the keccak256 hash of abc
isn’t 0xdb91bc5e087269e83dad667aa9d10c334acd7c63657ca8a58346bb89b9319348
. Analyzing the storage
layout of the deception
contract, we get something interesting stored at the third slot
1 | cast storage <your_deception_contract> 3 --rpc-url <your_rpc_url> |
1 | 0x000000000000000000000000000000000000000000000000000000000078797a |
The hexadecimal value 0x78797a
corresponds to the string xyz
. When combined with abc
, it results in xyzabc
. Taking the keccak256
hash of xyzabc
yields 0xdb91bc5e087269e83dad667aa9d10c334acd7c63657ca8a58346bb89b9319348
which is the target hash.
- Invoke the
solve
function, passing the string argumentxyzabc
.1
cast send <your_deception_contract> "solve(string)" "xyzabc" --private-key <your_private_key> --rpc-url <your_rpc_url>
- Connect to the server and get the flag