其它相關(guān)內(nèi)容可見個(gè)人主頁(yè)
1 Zunami攻擊事件相關(guān)信息
2023.8.13發(fā)生在Ethereum上發(fā)生的攻擊,存在兩個(gè)攻擊交易,具體信息如下:
-
攻擊合約地址:Contract Address 攻擊合約
-
攻擊者地址:Zunami Protocol Exploiter
-
攻擊交易hash1:Ethereum Transaction Hash (Txhash) Details | Etherscan
-
攻擊交易hash2:Ethereum Transaction Hash (Txhash) Details | Etherscan
-
phalcon分析調(diào)用序列:0x0788ba222970c7c68a | Phalcon Explorer (blocksec.com)
2 攻擊流程詳解
項(xiàng)目介紹
Zunami是穩(wěn)定幣投資聚合器,用戶給定用ETH/USDC/DAI等穩(wěn)定幣投資Zunami協(xié)議;
然后Zunami協(xié)議會(huì)使用用戶質(zhì)押的代幣到Curve中高收益的池子進(jìn)行質(zhì)押;
那么為了保證更進(jìn)一步的收益,Zunami還會(huì)把Curve的流動(dòng)性再次質(zhì)押到StakeDAO和Convex平臺(tái)中,吃兩波流動(dòng)性獎(jiǎng)勵(lì)。
然后把收到的流動(dòng)性獎(jiǎng)勵(lì)代幣(CRV)經(jīng)過用戶的質(zhì)押比例返回給用戶。
zETH是Zunami協(xié)議實(shí)現(xiàn)的變基代幣(rebase token),變基代幣的邏輯是因?yàn)樗拇鷰艛?shù)量計(jì)算是錨定了Zunami所有的資產(chǎn)來計(jì)算的,所以可以通過閃電貸對(duì)Zunami質(zhì)押的池子買入賣出就可以影響zETH的數(shù)量計(jì)算
攻擊流程
兩次攻擊交易是單獨(dú)的,但是基于的漏洞及原理是一致的
以0x0788ba222970c7c68a738b0e08fb197e669e61f9b226ceec4cab9b85abe8cceb攻擊交易為例進(jìn)行分析
- 對(duì)攻擊交易進(jìn)行調(diào)用序列分析,直接調(diào)用攻擊合約中的函數(shù);先查看了Balancer: Vault賬戶中USDC的余額
- 隨后攻擊者就調(diào)用UniswapV3中USDC-USDT對(duì)應(yīng)的閃電貸函數(shù),借出了7 e12wei的USDT;隨后查看pair對(duì)池子中的USDC和USDT的余額,樂觀轉(zhuǎn)賬會(huì)將對(duì)應(yīng)借貸轉(zhuǎn)給用戶。
- 閃電貸會(huì)回調(diào)攻擊者的
uniswapV3FlashCallback
函數(shù),回調(diào)中攻擊者調(diào)用Balancer: Vault的flashloan函數(shù),這里可以看一下這里的函數(shù)源碼
function flashLoan(
IFlashLoanRecipient recipient,
IERC20[] memory tokens,
uint256[] memory amounts,
bytes memory userData
) external override nonReentrant whenNotPaused
看了一下源碼,其功能無特別之處,就是一個(gè)閃電貸函數(shù),不過這個(gè)函數(shù)可以一次借貸多個(gè)代幣,用tokens
和amounts
表示對(duì)應(yīng)的數(shù)組,先后樂觀轉(zhuǎn)賬,后回調(diào)攻擊者,在進(jìn)行還款
- 在Balancer: Vault的flashloan函數(shù)中,會(huì)查看對(duì)應(yīng)的余額,進(jìn)行相應(yīng)的樂觀轉(zhuǎn)賬,隨后會(huì)再次回調(diào)到攻擊者的
receiveFlashLoan
函數(shù),此時(shí)用戶已經(jīng)通過借貸獲得了大量的USDT、USDC以及ETH
-
隨后攻擊者給curve finance,sushiswap以及uniswap上很多factory和router合約地址進(jìn)行相應(yīng)的代幣授權(quán),并調(diào)用Curve Finance: Swap用USDC給池子中添加流動(dòng)性,獲得crvFRAX
-
隨后調(diào)用Curve.fi Factory Pool中的一些pair對(duì)的exchange()函數(shù),DEX智能合約的代幣交換功能,可以把vyper代碼直接放到GPT中解析,可以理解為就算進(jìn)行代幣的交換,攻擊者將對(duì)應(yīng)的crvFRAX兌換為Zunami UZD,將USDC兌換為crvUSD。此時(shí)用戶擁有UZD和crvUSD
-
攻擊再次調(diào)用exchange()函數(shù),將所有的crvUSD兌換為對(duì)應(yīng)的UZD,最后攻擊者擁有4873316數(shù)量的UZD,并且將自身的ETH換成對(duì)應(yīng)的SDT,并且將全部的SDT轉(zhuǎn)到
MIMCurveStakeDao
中(為什么要進(jìn)行這樣一個(gè)存款,可能跟攻擊行為有關(guān)) -
隨后調(diào)用SushiSwap: Router的
swapExactTokensForTokens
函數(shù),進(jìn)行代幣的交換,攻擊者首先將自身的WETH兌換為對(duì)應(yīng)的SDT,隨后將步驟2中通過閃電貸獲得的USDT全部?jī)稉Q為WETH -
攻擊者調(diào)用UZD合約的cacheAssetPrice()函數(shù),仔細(xì)看一下函數(shù)源碼,獲得UZD緩存的資產(chǎn)價(jià)格,源碼如下:
function cacheAssetPrice() public virtual {
_blockCached = block.number;
uint256 currentAssetPrice = assetPrice();
if (_assetPriceCached < currentAssetPrice) {
_assetPriceCached = currentAssetPrice;
emit CachedAssetPrice(_blockCached, _assetPriceCached);
}
}
- 可以看出對(duì)應(yīng)的
_assetPriceCached
的價(jià)格是由assetPrice()決定的,進(jìn)一步閱讀函數(shù)源碼
function assetPrice() public view override returns (uint256) {
return priceOracle.lpPrice();
}
進(jìn)一步閱讀etherscan上源碼,可得priceOracle地址為0x2ffCC661011beC72e1A9524E12060983E74D14ce,查看該合約的lpPrice()
函數(shù)。
function lpPrice() external view returns (uint256) {
return (totalHoldings() * 1e18) / totalSupply();
}
價(jià)格取決于totalHoldings()
函數(shù),totalSupply()
為ERC標(biāo)準(zhǔn)函數(shù)
function totalHoldings() public view returns (uint256) {
uint256 length = _poolInfo.length;
uint256 totalHold = 0;
for (uint256 pid = 0; pid < length; pid++) {
totalHold += _poolInfo[pid].strategy.totalHoldings();
}
return totalHold;
}
這個(gè)會(huì)取決于每個(gè)_poolInfo[pid].strategy的Holdings()函數(shù),這里我們?nèi)タ?code>MIMCurveStakeDao對(duì)應(yīng)的函數(shù),源碼如下所示:
function totalHoldings() public view virtual returns (uint256) {
uint256 crvLpHoldings = (vault.liquidityGauge().balanceOf(address(this)) * getCurvePoolPrice()) /
CURVE_PRICE_DENOMINATOR;
uint256 sdtEarned = vault.liquidityGauge().claimable_reward(address(this), address(_config.sdt));
uint256 amountIn = sdtEarned + _config.sdt.balanceOf(address(this));
uint256 sdtEarningsInFeeToken = priceTokenByExchange(amountIn, _config.sdtToFeeTokenPath);
uint256 crvEarned = vault.liquidityGauge().claimable_reward(address(this), address(_config.crv));
amountIn = crvEarned + _config.crv.balanceOf(address(this));
uint256 crvEarningsInFeeToken = priceTokenByExchange(amountIn, _config.crvToFeeTokenPath);
uint256 tokensHoldings = 0;
for (uint256 i = 0; i < 3; i++) {
tokensHoldings += _config.tokens[i].balanceOf(address(this)) * decimalsMultipliers[i];
}
return
tokensHoldings +
crvLpHoldings +
(sdtEarningsInFeeToken + crvEarningsInFeeToken) *
decimalsMultipliers[feeTokenId];
}
function priceTokenByExchange(uint256 amountIn, address[] memory exchangePath)
internal
view
returns (uint256)
{
if (amountIn == 0) return 0;
uint256[] memory amounts = _config.router.getAmountsOut(amountIn, exchangePath);
return amounts[amounts.length - 1];
}
重點(diǎn)關(guān)注sdtEarningsInFeeToken,因?yàn)楣粽咴诖酥?,給該合約存入了大量的SDT,仔細(xì)看一下priceTokenByExchange()
函數(shù)
進(jìn)一步可以去SushiSwap: Router中查看getAmountsOut()
函數(shù),發(fā)現(xiàn)其返回值與amountIn正相關(guān),amountIn的值一定程度上取決于該合約當(dāng)前的SDT余額,而攻擊者在此之前給該地址存入了大量的SDT,最終導(dǎo)致sdtEarningsInFeeToken數(shù)量過高,CachedAssetPrice價(jià)格過高
- 隨后攻擊者調(diào)用SushiSwap: Router的
swapExactTokensForTokens
函數(shù),將SDT轉(zhuǎn)化為WETH,將WETH換成USDT - 隨后調(diào)用UZD合約中的balanceOf函數(shù),發(fā)現(xiàn)其依賴于被操縱的cacheAssetPrice價(jià)格,具體如下:
function balanceOf(address account) public view virtual override returns (uint256) {
if (!containRigidAddress(account)) return super.balanceOf(account);
return _balancesRigid[account];
}
function balanceOf(address account) public view virtual override returns (uint256) {
// don't cache price
return _convertFromNominalCached(_balances[account], Math.Rounding.Down);
}
function _convertFromNominalWithCaching(uint256 nominal, Math.Rounding rounding)
internal
virtual
returns (uint256 value)
{
if (nominal == type(uint256).max) return type(uint256).max;
_cacheAssetPriceByBlock();
return nominal.mulDiv(assetPriceCached(), DEFAULT_DECIMALS_FACTOR, rounding);
}
所以其會(huì)錯(cuò)誤計(jì)算攻擊者的UZD余額,這時(shí)攻擊者進(jìn)行相應(yīng)的套利即可
-
通過Curve.fi Factory Pool的exchange函數(shù),先將錯(cuò)誤余額數(shù)量的UZD,一部分兌換為crvFRAX,另一部分兌換為crvUSD。
移除Curve Finance: Swap中的流動(dòng)性,攻擊者獲得對(duì)應(yīng)的FRAX和USDC。
調(diào)用exchange函數(shù),將對(duì)應(yīng)的FRAX和crvUSD兌換城USDC
并且最后將大部分的USDC全部?jī)稉Q成USDT,現(xiàn)在攻擊者資產(chǎn)為USDT和USDC。
文章來源:http://www.zghlxwxcb.cn/news/detail-795360.html
- 調(diào)用WETH-USDCpair對(duì)的閃電貸,獲得大量的WETH,攻擊者償還相應(yīng)數(shù)量的USDC,并償還第2步中Balancer: Vault閃電貸借貸的WETH和USDC,最后償還第一步中uniswapV3借貸的USDT
- 最后償還完閃電貸后,攻擊者獲得資產(chǎn)USDT和WETH,將其全部提取完成攻擊。
再簡(jiǎn)單看一下另一個(gè)攻擊交易0x2aec4fdb2a09ad4269a410f2c770737626fb62c54e0fa8ac25e8582d4b690cca文章來源地址http://www.zghlxwxcb.cn/news/detail-795360.html
- 也是先調(diào)用攻擊合約,后進(jìn)行閃電貸,借出WETH,然后通過curve finance將eth兌換成zETH
- 將ETH兌換成CRV,存入sEthFraxEthCurveConvex合約中,與上述相同,攻擊者賬戶的zETH余額和sEthFraxEthCurveConvex合約中的CRV余額相關(guān),攻擊者通過多次在wETH/CRV在池子中兌換CRV,操縱了CRV的價(jià)格和漏洞合約的CRV余額,最終導(dǎo)致CachedAssetPrice變大
到了這里,關(guān)于Defi安全--Zunami Protocol攻擊事件分析的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!