reaper

Setup

Install Foundry:

curl -L https://foundry.paradigm.xyz | bash

foundryup

Git clone the reaper repo:

git clone git@github.com:ret2basic/reaper.git

Install dependencies, compile and run the test:

cd reaper

forge install

Compile and run the test:

forge test

This test will show 1 failed since we have not solved the challenge yet. Once we code up our solution in reaper/test/ReaperHack.t.sol, we can run forge test to check the solution.

Code Audit

Overview

Open reaper/src/ReaperVaultV2.sol in Visual Studio. The contract inheritance tells us many things:

  1. This contract inherits ERC4626 - Tokenized Vaults

  2. This contract inherits ERC20 - Fungible Tokens

  3. This contract is unlikely to be vulnerable to reentrancy attack

Digress: ERC-4626 Rounding Issue

Here we digress a bit from the reaper contract. Regarding ERC-4626, we should know the following things:

  • What is vault

  • Why ERC-4626

  • ERC-4626 Rounding Issue

Vault and ERC-4626

A vault is a multi-sig solution or smart contract that can store and manage assets such as crypto. Each vault always has the tokens it generates as a form of returns. These generated tokens can later be exchanged for tokens that were originally locked in vaults.

The problem developers face concerning yield-bearing tokens is integrating tokens of different protocols. Before ERC-4626, developers have to research a bunch of protocols and figure out how to stitch them together. This can be a pain in the ass and it is also error-prone.

The main benefit of ERC-4626 is that it standardizes tokenized vaults to make protocol integration easier and less prone to error. Since there is a common standard that you can integrate, there is no actual need to build separate adapters any longer. In a nutshell, it quickens development; composability at its peak.

What is the ERC-4626 token standard? - alchemy

ERC-4626 Rounding Issue

The ERC-4626 rounding issue often appears in Code4rena reports, for example:

Notional x Index Coop Findings & Analysis Report

deposit() and withdraw() Functions

For ERC20-like contracts, the deposit() and withdraw() functions are likely to be vulnerable since they are directly associated with transactions, hence directly associated with user's money.

deposit()

This function is simple enough:

  1. It checks if the contract is in "emergency shutdown" state

  2. It checks if the assets argument is 0

  3. It checks if the pool is reaching its capacity

  4. It collects token from the user and mints corresponding shares back to the user

The implementation seems correct.

withdraw()

This function is a wrapper of _withdraw(). We should audit that function instead.

_withdraw()

The vulnerability is pretty obvious: this function never verifies whether msg.sender is the owner or not. As a result, an attacker can feed his/her own address into this receiver field and specify any account to be the owner. When the withdraw() function is called, all the money in the vicitm account will be transferred to the attacker's account.

Solution

To exploit this vulnerability, we are going to look for some "rich" addresses and steal money from them by calling the withdraw() function.

First, we go to the vault and hunt for some "rich" addresses. Click on "DAI Crypt (rfDAI)":

DAI Crypt (rfDAI)

Grab a few addresses with large "Quantity" field:

Addresses with large Quantity field

Make sure you click into the address and copy the address in "correct" format. The correct address format contains uppercase letters, for example:

Correct address format

Next, we want to figure out how much money they have in these addresses. To do this, we use the reaper.balanceOf() function:

Here we divide each balance by 1e18 to convert wei to ether.

Use the following command to compile the contract and see the output of console.log():

Account balance

We will satisfy the 400k DAI requirement if we steal all money from these 4 addresses.

Finally, we are going to steal money from these addresses by calling a series of withdraw() functions:

Run forge test to verify our solution:

reaper test passed

To summarize, here is the complete solution code snipet:

Last updated