无题
OnChain Transaction Debugging: 5. Write Your Own PoC (Reentrancy)
Author: gbaleeee
Translation: Spark
In this article, we will learn reentrancy by demonstrating a real-world attack and using Foundry to conduct tests and reproduce the attack.
Prerequisite
- Understand common attack vectors in the smart contract. DeFiVulnLabs is a great resource to get started.
- Know how the basic Defi model work and how smart contracts interact with others.
What Is Reentrancy Attack
Source from: Reentrancy by Consensys.
Reentrancy Attack is a popular attack vector. It almost happens every month if we look into the DeFiHackLabs database. For more information, there is another great repo that maintains a collection of reentrancy-attacks,
In short, if one function invokes an untrusted external call, there could be a risk of the reentrancy attack.
Reentrancy Attacks can be mainly identified into three types:
- Single Function Reentrancy
- Cross-Function Reentrancy
- Cross-Contract Reentrancy
Hands-on PoC - DFX Finance
-
Source:Pckshield alert 11/11/2022
It seems @DFXFinance’s DEX pool (named Curve) is hacked (w/ loss of 3000 ETH or $~4M) due to the lack of proper reentrancy protection. Here comes an example tx: https://etherscan.io/tx/0x6bfd9e286e37061ed279e4f139fbc03c8bd707a2cdd15f7260549052cbba79b7. The stolen funds are being deposited into @TornadoCash
-
Transaction Overview
Based on the transaction above, we can observe limited info from etherscan. It includes information about the sender (exploiter), the exploiter’s contract, events during the transaction, etc. The transaction is labeled as an “MEV Transaction” and “Flashbots,” indicating that the exploiter attempted to evade the impact of front-run bots.
-
Transaction Analysis
We can use Phalcon from Blocksec to do the further investigation. -
Balance Analysis
In the Balance Changes section, we can see the alteration in funds with this transaction. The attack contract(receiver) collected a large amount ofUSDC
, andXIDR
tokens as profit, and the contract nameddfx-xidr-v2
lost a large amount ofUSDC
andXIDR
tokens. At the same time, the address starting with0x27e8
also obtained someUSDC
andXIDR
tokens. According to the investigation of this address, this is the DFX Finance: Governance Multi-Signature wallet address.Based on the aforementioned observations, the victim is DFX Finance’s
dfx-xidr-v2
contract and loss assets areUSDC
andXIDR
tokens. The DFX multi-signature address also receives some tokens during the process. Based on our experience, it should relate to the fee logic. -
Asset Stream Analysis
We can use another tool from Blocksec called metasleuth to analyze the asset flow.Based on the graph above, the exploiter borrowed a large amount of
USDC
,XIDR
tokens from the victim contract in step [1] and [2]. In step [3] and [4], the borrowed assets were sent back to the victim contract. After that,dfx-xidr-v2
token are minted to the exploiter in step [5] and the DFX multi-sig wallet receives the fee in bothUSDC
andXIDR
in step [6] and [7]. In the end,dfx-xidr-v2
tokens are burned from the exploiter’s address.As a summary, the asset stream is:
- The attacker borrowed
USDC
,XIDR
tokens from the victim contract. - The attacker sent the
USDC
,XIDR
tokens back to the victim contract. - The attacker minted
dfx-xidr-v2
tokens. - DFX multi-sig wallet received
USDC
,XIDR
tokens. - The attacker burned
dfx-xidr-v2
tokens.
This information can be verified with the following trace analysis.
- The attacker borrowed
-
Trace Analysis
Let’s observe the transaction under expand level 2.
The complete attack transaction’s function execution flow can be viewed as:
- The attacker invoked function
0xb727281f
for the attack. - The attacker called
viewDeposit
indfx-xidr-v2
contract viastaticcall
. - The attacker triggered
flash
function indfx-xidr-v2
contract withcall
. It is worth noting that in this trace, the function0xc3924ed6
in the attack contract was used as a callback.
- The attacker called
withdraw
function indfx-xidr-v2
contract.
- The attacker invoked function
-
Detail Analysis
The attacker’s intention of calling the viewDeposit function in the first step can be found in the comment for
viewDeposit
function. The exploiter wants to obtain the number ofUSDC
,XIDR
tokens to mint 200_000 * 1e18dfx-xidr-v2
token.And at the next step attack using the return value from
viewDeposit
function as a similar value for the input offlash
function invocation(the value is not exactly the same, more details later)The attacker invokes
flash
function in the victim contract as the second step. We can get some insight from the code:As you can see, the
flash
function is similar to the flash loan in Uniswap V2. User can borrow assets via this function. And theflash
function has a callback function for the user. The code is:1
IFlashCallback(msg.sender).flashCallback(fee0, fee1, data);
This invocation corresponds to the callback function in the attacker’s contract in the previous trace analysis section. If we do the 4Bytes Hash verification, it is
0xc3924ed6
The last step is calling
withdraw
function, and it will burn the stable token(dfx-xidr-v2
) and withdraw paired assets(USDC
,XIDR
). -
POC Implementation
Based on the analysis above we can implement the PoC skeleton below:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17contract EXP {
uint256 amount;
function testExploit() public{
uint[] memory XIDR_USDC = new uint[](2);
XIDR_USDC[0] = 0;
XIDR_USDC[1] = 0;
( , XIDR_USDC) = dfx.viewDeposit(200_000 * 1e18);
dfx.flash(address(this), XIDR_USDC[0] * 995 / 1000, XIDR_USDC[1] * 995 / 1000, new bytes(1)); // 5% fee
dfx.withdraw(amount, block.timestamp + 60);
}
function flashCallback(uint256 fee0, uint256 fee1, bytes calldata data) external{
/*
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
*/
}
}It is likely to raise the question of how an attacker steals assets with
withdraw
function in a flash loan. Obviously, this is the only part the attacker can work on. Now let’s dive into the callback function:As you can see, the attacker called
deposit
function in the victim contract and it will receive the numeraire assets that the pool supports and mint curves token. As mentioned in the graph above,USDC
andXIDR
are sent to the victim viatransferFrom
.At this point, it is known that the completion of the flash loan is determined by checking whether the corresponding token assets in the contract are greater than or equal to the state before the execution of the flash loan callback. And
depoit
function will make this validation complete.1
2require(balance0Before.add(fee0) <= balance0After, 'Curve/insufficient-token0-returned');
require(balance1Before.add(fee1) <= balance1After, 'Curve/insufficient-token1-returned');It should be noticed that the attacker prepared some
USDC
andXIDR
tokens for the flash loan fee mechanism before the attack. This is why the attacker’s deposit is relatively higher than the borrowed amount. So the total amount fordeposit
invocation is the amount borrowed with flash loan plus the fee. The validation in theflash
function can be passed with this.As a result, the attacker invoked
deposit
in the callback function, bypassed the validation in the flash loan and left the record for deposit. After all these operations, attacker withdrew tokens.In summary, the whole attack flow is:
- Prepare some
USDC
andXIDR
tokens in advance. - Using
viewDeposit()
to get the number of assets for laterdeposit()
. - Flash
USDC
andXIDR
tokens based on the return value in step 2. - Invoke
deposit()
function in the flash loan callback . - Since we have a deposit record in the previous step, now withdraw tokens.
The full PoC implementation:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15contract EXP {
uint256 amount;
function testExploit() public{
uint[] memory XIDR_USDC = new uint[](2);
XIDR_USDC[0] = 0;
XIDR_USDC[1] = 0;
( , XIDR_USDC) = dfx.viewDeposit(200_000 * 1e18);
dfx.flash(address(this), XIDR_USDC[0] * 995 / 1000, XIDR_USDC[1] * 995 / 1000, new bytes(1)); // 5% fee
dfx.withdraw(amount, block.timestamp + 60);
}
function flashCallback(uint256 fee0, uint256 fee1, bytes calldata data) external{
(amount, ) = dfx.deposit(200_000 * 1e18, block.timestamp + 60);
}
}More detailed codebase can be found in the DefiHackLabs repo: DFX_exp.sol
- Prepare some
-
Verify Fund Flow
Now, we can verify the asset stream graph with token events during the transaction.
At the end of the
deposit
function,dfx-xidr-v2
tokens were minted to the exploiter.In the
flash
function, the transfer event shows the fee collection(USDC
andXIDR
) for the DFX multi-sig wallet.The
withdraw
function burneddfx-xidr-v2
tokens that were minted in the previous steps. -
Summary
DFX Finance reentrancy attack is a typical cross-function reentrancy attack, where the attacker completes the reentrancy by calling the
deposit
function in the flash loan callback function.It is worth mentioning that the technique of this attack corresponds exactly to the fourth question in CTF damnvulnerabledefi [Side Entrance. If the project developers had done it carefully before, perhaps this attack would not have happened 🤣. In December of the same year, the Deforst project was also attacked due to a similar issue.
Learning Material
Reentrancy Attacks on Smart Contracts Distilled
C.R.E.A.M. Finance Post Mortem: AMP Exploit
Cross-Contract Reentrancy Attack
Sherlock Yield Strategy Bug Bounty Post-Mortem
Decoding $220K Read-only Reentrancy Exploit | QuillAudits