EGD価格操作攻撃事件の原理分析 - palcon+etherscan

その他の関連コンテンツは次の場所にあります:個人ホームページ

EGD 攻撃関連情報

BSCで起こる

EGD-Financeのコード解析と攻撃プロセスの説明

EGD_Finance コントラクトへの呼び出しはすべて、プロキシ コントラクトを通じて行われます。

ブロックチェーンブラウザで「Read as Proxy」をクリックすると、最終的な契約書が表示されます。

EGD Finance契約に実装されている主な機能は、USDTを一定期間担保することであり、銀行預金に相当する報酬EGDトークンを出金することができ、一定期間入金した後、利子を引き出すことができます。

以下のステーキング手順と償還報酬手順はすべて、攻撃者によって実際に開始されたトランザクション手順です。

誓約ステップ

アドレス 0xbc5e8602c4fba28d0efdbf3c6a52be455d9558f5 | BscScan は、攻撃コントラクトの関数を呼び出しstake()、対応する住宅ローン操作を実行します。このアドレスは、攻撃コントラクトを作成した攻撃者のアドレスでもあります。

特定のトランザクションは次のとおりです: BNB スマート チェーン トランザクション ハッシュ (Txhash) の詳細 | BscScan

Phalcon でこのトランザクションの特定の呼び出し情報を確認できます。

画像-20231219160115062

さらに詳しい分析は次のとおりです。

EGD_Finance | アドレス 0x93c175439726797dcee24d08e4ac9164e88e7aee | BscScanの関数bond()は、次の招待者を入力するだけです。web2 と同じである必要があります。各アドレスの招待者は、質権収入に関連しています。

function bond(address invitor) external {        
        require(userInfo[msg.sender].invitor == address(0), 'have invitor');
        require(userInfo[invitor].invitor != address(0) || invitor == fund, 'wrong invitor');
        userInfo[msg.sender].invitor = invitor;
        userInfo[invitor].refer ++;

    }

次のswapETHForExactTokens()呼び出しは、Defi で非常に一般的なトークン交換操作であり、アドレスからソース コードを見ると、uniswap_v2 の対応する関数と一致しています。

名前から、不確実な量のETHが一定数のトークンと交換されることがわかり、その交換がUSDTであることがわかります。

ユニスワップパラメータリスト

function swapETHForExactTokens(
    uint amountOut, // 交易获得的代币数量
    address[] calldata path, // 交易路径列表
    address to, // 交易获得的 token 发送到的地址
    uint deadline // 过期时间
) external virtual override payable ensure(deadline) returns (
    uint[] memory amounts // 交易期望数量列表
){
    ...
}

PancakeSwap: ルーター v2 | アドレス0x10ed43c718714eb63d5aa57b78b54704e256024e | Bscスキャン

    function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)
        external
        virtual
        override
        payable
        ensure(deadline)
        returns (uint[] memory amounts)
    {
    	//检查是否为WETH进行交换
        require(path[0] == WETH, 'PancakeRouter: INVALID_PATH');
        // 从library中获知得到amountOut数量的USDT,需要多少ETH
        amounts = PancakeLibrary.getAmountsIn(factory, amountOut, path);
        //发给pancake的ETH必须大于所需数量
        require(amounts[0] <= msg.value, 'PancakeRouter: EXCESSIVE_INPUT_AMOUNT');
        // 将 WETH 换成 ETH(对应phalcon的操作)
        IWETH(WETH).deposit{value: amounts[0]}();
        // 将 amounts[0] 数量的 path[0] 代币从用户账户中转移到 path[0], path[1] 的流动池
        assert(IWETH(WETH).transfer(PancakeLibrary.pairFor(factory, path[0], path[1]), amounts[0]));
        // 按 path 列表执行交易集合,不细究了,之后再详细看uniswap-qwq
        _swap(amounts, path, to);
        // 返回多余的ETH
        if (msg.value > amounts[0]) TransferHelper.safeTransferETH(msg.sender, msg.value - amounts[0]);
    }

最後に、stake()エージェント契約の関数が呼び出され、100 USDT が誓約されます。Phalcon の通話記録から、特定の関数コードは分析されないことがわかります。主に、その後の報酬計算に必要な多くの関連情報が記録されます。EGD_Finance | Address 0x93c175439726797dcee24d08e4ac9164e88e7aee | BscScan であることがわかります。

画像-20231219162437653

特典を引き換える手順

アドレス 0xee0221d76504aec40f63ad7e36855eebf5ea5edd | BscScan攻撃者は、harvest()攻撃コントラクトの関数を呼び出して、対応する報酬を引き換えます halcon のトランザクション分析は、次の図に示されています。

画像-20231219162808682

まず、コントラクトを呼び出して、calculateAll()函数ユーザーの誓​​約報酬とユーザーが得られる合計収入を計算します。

    function calculateReward(address addr, uint slot) public view returns (uint){
        UserSlot memory info = userSlot[addr][slot];
        if (info.leftQuota == 0) {
            return 0;
        }
        uint totalRew = (block.timestamp - info.claimTime) * info.rates;
        if (totalRew >= info.leftQuota) {
            totalRew = info.leftQuota;
        }
        return totalRew;
    }

その後、ユーザーはフラッシュ ローン操作を開始しました。

まず、 (0x16b9a82891338f9ba80e2d6970fdda79d1eb0dae) コールを通じて200 USDTPancakeSwapが借りられ、その後、楽観的転送とコールバック攻撃コントラクトが前のフラッシュ ローン分析記事で確認できました。swap()函数pancakeCall()函数

pancakecall()函数中別のフラッシュ ローンが開始された後、 424456 USDT がPancake LPs(0xa361433e409adac1f87cdf133127585f8a93c67d)から借用され、再度コールバックされました。これは、ユーザーが引き換える前に約束した報酬です。swap()函数pancakeCall()函数claimAllReward()函数

なぜここで報酬が償還されるのかは明らかですが、多額のフラッシュローンが報酬の計算方法に影響を与えるためであり、誓約報酬を計算する関数に抜け穴があるはずです。

次に、PancakeSwap: Router v2 | Address 0x10ed43c718714eb63d5aa57b78b54704e256024e | BscScanプロジェクトに移動してclaimAllReward()、特定のソース コードを確認し、詳細なコメントを作成します。

 function claimAllReward() external {
 		//判断是否存在对应的质押
        require(userInfo[msg.sender].userStakeList.length > 0, 'no stake');
        require(!black[msg.sender],'black');
        //获取质押时的,一系列质押记录,包括金额、时间戳等等
        uint[] storage list = userInfo[msg.sender].userStakeList;
        uint rew;
        uint outAmount;
        uint range = list.length;
        //计算对应的奖励
        for (uint i = 0; i < range; i++) {
            UserSlot storage info = userSlot[msg.sender][list[i - outAmount]];
            require(info.totalQuota != 0, 'wrong index');
            //不能超过一个最大奖励
            uint quota = (block.timestamp - info.claimTime) * info.rates;
            if (quota >= info.leftQuota) {
                quota = info.leftQuota;
            }
            //关键步骤,计算对应的奖励,仔细看一下getEGDPrice()函数
            //根据EGD的价格,来确定奖励多少EGD
            rew += quota * 1e18 / getEGDPrice();
            //下面是一些计算账户剩下最大奖励,以及账户余额(+利息)等操作
            info.claimTime = block.timestamp;
            info.leftQuota -= quota;
            info.claimedQuota += quota;
            if (info.leftQuota == 0) {
                userInfo[msg.sender].totalAmount -= info.totalQuota;
                delete userSlot[msg.sender][list[i - outAmount]];
                list[i - outAmount] = list[list.length - 1];
                list.pop();
                outAmount ++;
            }
        }
        //更新相应的质押列表
        userInfo[msg.sender].userStakeList = list;
        //发送响应的奖励
        EGD.transfer(msg.sender, rew);
        userInfo[msg.sender].totalClaimed += rew;
        emit Claim(msg.sender,rew);
    }
    function getEGDPrice() public view returns (uint){
    	//可在phalcon上看到行营的记录
        uint balance1 = EGD.balanceOf(pair);
        uint balance2 = U.balanceOf(pair);
        //EGD的价格仅仅是根据两种代币的实时数量(流动性)来进行计算,可以被攻击者操纵
        return (balance2 * 1e18 / balance1);
    }
    function initialize() public initializer {
        __Context_init_unchained();
        __Ownable_init_unchained();
        rate = [200, 180, 160, 140];
        startTime = block.timestamp;
        referRate = [6, 3, 1, 1, 1, 1, 1, 1, 2, 3];
        rateList = [547,493,438,383];
        dailyStakeLimit = 1000000 ether;
        wallet = 0xC8D45fF624F698FA4E745F02518f451ec4549AE8;
        fund = 0x9Ce3Aded1422A8c507DC64Ce1a0C759cf7A4289F;
        EGD = IERC20(0x202b233735bF743FA31abb8f71e641970161bF98);
        U = IERC20(0x55d398326f99059fF775485246999027B3197955);
        router = IPancakeRouter02(0x10ED43C718714eb63d5aA57B78B54704E256024E);
        pair = IPancakeFactory(router.factory()).getPair(address(EGD),address(U));
    }

EGD の価格は、1 つのアドレス上の 2 つのトークンの数に基づいて計算されますinitialize()函数。 でペアのアドレスを取得します。 ペアのアドレスは、ルーターのアドレスに基づいて計算されます。 ルーターはプロキシ契約です。 ブロックチェーン ブラウザー上で、ペアのアドレスが 0xa361433e409adac1f87cdf133127585f8a93c67d であることは、パンケーキに流動性を提供するコントラクトであるため、見覚えがあるように思えます。

この時点で、攻撃が成功した理由がわかったはずです?

  • ユーザーは最初にPancake LPsフラッシュ ローンを通じて 0xa361... から多額の USDT を借りました。これにより、Pancake LPsUSDT と EGD がペアになり、EGD の価格が非常に安くなりました。
  • この時点でpancakeCall()回调函数、ユーザーは報酬を引き換えています。報酬はPancake LPsEDG 価格の 2 つのトークンの数に基づいて計算されます。その結果、EDG の価格は非常に安くなります。これがrew見られる計算式であり、ユーザー過剰な報酬を受け取る。

以下は、palcon に対するその後の呼び出しの簡単な紹介です。

まず、Pancake LPs上記で借りたフラッシュ ローンを返却し、その後、対応する k 値検証を実行します (返済 = 元の金額 + 手数料であることを確認します)。

次に、PancakeSwap: WBNB-BSC-USD 2借りたフラッシュ ローンに対して対応する承認承認を実行します。

swapExactTokensForTokensSupportingFeeOnTransferTokens 関数を呼び出して、取得したすべての EGD を USDT に交換します

次に、PancakeSwap: WBNB-BSC-USD 2借りたフラッシュ ローンが返され、対応する k 値が検証されます。

最終的に、攻撃者は 36044 USDT の利益を得ました

おすすめ

転載: blog.csdn.net/m0_53689197/article/details/135101484