Defi安全挑战系列-Damn Vulnerable DeFi(#1 Unstoppable)

有一个以百万DVT代币抵押的代币化保险库。在宽限期结束之前,它提供免费的闪电贷款服务。

为了完成挑战,使该保险库停止提供闪电贷款服务。

你的初始余额为10个DVT代币。

https://www.damnvulnerabledefi.xyz/challenges/unstoppable/

分析合约

挑战合约代码分为两部分,分别是提供闪电贷的金库合约(UnstoppableVault.sol)和执行闪电贷的合约(ReceiverUnstoppable.sol)。

通过阅读合约代码就可以了解到 Flashloan原理,俗称闪电贷,原理简单来说就是你的合约先调用金库合约,例如aave、uniswap等等这些合约提供闪电贷功能,金库合约先转币给你的智能合约,并且回调你的智能合约,你的智能合约收到币和被回调后执行合约逻辑去套利,执行完你的合约逻辑后要还币加手续费给金库合约。如果金库合约检查收到的币足够就交易完成,否则就触发revert中断交易。

闪电贷的金库合约(UnstoppableVault.sol)

其中金库合约继承了ERC4626协议。简单来说,存入100ETH,协议mint 一定量 xETH。xETH就是存入计数的,更多细节查关于ERC4626资料吧。

金库代码中还有一段Solidity的内联汇编:

    /**
     * @inheritdoc ERC4626
     */
    function totalAssets() public view override returns (uint256) {
        assembly {
            // better safe than sorry
            if eq(sload(0), 2) {
                mstore(0x00, 0xed3ba6a6)
                revert(0x1c, 0x04)
            }
        }
        return asset.balanceOf(address(this));
    }

如果不熟悉Solidity的内联汇编,通过chatGPT翻译得到:

post image

sload(0)表示从存储器中加载位置0的值。 这里涉及到Ethereum Virtual Machine (EVM)储存布局。简单来说就是合约中的全局变量是按顺序标记的。这是ERC20合约代码例子:

post image

同样constant修饰因为是确定值也是直接将值写死在代码中,不会占用插槽。 回头看ReceiverUnstoppable.sol中代码,发现ReetrancyGuard.sol中是第一个占用插槽。

post image

sload(0)指的就是全局变量locked。

post image
if eq(sload(0), 2) {//其实这句代码本意是防止重入攻击
      mstore(0x00, 0xed3ba6a6)
      revert(0x1c, 0x04)
}

通过挑战的关键就是UnstoppableVault.sol的104行的断言

if (convertToShares(totalSupply) != balanceBefore)
            revert InvalidBalance(); // enforce ERC4626 requirement

unstoppable.challenge.js,给金库转币token,上面的断言就无法通过,金库的闪电贷功能就废了。

解决方案

it('Execution', async function () {
        /** CODE YOUR SOLUTION HERE */
        await token.transfer(vault.address, 1);
    });