Defi安全-Mono攻击事件分析--etherscan+phalcon

MonoX攻击事件相关信息

在Ethereum和Polygon网络都发生了,攻击手段相同,以Ethereum为例进行分析:

  • 攻击者地址:MonoX Finance Exploiter | Address 0xecbe385f78041895c311070f344b55bfaa953258 | Etherscan

  • 攻击合约:Contract Address 0xf079d7911c13369e7fd85607970036d2883afcfd | Etherscan

  • 攻击交易:Ethereum Transaction Hash (Txhash) Details | Etherscan

  • 漏洞合约:Monoswap | Address 0x66e7d7839333f502df355f5bd87aea24bac2ee63 | Etherscan

Monox代码分析及攻击流程讲解

Monox介绍:

与Uniswap不同,其使用的是单边代币池模型,其使用vCash稳定币与AMM提供的代币创建虚拟的交易对。Monox创建的是代币-vCash交易对,添加流动性的时候,只需添加代币,进行任意代币兑换,兑换方式为:代币A -- vCash -- 代币B

攻击原理及过程:

极大地提高Monoswap中Mono代币的价格,后将拥有的Mono代币通过Monoswap换取代币。

具体步骤,查看phalcon上攻击交易的调用序列进行分析

  1. 前置阶段

image-20231223171211651

  • 首先调用WETHdeposit()函数,向WETH中存入0.1WETH
  • 随后调用approve()函数,向Monoswap进行授权,以便后续代币兑换正常进行(在foundry中写测试函数时,很容易遗忘approve这点)
  • 随后调用Monoswap的swapExactTokenForToken()函数,将0.1个WETH换成一定数量的Mono(该函数如何实现,可见漏洞合约Monoswap)
  • 调用Monoswap的pools()函数,具体后续介绍,获得Mono代币在Monoswap中的pid
  • 根据pid调用Monoxpool中的totalSupplyOf()函数,查询Mono-vCash池子中作为LP流动性证明的Mono总量。
  1. 移除用户流动性

image-20231223215453923

在Monox的官方界面可以看到给Mono代币提供代币流动的用户地址,这里从交易序列中可以很明显发现一个漏洞,别的用户的流动性,攻击者竟然可以任意移除

在Monoswap源码中可以很明显发现,并没有流动性所有者进行相应的校验

function _removeLiquidity (address _token, uint256 liquidity,address to) view public returns(uint256 poolValue, uint256 liquidityIn, uint256 vcashOut, uint256 tokenOut) {require (liquidity>0, "MonoX:BAD_AMOUNT");uint256 tokenBalanceVcashValue;uint256 vcashCredit;uint256 vcashDebt;PoolInfo memory pool = pools[_token];IMonoXPool monoXPoolLocal = monoXPool;uint256 lastAdded = monoXPoolLocal.liquidityLastAddedOf(pool.pid, msg.sender);require((lastAdded + (pool.status == PoolStatus.OFFICIAL ? 4 hours : pool.status == PoolStatus.LISTED ? 24 hours : 0)) <= block.timestamp, "MonoX:WRONG_TIME"); // Users are not allowed to remove liquidity right after addingaddress topLPHolder = monoXPoolLocal.topLPHolderOf(pool.pid);require(pool.status != PoolStatus.LISTED || msg.sender != topLPHolder || pool.createdAt + 90 days < block.timestamp, "MonoX:TOP_HOLDER & WRONG_TIME"); // largest LP holder is not allowed to remove LP within 90 days after pool creation(poolValue, tokenBalanceVcashValue, vcashCredit, vcashDebt) = getPool(_token);uint256 _totalSupply = monoXPool.totalSupplyOf(pool.pid);liquidityIn = monoXPool.balanceOf(to, pool.pid)>liquidity?liquidity:monoXPool.balanceOf(to, pool.pid);uint256 tokenReserve = IERC20(_token).balanceOf(address(monoXPool));if(tokenReserve < pool.tokenBalance){tokenBalanceVcashValue = tokenReserve.mul(pool.price)/1e18;}if(vcashDebt>0){tokenReserve = (tokenBalanceVcashValue.sub(vcashDebt)).mul(1e18).div(pool.price);}// if vcashCredit==0, vcashOut will be 0 as wellvcashOut = liquidityIn.mul(vcashCredit).div(_totalSupply);tokenOut = liquidityIn.mul(tokenReserve).div(_totalSupply);}

攻击者发现三个主要提供流动性的用户,先调用Monoxpool的balanceOf()函数查看地址在Monoswap中的Mono数量,后调用移除流动性函数,使得池子中的Mono为0.

  1. 添加流动性

攻击者自己添加极少的Mono代币到Monoswap中,获得927个LP,为后续拉升Mono的价格做准备

image-20231223220301534

  1. 拉高Mono代币在Monoswap中的价格

image-20231223220818186

攻击交易中,重复了55次上述行为

先是调用Monoswap中的pools()函数,从中我们可以看出solidity中这种mapping映射的获得,是通过调用函数的形式活动,可以看一下该函数返回的函数类型:

mapping (address => PoolInfo) public pools;struct PoolInfo {uint256 pid;uint256 lastPoolValue;address token;PoolStatus status;uint112 vcashDebt;uint112 vcashCredit;uint112 tokenBalance;uint256 price; // over 1e18uint256 createdAt; // timestamp}

这里重点关注的是我们可以通过调用该函数获得该代币在Monoswap中的tokenBalance余额和price当前价格,攻击交易这里主要想获得池子中的tokenBalance余额。

随后查看攻击者先前用0.1个WETH兑换的Mono代币的余额,即还剩多少个

随后最关键的步骤调用Monoswap的swapExactTokenForToken()函数,这个函数的功能与uniswap很像,顾名思义,将精准数量的代币兑换成一定数量的另一种代币,这里我们能够很明显发现,参数tokenIntokenOut都是Mono,这就是攻击手段!

所以肯定是该函数中存在漏洞,导致Mono代币价格的拉高。进入函数中看一下。

  function swapExactTokenForToken(address tokenIn,address tokenOut,uint amountIn,uint amountOutMin,address to,uint deadline) external virtual ensure(deadline) returns (uint amountOut) {amountOut = swapIn(tokenIn, tokenOut, msg.sender, to, amountIn);require(amountOut >= amountOutMin, 'MonoX:INSUFF_OUTPUT');}function swapIn (address tokenIn, address tokenOut, address from, address to,uint256 amountIn) internal lockToken(tokenIn) returns(uint256 amountOut)  {address monoXPoolLocal = address(monoXPool);amountIn = transferAndCheck(from,monoXPoolLocal,tokenIn,amountIn); // uint256 halfFeesInTokenIn = amountIn.mul(fees)/2e5;uint256 tokenInPrice;uint256 tokenOutPrice;uint256 tradeVcashValue;(tokenInPrice, tokenOutPrice, amountOut, tradeVcashValue) = getAmountOut(tokenIn, tokenOut, amountIn);uint256 oneSideFeesInVcash = tokenInPrice.mul(amountIn.mul(fees)/2e5)/1e18;// trading inif(tokenIn==address(vCash)){vCash.burn(monoXPoolLocal, amountIn);// all fees go to the other sideoneSideFeesInVcash = oneSideFeesInVcash.mul(2);}else{_updateTokenInfo(tokenIn, tokenInPrice, 0, tradeVcashValue.add(oneSideFeesInVcash), 0);}// trading outif(tokenOut==address(vCash)){vCash.mint(to, amountOut);}else{if (to != monoXPoolLocal) {IMonoXPool(monoXPoolLocal).safeTransferERC20Token(tokenOut, to, amountOut);}_updateTokenInfo(tokenOut, tokenOutPrice, tradeVcashValue.add(oneSideFeesInVcash), 0, to == monoXPoolLocal ? amountOut : 0);}if(pools[tokenIn].vcashDebt > 0 && pools[tokenIn].status == PoolStatus.OFFICIAL){_internalRebalance(tokenIn);}emit Swap(to, tokenIn, tokenOut, amountIn, amountOut, tradeVcashValue);}

swapIn函数较复杂,我们可以从后往前看,看到它有个_updateTokenInfo()函数,更新token的信息,看一下源码

  function _updateTokenInfo (address _token, uint256 _price,uint256 _vcashIn, uint256 _vcashOut, uint256 _ETHDebt) internal {uint256 _balance = IERC20(_token).balanceOf(address(monoXPool));_balance = _balance.sub(_ETHDebt);require(pools[_token].status!=PoolStatus.PAUSED,"MonoX:PAUSED");require(_balance <= uint112(-1));(uint initialPoolValue, , ,) = getPool(_token);pools[_token].tokenBalance = uint112(_balance);pools[_token].price = _price;// record last trade's block number in mapping: lastTradedBlocklastTradedBlock[_token] = block.number;_updateVcashBalance(_token, _vcashIn, _vcashOut);(uint poolValue, , ,) = getPool(_token);require(initialPoolValue <= poolValue || poolValue >= poolSizeMinLimit,"MonoX:MIN_POOL_SIZE");}

从代码中我们可以看出,将Monoswap池子中代币的数量和价格更新,其中代币的价格就是函数参数的tokenInPricetokenOutPrice,这两个参数都是通过getAmountOut()函数计算得到,进入该函数,分析源码:

function getAmountOut(address tokenIn, address tokenOut, uint256 amountIn) public view returns (uint256 tokenInPrice, uint256 tokenOutPrice, uint256 amountOut, uint256 tradeVcashValue) {require(amountIn > 0, 'MonoX:INSUFF_INPUT');uint256 amountInWithFee = amountIn.mul(1e5-fees)/1e5;address vcashAddress = address(vCash);uint tokenInPoolPrice = pools[tokenIn].price;uint tokenInPoolTokenBalance = pools[tokenIn].tokenBalance;if(tokenIn==vcashAddress){tradeVcashValue = amountInWithFee;tokenInPrice = 1e18;}else{require (tokenPoolStatus[tokenIn]==1, "MonoX:NO_POOL");// PoolInfo memory tokenInPool = pools[tokenIn];PoolStatus tokenInPoolStatus = pools[tokenIn].status;require (tokenInPoolStatus != PoolStatus.UNLISTED, "MonoX:POOL_UNLST");tokenInPrice = _getNewPrice(tokenInPoolPrice, tokenInPoolTokenBalance, amountInWithFee, 0, TxType.SELL);tradeVcashValue = _getAvgPrice(tokenInPoolPrice, tokenInPrice).mul(amountInWithFee)/1e18;}if(tokenOut==vcashAddress){amountOut = tradeVcashValue;tokenOutPrice = 1e18;}else{require (tokenPoolStatus[tokenOut]==1, "MonoX:NO_POOL");// PoolInfo memory tokenOutPool = pools[tokenOut];PoolStatus tokenOutPoolStatus = pools[tokenOut].status;uint tokenOutPoolPrice = pools[tokenOut].price;uint tokenOutPoolTokenBalance = pools[tokenOut].tokenBalance;require (tokenOutPoolStatus != PoolStatus.UNLISTED, "MonoX:POOL_UNLST");amountOut = tradeVcashValue.add(tokenOutPoolTokenBalance.mul(tokenOutPoolPrice).div(1e18));amountOut = tradeVcashValue.mul(tokenOutPoolTokenBalance).div(amountOut);bool allowDirectSwap=directSwapAllowed(tokenInPoolPrice,tokenOutPoolPrice,tokenInPoolTokenBalance,tokenOutPoolTokenBalance,tokenOutPoolStatus,true);// assuming p1*p2 = k, equivalent to uniswap's x * y = kuint directSwapTokenOutPrice = allowDirectSwap?tokenInPoolPrice.mul(tokenOutPoolPrice).div(tokenInPrice):uint(-1);// prevent the attack where user can use a small pool to update price in a much larger pooltokenOutPrice = _getNewPrice(tokenOutPoolPrice, tokenOutPoolTokenBalance, amountOut, 0, TxType.BUY);tokenOutPrice = directSwapTokenOutPrice < tokenOutPrice?directSwapTokenOutPrice:tokenOutPrice;amountOut = tradeVcashValue.mul(1e18).div(_getAvgPrice(tokenOutPoolPrice, tokenOutPrice));}}

通过上述代码可以得到,tokenInPricetokenOutPrice参数的计算都是通过_getNewPrice()函数,得到函数源码

  function _getNewPrice (uint256 originalPrice, uint256 reserve, uint256 delta, uint256 deltaBlocks, TxType txType) pure internal returns(uint256 price) {if(txType==TxType.SELL) {// no risk of being div by 0price = originalPrice.mul(reserve)/(reserve.add(delta));}else{ // BUYprice = originalPrice.mul(reserve).div(reserve.sub(delta));}}

通过,我们可以发现tokenIn代币,其TxType为SELL,tokenOut代币其Txtype为BUY。

故可分析,tokenIn代表先进行价格更新计算,originalPrice和reserve都是池子中原来保存的参数,其不会发生变动,相较于originalPrice价格,tokenInPrice变低了。

分析_getAvgPrice()函数,我们进一步可以分析得到trashVcashValue也变低了,其与toknInPrice呈相同趋势。

  function _getAvgPrice (uint256 originalPrice, uint256 newPrice) pure internal returns(uint256 price) {price = originalPrice.add(newPrice.mul(4))/5;}

随后,getAmountOut()函数正常执行,计算tokenOut代币的相关信息,分析_getNewPrice()函数,肯定可以得到的一个结论是相比于originalPrice也就是池子中代币的价格,tokenOutPrice变高的。

这时可以不用管其它参数的变化,这里最大的问题,就是这种同种代币的兑换,在swapIn()函数中,其先对tokenIn进行处理,更新代币相应的信息,但其后对tokenOut进行处理时,没有考虑前后兑换为同一种代币的情况,导致代币的价格被覆盖。

从上述分析中,可得到tokenOut的价格被抬升,tokenIn价格降低,但Mono的价格在兑换时,被覆盖,导致Mono价格异常增长。

对phalcon中兑换交易的参数分析可得,每次兑换的数量都是交易池中Mono的总量减去1,使得_getNewPrice()函数计算tokenOutPrice时,能够快速提升价格,这里也就不能理解第3步中添加流动性的时候,添加很少的Mono,确保攻击者有足够的余额拉高mono的价格。

  1. 转移非法资产

image-20231224105100083

攻击者先通过Monoswap查看池子中USDC的价格和余额,随后通过uniswap的USDC/WETH池接入WETH,乐观转账,在uniswapV2call()函数中调用Monoswap的swapTokenForExactToken()函数,将价格极高的Mono代币,换成一定数量的USDB,用以偿还uniswap闪电贷中的USDC(在uniswap闪电贷中,其可以通过还对应的pair代币),这样就将高价格的Mono代币转换成了对应的WETH(可以注意一下phalcon上这里的USDC数字,应该只是6位小数)。

随后的资产转移方式相同。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/487853.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

特斯拉自动驾驶靠自研芯片,国产智能汽车怎么办?

文章来源&#xff1a;量子位从这个月开始&#xff0c;特斯拉终于跌破30万&#xff0c;进入补贴行列&#xff0c;与国产汽车展开正面竞争。在国产汽车和特斯拉之间该如何选择&#xff0c;除了续航里程外&#xff0c;最重要的可能就是智能驾驶系统了。智能驾驶系统该怎么比较&…

【星·企业】竞逐卫星互联网“新基建”,「九天微星」完成2.7亿元B 轮融资

图为九天微星一箭七星“瓢虫系列”主星示意图来源&#xff1a;中科创星据了解&#xff0c;「九天微星」将用此轮融资在河北唐山和四川宜宾分别建设互联网卫星平台、载荷自动化产线&#xff0c;同时强化宽带通信系统研发能力&#xff0c;加速地面终端产品投产。九天微星卫星工厂…

3D原子映射有助于研究生命的起源

Chi Ma/Royal Ontario Museum来源&#xff1a;IEEE电气电子工程师一种相对较新的研究原子的技术可能会对地球上生命的起源提供更多的线索。科学家利用三维原子图&#xff08;特别是原子探针层析成像&#xff09;发现&#xff0c;太阳系中最古老的分子流体本可以支持氨基酸的发展…

混合现实未来的八大应用场景

来源&#xff1a;诚迈科技近日&#xff0c;福布斯发布了一篇关于混合现实未来的八个值得关注的领域和应用场景。文中提到&#xff1a;随着时间的推移&#xff0c;技术永远在不断进展&#xff0c;而未来技术中很可能是AR和VR的结合形式。有报告显示&#xff1a;2024年&#xff0…

4个重要的量子理论实验综述

文章原载于&#xff1a;大数据01 量子理论量子理论是一个容易让人迷惑的理论&#xff0c;因此&#xff0c;如果我们想准确地了解其概况&#xff0c;就需要非常谨慎。与大多数自牛顿著作和其他17世纪晚期科学家著作问世以来出现的物理学成果一样&#xff0c;量子理论是一个以数学…

前沿|《细胞》:绕过眼睛植入幻觉,科学家成功在盲人脑海中呈现指定图像!...

该文章内容转载自学术头条对于全球 5000 多万盲人来说&#xff0c;重见光明是一个遥不可及的梦想。而为了与盲人朋友进行交互&#xff0c;我们发明了盲文&#xff0c;用各种凸起的字符集合来表达各种意思。但这种通过手指触摸来识别的方式太过低效。随着脑科学研究的深入&#…

mysql error1045 yes,MySQLERROR1045(28000)错误的解决办法

错误现象&#xff1a;ERROR 1045 (28000): Access denied for userODBClocalhost(using password: NO)ERROR 1045 (28000): Access denied for userODBClocalhost(using password: YES)windows下&#xff0c;以上两个错误的解决方法&#xff0c;本人亲测有效&#xff0c;现分享…

软件测试常考面试题-软件测试面试宝典 ---面试过程中踩过的坑

问&#xff1a;你在测试中发现了一个 bug &#xff0c;但是开发经理认为这不是一个 bug &#xff0c;你应该怎样解决。 首先&#xff0c;将问题提交到缺陷管理库里面进行备案。 然后&#xff0c;要获取判断的依据和标准&#xff1a; 根据需求说明书、产品说明、设计文档等&am…

Soft robotics:造仿生昆虫机器人柔性骨骼新技术,只需2小时,成本不到7块!

来源&#xff1a;量子位原标题&#xff1a;只需2小时&#xff0c;成本不到7块&#xff0c;你我皆可制作的3D机器人机器人的骨架能像昆虫那般灵活、健壮吗&#xff1f;这个问题一直困扰着研究人员。以往&#xff0c;要么制作工艺太过复杂&#xff0c;耗的时间长&#xff1b;要么…

2019-2020年半导体行业深度报告

来源&#xff1a;华安证券新科技起点&#xff0c;不可缺芯半导体位于电子行业中游。通过集成电路、分立器件、被动器件在 PCB 上组合形 成模组&#xff0c;构成了手机、电脑、工业、航空航天、军事装备等电子产品的核心。这些产 品又直接影响到国家的发展、社会的进步以及个人的…

5G 智慧城市安全参考架构概述

来源&#xff1a;安全内参2020年5月12日&#xff0c;IMT-2020(5G)推进组安全工作组发布了《5G智慧城市安全需求与架构白皮书》&#xff0c;本文节选自该白皮书&#xff0c;主要概述了5G 智慧城市安全参考框架。该框架是参考GB/T 37971-2019《信息安全技术 智慧城市安全体系框架…

vue 学习笔记—路由篇

一.关于三种路由 动态路由 就是path:good/:ops 这种 用 $route.params接收  <router-link>是用来跳转 <router-view></router-view>用来盛放内容的容易 在routes 里面进行配置 [{ path:,name:,component:}] path为router-link的路径 component为 …

MEMS传感器的未来在哪?

来源&#xff1a;MEMS技术1 引言陀螺是用于测量载体相对惯性空间旋转运动中运动角速度和角度的传感器&#xff0c;是运动控制、姿态监测、导航制导等领域的核心器件&#xff0c;在工业和国防领域具有广泛且重要的应用。陀螺从原理上可分为基于高速旋转刚体的定轴性与进动性工作…

黑科技:绕过眼睛植入幻觉,科学家成功在盲人脑海中呈现指定图像!

来源 | 学术头条&#xff08;ID:SciTouTiao&#xff09;头图 | CSDN付费下载自视觉中国对于全球 5000 多万盲人来说&#xff0c;重见光明是一个遥不可及的梦想。而为了与盲人朋友进行交互&#xff0c;我们发明了盲文&#xff0c;用各种凸起的字符集合来表达各种意思。但这种通过…

ssrf漏洞 php,DokuWiki fetch.php SSRF漏洞与tok安全验证绕过分析

作者&#xff1a;baolongniu of Tencent Security Platform Department关于DokuWikiDokuWiki是一个开源wiki引擎程序&#xff0c;运行于PHP环境下。DokuWiki程序小巧而功能强大、灵活&#xff0c;适合中小团队和个人网站知识库的管理。漏洞简介DokuWiki最新 2016-06-26a版本存在…

[Codevs] 1014 棋盘染色

1049 棋盘染色 时间限制: 1 s空间限制: 128000 KB题目等级 : 黄金 Gold题目描述 Description有一个55的棋盘&#xff0c;上面有一些格子被染成了黑色&#xff0c;其他的格子都是白色&#xff0c;你的任务的对棋盘一些格子进行染色&#xff0c;使得所有的黑色格子能连成一块&…

专访王田苗:机器人是“刚需”,市场正处于逆周期增长

来源&#xff1a;亿欧【本文三大核心点】一、未来老龄化社会的到来与新生代快节奏生活工作的方式让智能机器人处于长期刚性需求。二、人工智能算法、软体材料、5G互联网开放软件平台、多机与人机协作、云服务租赁共享模式等技术给新一代机器人发展带来了大量的产业机遇。三、中…

php算法入门,a011.PHP实战:加密解密,简单算法入门

原标题&#xff1a;a011.PHP实战&#xff1a;加密解密&#xff0c;简单算法入门在PHP编程中&#xff0c;很多时候我们会遇到传递信息的问题&#xff0c;而传递过程中为了安全&#xff0c;我们肯定是要进行加密和解密的&#xff0c;这里&#xff0c;我们来说一说使用PHP怎么进行…

tomcat的安装及配置

1.首先进tomcat官网下载zip压缩文件&#xff1a;http://tomcat.apache.org/download-90.cgi 2.解压缩到指定文件压&#xff08;后面配置环境变量会用到&#xff09; 3.配置环境变量 4.打开解压后文件中的bin文件夹&#xff0c;运行startup.bat 如上图结果则配置成功 5.打开浏览…

好文|奔向宇宙,揭开太空机器人的神秘面纱

来源&#xff1a;千慧知识产权空间机器人是在太空中执行空间站建造与运营、卫星组装与服务、行星表面探测与实验等任务的一类特种机器人&#xff0c;是世界航天大国竞相发展的热点领域。当前&#xff0c;空间机器人已经在国际空间站、飞船、卫星等飞行器的在轨维护、空间装配、…