其它相關(guān)文章可見(jiàn)個(gè)人主頁(yè)
1. Orion Protocol攻擊事件相關(guān)信息
2023年2月2日,在ETH和BSC上的Orion Protocol項(xiàng)目被攻擊,這里以ETH上攻擊為例:
- 攻擊合約地址:Attacker Contract Address | Etherscan
- 攻擊者地址:Orion Protocol Exploiter 2 | Address
- 攻擊交易:Ethereum Transaction Hash (Txhash) Details | Etherscan
- Phalcon調(diào)用序列分析:0xa6f63fcb6bec881886 | Phalcon Explorer (blocksec.com)
2. Orion Protocol攻擊事件分析
攻擊流程詳解
Eth上的攻擊交易Ethereum Transaction Hash (Txhash) Details | Etherscan
從中我們可以看出,input data為單純的函數(shù)簽名,沒(méi)有參數(shù),只是調(diào)用了一個(gè)攻擊函數(shù)
查看對(duì)應(yīng)的phalcon調(diào)用序列:
- 先進(jìn)行了一系列基礎(chǔ)操作,對(duì)Orion Protocol項(xiàng)目合約進(jìn)行一系列的代幣授權(quán)approve()操作,如USDT和USDC等。
- 隨后我們可以看到攻擊者調(diào)用了Orion Protocol的
depositAsset
函數(shù),看一下該函數(shù)的源碼:
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));
}
- 攻擊者向orion Protocol合約轉(zhuǎn)入對(duì)應(yīng)數(shù)量的USDC,將該合約轉(zhuǎn)賬前后的代幣余額,作為用戶存款的數(shù)量,并調(diào)用
generateDeposit
函數(shù),這一步USDC的存款是為后續(xù)的攻擊做準(zhǔn)備。 - 攻擊者調(diào)用Uniswap V2: USDT的閃電貸函數(shù),借出200多萬(wàn)個(gè)USDT
- 調(diào)用uniswapv2的閃電貸函數(shù),借貸對(duì)應(yīng)的USDT,樂(lè)觀轉(zhuǎn)賬,先將對(duì)應(yīng)的USDT轉(zhuǎn)賬給了攻擊者,后回調(diào)攻擊者的
uniswapV2Call
函數(shù) - 回調(diào)函數(shù)中,因?yàn)楣粽呦惹按嫒肓薝SDC,現(xiàn)在攻擊者調(diào)用了orion Protocol項(xiàng)目ExchangeWithAtomic合約中的一個(gè)函數(shù)
swapThroughOrionPool
,orion Protocol提供的代幣交換函數(shù),代幣兌換路徑為[USDC, ATK, USDT],其中ATK為攻擊者提前創(chuàng)建的惡意代幣,將USDC兌換成USDT - 隨后調(diào)用LibPool的doSwapThroughOrionPool的函數(shù),再調(diào)用PoolFunctionality 合約中的doSwapThroughOrionPool函數(shù)
function 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);
-
進(jìn)一步調(diào)用PoolFunctionality 合約中的 doSwapThroughOrionPool 函數(shù),仔細(xì)看一下函數(shù)源碼,該函數(shù)進(jìn)一步調(diào)用了_doSwapTokens()函數(shù)
-
上述代碼中_doSwapTokens()函數(shù)時(shí)進(jìn)行相應(yīng)的輸入,輸出代幣數(shù)量的計(jì)算,跟進(jìn)該函數(shù)的實(shí)現(xiàn)
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);
//計(jì)算轉(zhuǎn)賬前的余額
if (swapData.curFactoryType == FactoryType.CURVE) {
_swapCurve(swapData.curFactory, amounts, swapData.path, swapData.supportingFee);
} else if (swapData.curFactoryType == FactoryType.UNISWAPLIKE) {
//這里的swap函數(shù)完成相應(yīng)的代幣兌換
_swap(swapData.curFactory, amounts, swapData.path, toAuto, swapData.supportingFee);
}
//將賬戶余額與轉(zhuǎn)賬前余額相減,得到新增的金額
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
);
}
- 這里進(jìn)行相應(yīng)的代幣兌換,之前的兌換path為[USDC, ATK, USDT],這里通過(guò)PoolFunctionality合約中的_swap()完成相應(yīng)的兌換,跟進(jìn) _swap()函數(shù)的源碼
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],每?jī)蓚€(gè)代幣對(duì)之間存在一個(gè)pair合約,即USDC轉(zhuǎn)到ATK,ATK轉(zhuǎn)到對(duì)應(yīng)的USDT,實(shí)現(xiàn)對(duì)應(yīng)的代幣兌換,攻擊者創(chuàng)建的pair對(duì)合約,這里通過(guò)相應(yīng)的計(jì)算金融模型,得到對(duì)應(yīng)的轉(zhuǎn)賬金額,調(diào)用pair合約中的swap函數(shù),實(shí)現(xiàn)相應(yīng)的代幣轉(zhuǎn)移。
-
由于pair對(duì)中的swap函數(shù),進(jìn)行相應(yīng)的轉(zhuǎn)賬,需要調(diào)用ATK代幣的轉(zhuǎn)賬函數(shù),ATK是攻擊者部署的惡意代幣,攻擊者可控,攻擊者這里調(diào)用自身的deposit()函數(shù),調(diào)用ExchangeWithAtomic合約的depositAsset函數(shù),并將閃電貸得到的200多萬(wàn)USDT全部轉(zhuǎn)進(jìn)Orion Protocol的depositAsset()函數(shù)中
-
這時(shí)攻擊者在ExchangeWithAtomic 合約中USDT的存款被記賬為了200多萬(wàn),原來(lái)ExchangeWithAtomic 合約的余額為200多萬(wàn),兩者數(shù)值相近(攻擊者設(shè)計(jì)的)
-
而通過(guò)
swapThroughOrionPool
函數(shù)中攻擊者USDC兌換出多少的USDT最終是通過(guò)ExchangeWithAtomic 合約兌換前后的USDT余額計(jì)算的,相當(dāng)于存入的200萬(wàn)USDT被認(rèn)為是USDC兌換出來(lái)的,最后通過(guò)creditUserAssets 函數(shù)來(lái)更新ExchangeWithAtomic 維護(hù)的adress-balance的賬本,攻擊者被認(rèn)為是存入了200+200萬(wàn)
文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-813128.html
- 攻擊者進(jìn)行相應(yīng)的閃電貸還款,歸還借出的200多萬(wàn),獲利200多萬(wàn)
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-813128.html
- 調(diào)用閃電貸,借出WETH,歸還USDT,實(shí)現(xiàn)對(duì)應(yīng)的套利離場(chǎng)
攻擊事件發(fā)生的主要原因
- doswapThroughOrionPool 函數(shù),兌換路徑攻擊者可控,代幣類型攻擊者可控(惡意代幣)
- 兌換后更新賬本的記賬方式不正確,利用前后余額計(jì)算(×)
- 合約兌換功能的函數(shù)沒(méi)有做重入保護(hù)
3. 分析Orion Protocol攻擊事件所需信息
- 最關(guān)鍵的一點(diǎn),重入的發(fā)生回調(diào)不在這個(gè)攻擊合約之中,在攻擊者創(chuàng)建的惡意代幣合約之中(可能是這個(gè)案例的特殊情況)
- 普適的一點(diǎn):觸發(fā)惡意合約回調(diào)的功能,是在經(jīng)過(guò)5次外部函數(shù)調(diào)用后,才最終調(diào)用到攻擊者的惡意代幣合約中的函數(shù),在我們的工具中是無(wú)法獲得這樣的調(diào)用過(guò)程,全路徑覆蓋不太現(xiàn)實(shí)。
到了這里,關(guān)于Defi安全--Orion Protocol攻擊事件分析的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!