网站账号注册程序邯郸做网站推广的公司

当前位置: 首页 > news >正文

网站账号注册程序,邯郸做网站推广的公司,建筑网格化,遵义网上制作网站其它相关内容可见个人主页 1 Zunami攻击事件相关信息 2023.8.13发生在Ethereum上发生的攻击#xff0c;存在两个攻击交易#xff0c;具体信息如下#xff1a; 攻击合约地址#xff1a;Contract Address 攻击合约 攻击者地址#xff1a;Zunami Protocol Exploiter 攻击…其它相关内容可见个人主页 1 Zunami攻击事件相关信息 2023.8.13发生在Ethereum上发生的攻击存在两个攻击交易具体信息如下 攻击合约地址Contract Address 攻击合约 攻击者地址Zunami Protocol Exploiter 攻击交易hash1Ethereum Transaction Hash (Txhash) Details | Etherscan 攻击交易hash2Ethereum Transaction Hash (Txhash) Details | Etherscan phalcon分析调用序列0x0788ba222970c7c68a | Phalcon Explorer (blocksec.com)
2 攻击流程详解 项目介绍 Zunami是稳定币投资聚合器用户给定用ETH/USDC/DAI等稳定币投资Zunami协议 然后Zunami协议会使用用户质押的代币到Curve中高收益的池子进行质押 那么为了保证更进一步的收益Zunami还会把Curve的流动性再次质押到StakeDAO和Convex平台中吃两波流动性奖励。 然后把收到的流动性奖励代币(CRV)经过用户的质押比例返回给用户。 zETH是Zunami协议实现的变基代币(rebase token)变基代币的逻辑是因为他的代币数量计算是锚定了Zunami所有的资产来计算的所以可以通过闪电贷对Zunami质押的池子买入卖出就可以影响zETH的数量计算 攻击流程 两次攻击交易是单独的但是基于的漏洞及原理是一致的 以0x0788ba222970c7c68a738b0e08fb197e669e61f9b226ceec4cab9b85abe8cceb攻击交易为例进行分析 对攻击交易进行调用序列分析直接调用攻击合约中的函数先查看了Balancer: Vault账户中USDC的余额随后攻击者就调用UniswapV3中USDC-USDT对应的闪电贷函数借出了7 e12wei的USDT随后查看pair对池子中的USDC和USDT的余额乐观转账会将对应借贷转给用户。闪电贷会回调攻击者的uniswapV3FlashCallback函数回调中攻击者调用Balancer: Vault的flashloan函数这里可以看一下这里的函数源码 function flashLoan(IFlashLoanRecipient recipient,IERC20[] memory tokens,uint256[] memory amounts,bytes memory userData) external override nonReentrant whenNotPaused 看了一下源码其功能无特别之处就是一个闪电贷函数不过这个函数可以一次借贷多个代币用tokens和amounts表示对应的数组先后乐观转账后回调攻击者在进行还款 在Balancer: Vault的flashloan函数中会查看对应的余额进行相应的乐观转账随后会再次回调到攻击者的receiveFlashLoan函数此时用户已经通过借贷获得了大量的USDT、USDC以及ETH 随后攻击者给curve financesushiswap以及uniswap上很多factory和router合约地址进行相应的代币授权并调用Curve Finance: Swap用USDC给池子中添加流动性获得crvFRAX 随后调用Curve.fi Factory Pool中的一些pair对的exchange()函数DEX智能合约的代币交换功能可以把vyper代码直接放到GPT中解析可以理解为就算进行代币的交换攻击者将对应的crvFRAX兑换为Zunami UZD将USDC兑换为crvUSD。此时用户拥有UZD和crvUSD 攻击再次调用exchange()函数将所有的crvUSD兑换为对应的UZD最后攻击者拥有4873316数量的UZD,并且将自身的ETH换成对应的SDT并且将全部的SDT转到MIMCurveStakeDao中为什么要进行这样一个存款可能跟攻击行为有关 随后调用SushiSwap: Router的swapExactTokensForTokens函数进行代币的交换攻击者首先将自身的WETH兑换为对应的SDT随后将步骤2中通过闪电贷获得的USDT全部兑换为WETH 攻击者调用UZD合约的cacheAssetPrice()函数,仔细看一下函数源码获得UZD缓存的资产价格源码如下 function cacheAssetPrice() public virtual {_blockCached block.number;uint256 currentAssetPrice assetPrice();if (_assetPriceCached currentAssetPrice) {_assetPriceCached currentAssetPrice;emit CachedAssetPrice(_blockCached, _assetPriceCached);}}可以看出对应的_assetPriceCached的价格是由assetPrice()决定的进一步阅读函数源码 function assetPrice() public view override returns (uint256) {return priceOracle.lpPrice();}进一步阅读etherscan上源码可得priceOracle地址为0x2ffCC661011beC72e1A9524E12060983E74D14ce查看该合约的lpPrice()函数。 function lpPrice() external view returns (uint256) {return (totalHoldings() * 1e18) / totalSupply();}价格取决于totalHoldings()函数totalSupply()为ERC标准函数 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;}这个会取决于每个_poolInfo[pid].strategy的Holdings()函数这里我们去看MIMCurveStakeDao对应的函数源码如下所示 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];}returntokensHoldings crvLpHoldings (sdtEarningsInFeeToken crvEarningsInFeeToken) *decimalsMultipliers[feeTokenId];}function priceTokenByExchange(uint256 amountIn, address[] memory exchangePath)internalviewreturns (uint256){if (amountIn 0) return 0;uint256[] memory amounts _config.router.getAmountsOut(amountIn, exchangePath);return amounts[amounts.length - 1];}重点关注sdtEarningsInFeeToken因为攻击者在此之前给该合约存入了大量的SDT仔细看一下priceTokenByExchange()函数 进一步可以去SushiSwap: Router中查看getAmountsOut()函数发现其返回值与amountIn正相关amountIn的值一定程度上取决于该合约当前的SDT余额而攻击者在此之前给该地址存入了大量的SDT最终导致sdtEarningsInFeeToken数量过高CachedAssetPrice价格过高 随后攻击者调用SushiSwap: Router的swapExactTokensForTokens函数将SDT转化为WETH将WETH换成USDT随后调用UZD合约中的balanceOf函数发现其依赖于被操纵的cacheAssetPrice价格具体如下 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) {// dont cache pricereturn _convertFromNominalCached(_balances[account], Math.Rounding.Down);}function _convertFromNominalWithCaching(uint256 nominal, Math.Rounding rounding)internalvirtualreturns (uint256 value){if (nominal type(uint256).max) return type(uint256).max;_cacheAssetPriceByBlock();return nominal.mulDiv(assetPriceCached(), DEFAULT_DECIMALS_FACTOR, rounding);}所以其会错误计算攻击者的UZD余额这时攻击者进行相应的套利即可 通过Curve.fi Factory Pool的exchange函数先将错误余额数量的UZD一部分兑换为crvFRAX另一部分兑换为crvUSD。 移除Curve Finance: Swap中的流动性攻击者获得对应的FRAX和USDC。 调用exchange函数将对应的FRAX和crvUSD兑换城USDC 并且最后将大部分的USDC全部兑换成USDT现在攻击者资产为USDT和USDC。 调用WETH-USDCpair对的闪电贷获得大量的WETH攻击者偿还相应数量的USDC并偿还第2步中Balancer: Vault闪电贷借贷的WETH和USDC最后偿还第一步中uniswapV3借贷的USDT最后偿还完闪电贷后攻击者获得资产USDT和WETH将其全部提取完成攻击。 再简单看一下另一个攻击交易0x2aec4fdb2a09ad4269a410f2c770737626fb62c54e0fa8ac25e8582d4b690cca 也是先调用攻击合约后进行闪电贷借出WETH然后通过curve finance将eth兑换成zETH将ETH兑换成CRV存入sEthFraxEthCurveConvex合约中与上述相同攻击者账户的zETH余额和sEthFraxEthCurveConvex合约中的CRV余额相关攻击者通过多次在wETH/CRV在池子中兑换CRV操纵了CRV的价格和漏洞合约的CRV余额最终导致CachedAssetPrice变大