赞
踩
2023年2月2日,在ETH和BSC上的Orion Protocol项目被攻击,这里以ETH上攻击为例:
Eth上的攻击交易Ethereum Transaction Hash (Txhash) Details | Etherscan
从中我们可以看出,input data为单纯的函数签名,没有参数,只是调用了一个攻击函数
查看对应的phalcon调用序列:
depositAsset
函数,看一下该函数的源码: function depositAsset(address assetAddress, uint112 amount) external {
uint256 actualAmount = IERC20(assetAddress).balanceOf(address(this));
IERC20(assetAddress).safeTransferFrom(
msg.sender,
address(this),
uint256(amount)
);
actualAmount = IERC20(assetAddress).balanceOf(address(this)) - actualAmount;
generalDeposit(assetAddress, uint112(actualAmount));
}
generateDeposit
函数,这一步USDC的存款是为后续的攻击做准备。uniswapV2Call
函数swapThroughOrionPool
,orion Protocol提供的代币交换函数,代币兑换路径为[USDC, ATK, USDT],其中ATK为攻击者提前创建的恶意代币,将USDC兑换成USDTfunction swapThroughOrionPool( uint112 amount_spend, uint112 amount_receive, address[] calldata path, bool is_exact_spend ) public payable nonReentrant { bool isCheckPosition = LibPool.doSwapThroughOrionPool( IPoolFunctionality.SwapData({ amount_spend: amount_spend, amount_receive: amount_receive, is_exact_spend: is_exact_spend, supportingFee: false, path: path, orionpool_router: _orionpoolRouter, isInContractTrade: false, isSentETHEnough: false, isFromWallet: false, asset_spend: address(0) }), assetBalances, liabilities);
进一步调用PoolFunctionality 合约中的 doSwapThroughOrionPool 函数,仔细看一下函数源码,该函数进一步调用了_doSwapTokens()函数
上述代码中_doSwapTokens()函数时进行相应的输入,输出代币数量的计算,跟进该函数的实现
function _doSwapTokens(InternalSwapData memory swapData) internal returns (uint256 amountIn, uint256 amountOut) { bool isLastWETH = swapData.path[swapData.path.length - 1] == WETH; address toAuto = isLastWETH || swapData.curFactoryType == FactoryType.CURVE ? address(this) : swapData.to; uint256[] memory amounts; if (!swapData.supportingFee) { if (swapData.isExactIn) { amounts = OrionMultiPoolLibrary.getAmountsOut( swapData.curFactory, swapData.curFactoryType, swapData.amountIn, swapData.path ); require(amounts[amounts.length - 1] >= swapData.amountOut, "PoolFunctionality: IOA"); } else { amounts = OrionMultiPoolLibrary.getAmountsIn( swapData.curFactory, swapData.curFactoryType, swapData.amountOut, swapData.path ); require(amounts[0] <= swapData.amountIn, "PoolFunctionality: EIA"); } } else { amounts = new uint256[](1); amounts[0] = swapData.amountIn; } amountIn = amounts[0]; { uint256 curBalance; address initialTransferSource = swapData.curFactoryType == FactoryType.CURVE ? address(this) : OrionMultiPoolLibrary.pairFor(swapData.curFactory, swapData.path[0], swapData.path[1]); if (swapData.supportingFee) curBalance = IERC20(swapData.path[0]).balanceOf(initialTransferSource); IPoolSwapCallback(msg.sender).safeAutoTransferFrom( swapData.asset_spend, swapData.user, initialTransferSource, amountIn ); if (swapData.supportingFee) amounts[0] = IERC20(swapData.path[0]).balanceOf(initialTransferSource) - curBalance; } { uint256 curBalance = IERC20(swapData.path[swapData.path.length - 1]).balanceOf(toAuto); //计算转账前的余额 if (swapData.curFactoryType == FactoryType.CURVE) { _swapCurve(swapData.curFactory, amounts, swapData.path, swapData.supportingFee); } else if (swapData.curFactoryType == FactoryType.UNISWAPLIKE) { //这里的swap函数完成相应的代币兑换 _swap(swapData.curFactory, amounts, swapData.path, toAuto, swapData.supportingFee); } //将账户余额与转账前余额相减,得到新增的金额 amountOut = IERC20(swapData.path[swapData.path.length - 1]).balanceOf(toAuto) - curBalance; } require( swapData.amountIn == 0 || swapData.amountOut == 0 || amountIn * 1e18 / swapData.amountIn <= amountOut * 1e18 / swapData.amountOut, "PoolFunctionality: OOS" ); if (isLastWETH) { SafeTransferHelper.safeAutoTransferTo( WETH, address(0), swapData.to, amountOut ); } else if (swapData.curFactoryType == FactoryType.CURVE) { IERC20(swapData.path[swapData.path.length - 1]).safeTransfer(swapData.to, amountOut); } emit OrionPoolSwap( tx.origin, convertFromWETH(swapData.path[0]), convertFromWETH(swapData.path[swapData.path.length - 1]), swapData.amountIn, amountIn, swapData.amountOut, amountOut, swapData.curFactory ); }
function _swap( address curFactory, uint256[] memory amounts, address[] memory path, address _to, bool supportingFee ) internal { for (uint256 i; i < path.length - 1; ++i) { (address input, address output) = (path[i], path[i + 1]); IOrionPoolV2Pair pair = IOrionPoolV2Pair(OrionMultiPoolLibrary.pairFor(curFactory, input, output)); (address token0, ) = OrionMultiPoolLibrary.sortTokens(input, output); uint256 amountOut; if (supportingFee) { (uint reserve0, uint reserve1,) = pair.getReserves(); (uint reserveInput, uint reserveOutput) = input == token0 ? (reserve0, reserve1) : (reserve1, reserve0); uint256 amountIn = IERC20(input).balanceOf(address(pair)).sub(reserveInput); amountOut = OrionMultiPoolLibrary.getAmountOutUv2(amountIn, reserveInput, reserveOutput); } else { amountOut = amounts[i + 1]; } (uint256 amount0Out, uint256 amount1Out) = input == token0 ? (uint256(0), amountOut) : (amountOut, uint256(0)); address to = i < path.length - 2 ? OrionMultiPoolLibrary.pairFor(curFactory, output, path[i + 2]) : _to; pair.swap(amount0Out, amount1Out, to, new bytes(0)); } }
path序列中的[USDC, ATK, USDT],每两个代币对之间存在一个pair合约,即USDC转到ATK,ATK转到对应的USDT,实现对应的代币兑换,攻击者创建的pair对合约,这里通过相应的计算金融模型,得到对应的转账金额,调用pair合约中的swap函数,实现相应的代币转移。
由于pair对中的swap函数,进行相应的转账,需要调用ATK代币的转账函数,ATK是攻击者部署的恶意代币,攻击者可控,攻击者这里调用自身的deposit()函数,调用ExchangeWithAtomic合约的depositAsset函数,并将闪电贷得到的200多万USDT全部转进Orion Protocol的depositAsset()函数中
这时攻击者在ExchangeWithAtomic 合约中USDT的存款被记账为了200多万,原来ExchangeWithAtomic 合约的余额为200多万,两者数值相近(攻击者设计的)
而通过swapThroughOrionPool
函数中攻击者USDC兑换出多少的USDT最终是通过ExchangeWithAtomic 合约兑换前后的USDT余额计算的,相当于存入的200万USDT被认为是USDC兑换出来的,最后通过creditUserAssets 函数来更新ExchangeWithAtomic 维护的adress-balance的账本,攻击者被认为是存入了200+200万
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。