EGD 시세조작 공격 사건의 원리 분석 - 팔콘+이더스캔

기타 관련 콘텐츠는 개인 홈페이지 에서 확인할 수 있습니다.

EGD 공격 관련 정보

BSC에서 발생

EGD-Finance 코드분석 및 공격과정 설명

EGD_Finance 계약에 대한 모든 호출은 프록시 계약을 통해 이루어집니다.

최종 계약서를 보려면 블록체인 브라우저에서 "프록시로 읽기"를 클릭하세요.

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 | 주소 0x93c175439726797dcee24d08e4ac9164e88e7aee | BscScan 임을 알 수 있습니다.

이미지-20231219162437653

보상을 사용하는 단계

주소 0xee0221d76504aec40f63ad7e36855eebf5ea5edd | BscScan 공격자는 harvest()해당 보상을 상환하기 위해 공격 계약의 함수를 호출합니다.phalcon의 거래 분석은 아래 그림과 같습니다.

이미지-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) call을 통해 200 USDT를 PancakeSwap빌린 뒤, 이전 플래시론 분석 글에서 낙관적 이체 및 콜백 공격 계약을 볼 수 있었습니다.swap()函数pancakeCall()函数

pancakecall()函数中또 다른 플래시 대출이 시작된 Pancake LPs(0xa361433e409adac1f87cdf133127585f8a93c67d) 에서 swap()函数424456 USDT를 빌린 후 다시 호출했는데 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의 가격은 한 주소에 있는 두 개의 토큰 수를 기준으로 계산됩니다. initialize()函数쌍 주소는 에서 얻습니다. 쌍 주소는 라우터 주소를 기준으로 계산됩니다. 라우터는 프록시 계약입니다. 블록체인 브라우저에서 볼 수 있습니다. pair의 주소가 팬케이크에 유동성을 제공하는 계약인 0xa361433e409adac1f87cdf133127585f8a93c67d라는 것이 낯익어 보입니다.

이 시점에서 우리는 공격이 성공한 이유를 발견했을 것입니다.

  • 사용자는 먼저 Pancake LPs플래시론을 통해 0xa361에서 대량의 USDT를 빌렸는데, 이로 인해 Pancake LPsUSDT와 EGD가 페어링되면서 EGD의 가격이 매우 저렴해졌습니다.
  • 이때 pancakeCall()回调函数사용자는 보상을 상환하고 있는데, 보상은 Pancake LPsEDG 가격에 있는 두 개의 토큰 수를 기준으로 계산되므로 EDG 가격은 매우 저렴합니다 rew. 초과 보상을 받습니다.

다음은 phalcon에 대한 후속 호출에 대한 간략한 소개입니다.

위에서 빌린 플래시론을 먼저 Pancake LPs반납한 후 해당 k-value 검증을 진행합니다. (상환금=원금+수수료 확인)

그런 다음 PancakeSwap: WBNB-BSC-USD 2빌린 플래시 대출에 대해 해당 승인 승인을 수행합니다.

swapExactTokensForTokensSupportingFeeOnTransferTokens 함수를 호출하여 획득한 모든 EGD를 USDT로 교환합니다.

그런 다음 PancakeSwap: WBNB-BSC-USD 2빌린 플래시 대출이 반환되고 해당 k 값이 확인됩니다.

결국 공격자는 36044 USDT의 이익을 얻었습니다.

추천

출처blog.csdn.net/m0_53689197/article/details/135101484