Defi安全--Zunami Protocol攻击事件分析
其它相关内容可见个人主页
1 Zunami攻击事件相关信息
2023.8.13发生在Ethereum上发生的攻击,存在两个攻击交易,具体信息如下:
-
攻击合约地址:Contract Address 攻击合约
-
攻击者地址:Zunami Protocol Exploiter
-
攻击交易hash1:Ethereum Transaction Hash (Txhash) Details | Etherscan
-
攻击交易hash2:Ethereum 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 finance,sushiswap以及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) {// don't 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变大
相关文章:
Defi安全--Zunami Protocol攻击事件分析
其它相关内容可见个人主页 1 Zunami攻击事件相关信息 2023.8.13发生在Ethereum上发生的攻击,存在两个攻击交易,具体信息如下: 攻击合约地址:Contract Address 攻击合约 攻击者地址:Zunami Protocol Exploiter 攻击…...
虾皮电商 电商平台:虾皮(Shopee)东南亚领先的电子商务平台
在当今数字化时代,电子商务平台的兴起改变了人们的购物方式。虾皮(Shopee)作为东南亚地区领先的电子商务平台,为消费者提供了便捷、多样化的购物体验。由新加坡的Sea Group(前称Garena)于2015年创立&#x…...
【降龙算法】基于QT插件机制实现一个机器视觉算法小框架
机器视觉行业有各种各样的拖拉拽框架,也叫做低代码平台,例如国内海康的VisionMaster: 一个机器视觉框架需要包含各种算法模块,日志窗口,图像显示窗口等等,【降龙算法】就是做了一个入门级的机器视觉算法框…...
智能路由器 端口映射 (UPnP) Padavan内网端口映射配置方法
新版本Padavan 4.4内核的端口映射配置和老版本的不太一样,因为新版本默认是启用的 UPnP端口映射, 同时默认使用的是 IGD UPnP自动端口映射, UPnP名词解释: UPnP通用即插即用,是一组协议的统称,是一种基于TCP/IP、UDP和HTTP的分布式、开放体系ÿ…...
MR-GCN
∘ Φ \circ_Φ ∘Φ denotes a convolution Let b l o c k d i a g blockdiag blockdiag(A) be a n1n3-by-n2n3 block diagonal matrix, f o l d fold fold indicate its inverse operator diagonal degree tensor D \mathcal{D} D 作者未提供代码...
Java http 响应式请求和非响应式请求有什么区别
在Java中,HTTP的响应式请求和非响应式请求有以下区别: HTTP协议本身并不直接支持响应式请求,因为HTTP是基于请求-响应模型的。然而,可以通过使用其他技术和协议来实现响应式请求。 响应方式:响应式请求是指使用响应式编…...
CHS_06.2.1.6_2+线程的实现方式和多线程模型
CHS_06.2.1.6_2线程的实现方式和多线程模型 知识总览线程的实现方式用户级线程(User-Level Thread, ULT)内核级线程 多线程模型一对一模型多对一多对多模型 知识回顾 在上个小节中 我们学习了线程相关的一些基本概念 基础的知识 那这个小节中 我们回来看…...
k8s集群配置NodeLocal DNSCache
一、简介 当集群规模较大时,运行的服务非常多,服务之间的频繁进行大量域名解析,CoreDNS将会承受更大的压力,可能会导致如下影响: 延迟增加:有限的coredns服务在解析大量的域名时,会导致解析结果…...
Superpoint Transformer for 3D Scene Instance Segmentation
Abstract 现有的大多数方法通过扩展用于3D物体检测或3D语义分割的模型来实现3D实例分割。然而,这些非直接的方法存在两个缺点:1) 不精确的边界框或不令人满意的语义预测限制了整体3D实例分割框架的性能。2) 现有方法需要一个耗时的中间聚合步骤。为了解决这些问题,本文提出…...
adb调试软件下载 及 常用调试命令
一、软件下载 Windows版本:下载 Mac版本:下载 Linux版本:下载 二、常见调试命令 进入ADB调试 在文件路径栏输入cmd,回车,即可进入adb调试。注意:以下3条不要登录设备 shell (一)显…...
变电站综合自动化监控系统在某物流园35kV变电站中应用
摘 要:Acrel-1000变电站综合自动化系统,是我司根据电力系统自动化及无人值守的要求,总结国内外的研究和生产的先进经验,专门研制出的新一代电力监控系统。本系统具有保护、遥测、遥信、遥脉、遥调、遥控功能,可实现无人…...
技术的本质,是解决需求
分享下今日朋友圈精华内容。 很多人在初学时,走了很多弯路,一味追求热门、高性能、高复杂的芯片,或者系统,学了一堆东西,最后连个简单的功能,都实现不了。 大概是忘了,学技术的本质,…...
【刷题】leetcode 1 . 两数之和
两数之和 两数之和1 思路一 (简单突破)2 思路二 (进行优化)3 思路三 (哈希表 我还不会) 谢谢阅读Thanks♪(・ω・)ノ下一篇文章见!!! 两数…...
Sip - Ubuntu 配置 miniSIPServer 服务器(测试用)
客户提供的账号过期了,简单搭建 SIP 服务器,以便测试使用。个人认为这个配置起来最为简单,且测试功能足够。 官网miniSIPServer - 基于 Windows 以及 Linux 平台的 VoIP (SIP) 服务器软件. miniSIPServer 可能是最容易使用的 VoIP(SIP) 服务器…...
SpringCloud openFeign 之 获取被调用服务名
SpringCloud openFeign 之 获取被调用服务名 一. 概述 低版本 feign 只能获取到被调用方法的信息。 只有高版本 feign 才支持获取到被调用服务的信息。 二. 代码实现 package com.zxguan.springcloud2.template.user;import com.zxguan.springcloud2.template.user.config…...
ChatGPT和文心一言哪个更好用?
ChatGPT和文心一言都是基于深度学习技术的自然语言处理模型,它们各自具有优势和局限性,需要根据具体需求进行选择。以下是两者的比较: 算力:ChatGPT由OpenAI开发,具有强大的文本生成能力和语言理解能力,其训…...
第07章_面向对象编程(进阶)拓展练习(关键字:this,继承性和方法重写,关键字:super,多态性,Object类)
文章目录 第07章_面向对象编程(进阶)拓展练习01-关键字:this1、Circle类2、MyDate类3、Card类 02-继承性和方法重写4、Person、Student、Teacher类5、DepositCard、CreditCard类6、Employee、Programmer、Designer、Architect类7、判断输出结…...
小米路由器有线中继模式设置固定IP
第一步 小米路由器切换为有线中继模式后,进电脑版web管理界面,点击中继设置,把web页面地址中apsetting修改为setting(如下)后按回车键加载新页面。 修改前: http://192.168.1.168/cgi-bin/luci/;stokxxxx…...
用python实现给出关键字查找并标注pdf文件中关键字
要在Python中标注PDF文件中的关键字,可以使用Python的PDFMiner库和Python的matplotlib库。 首先,需要安装这两个库。可以使用pip命令进行安装: shell 复制代码 pip install pdfminer.six matplotlib 接下来,可以使用以下代码实现…...
postman自动化接口测试
背景描述 有一个项目要使用postman进行接口测试,接口所需参数有: appid: 应用标识;sign:请求签名,需要使用HMACSHA1加密算法计算,签名串是:{appid}${url}${stamp};stamp࿱…...
Minecraft启动器与游戏配置工具全攻略:从新手到大师的进阶指南
Minecraft启动器与游戏配置工具全攻略:从新手到大师的进阶指南 Minecraft启动器是每一位玩家进入方块世界的第一道门,而一款优秀的游戏配置工具则能让你的冒险之旅更加顺畅。本文将以玩家视角,带你深入了解如何利用PCL2-CE这款强大的开源工具…...
Java Web 入门学习笔记:Servlet 请求响应与登录功能实战
一、开篇:今日学习核心与目标学习背景:Java Web 入门阶段核心 ——Servlet 与 HTTP 请求响应交互学习目标:掌握 HttpServletRequest/HttpServletResponse 核心用法实现「登录表单提交→Servlet 验证→页面重定向」完整流程解决实操中 404、中…...
深入Linuxptp:ptp4l与E2E模式下的状态机与报文处理流程剖析
1. Linuxptp与ptp4l基础认知 第一次接触PTP协议时,我被那些专业术语搞得晕头转向。直到在实验室里用示波器抓到实际报文,才真正理解这个时间同步协议的精妙之处。Linuxptp作为开源实现,其中的ptp4l守护进程就像个尽职的交通警察,协…...
降AI率工具哪个好用知网维普万方分开对比
很多同学只关心"知网通没通过",但2026年越来越多学校开始同时要求知网和维普双重检测,部分学校还加了万方。 问题在于:一款工具在知网效果好,不代表在维普和万方也同样好。这是因为三个平台的AIGC检测算法不同。 这篇…...
测试缺陷类型词云图分析:聚焦“需求理解错误”
在软件质量保障的浩瀚星图中,缺陷是不可避免的阴影。通过对海量缺陷报告进行文本挖掘与可视化分析,一张揭示问题本质的“词云图”便清晰浮现。在这张图上,若“需求理解错误”一词以其巨大、醒目的字体高频占据中心,它便不再是一个…...
FGA开源工具问题速解:从入门到精通的实战指南
FGA开源工具问题速解:从入门到精通的实战指南 【免费下载链接】FGA Auto-battle app for F/GO Android 项目地址: https://gitcode.com/gh_mirrors/fg/FGA 一、基础配置问题:3步定位法解决启动障碍 1.1 权限访问失败:系统级操作权限配…...
教育培训品牌视觉体系全攻略:5步打造统一、专业、让人过目不忘的品牌形象
教育培训机构的品牌视觉是否混乱,直接影响家长和学员的第一印象。宣传海报用一种蓝,公众号封面又是另一种蓝,课程介绍册的字体也和官网不一样。这种视觉不统一的问题,会让品牌显得不够专业,降低信任感。今天分享一套用…...
告别本地卡顿:用PyCharm专业版SSH连接远程服务器,把算力搬到云端(附环境配置避坑点)
告别本地卡顿:用PyCharm专业版SSH连接远程服务器,把算力搬到云端(附环境配置避坑点) 当你的笔记本风扇开始像喷气发动机一样轰鸣,而TensorFlow模型训练进度条却像蜗牛爬行时,是时候考虑把开发环境搬到云端了…...
告别手动重启!用宝塔PM2管理器实现Node.js热更新(2023最新配置指南)
2023终极指南:用宝塔PM2打造Node.js热更新开发流水线 每次保存代码都要手动重启服务?还在为部署中断用户体验而头疼?作为经历过数百次深夜紧急部署的全栈开发者,我总结出一套零中断热更新方案。只需15分钟配置,让你的N…...
基于NLP-StructBERT的智能客服语义匹配实战:Java微服务集成
基于NLP-StructBERT的智能客服语义匹配实战:Java微服务集成 你有没有遇到过这种情况?用户问“我的订单怎么还没发货”,而你的知识库里只有“订单发货状态查询”这样的标准问题。传统的关键词匹配,比如搜索“订单”和“发货”&…...
