{"content":{"title":"零时科技 || Hopenlend 攻击事件详细技术分析","body":"上次的文章《零时科技 || Fount-running, MEV和Hacker》介绍了Hopelend的成功攻击是由抢跑者发起的。本文，我们将详细介绍抢跑者是如何进行攻击的。\r\n\r\n# HopeLend简介\r\n\r\nHopeLend是一个去中心化的虚拟货币借贷平台，能够在无需双方单独撮合的情况下，根据资金池状态实现即时贷款。类似于Aave等DeFi项目，HopeLend的运行模式如下图：\r\n![](https://p1.ssl.qhimg.com/t0195aa41b719f6a627.png)\r\n# HopeLend攻击分析\r\n\r\n**简单来讲，黑客攻击流程主要分为两部分：**\r\n利用Hopelend的hEthWBTC交易池流动性不足（流动性为0）抬高hEthWBTC的价值，然后通过borrow掏空所有代币（HOPE，stHOPE，wstETH，WETH，USDT，USDC）；\r\n\r\n利用rayDiv精度丢失问题，掏空从Aave中flashloan的其中2000 WBTC给Hopelend中存入的2000 WBTC。（掏空黑客前期攻击投入的所有WBTC）。\r\n\r\n在具体分析攻击事件之前，我们需要简单了解一下HopeLend的相关内容。\r\n\r\nHopelend是去中心化借贷平台，用户通过deposti存入underlyingAssets（标的资产），获得对应的hToken；反之，通过withdraw取出underlyingAssets时，销毁对应的hToken。\r\n\r\n其中，underlyingAssets标的资产和hToken的兑换比例是通过liquidityIndex（流动性指数）来控制的，简单来讲liquidityIndex（流动性指数）就是hToken的价值。例如，当liquidityIndex为2时，1个对应的hToken可以兑换2个对应的underlyingAssets（标的资产）。其中，liquidityIndex是通过收益进行计算的。计算方式如下：\r\n![](https://p3.ssl.qhimg.com/t01244db4c82109f770.png)\r\n**对应的代码如下：**\r\n![](https://p1.ssl.qhimg.com/t018212bc128b439b92.png)\r\n🔹在此次攻击中，简单来说，黑客通过操纵liquidityIndex（流动性指数）来抬高hToken的价值，使得hToken的价值失真。最终使用很小单位的hToken来借贷大额的其他标的资产。从而掏空Hopelend其他池子的underlyingAssets（标的资产）。随后，黑客通过利用rayDiv精度丢失问题，反复deposit和withdraw，最终掏空转给池子的所有WBTC完成攻击。\r\n\r\n**步骤1详细分析**\r\n\r\n黑客通过deposit存入标的资产WBTC，获得mint的hEthWBTC，然后通过重复的flashloan来操纵hEthWBTC的流动性指数liquidityIndex，使hEthWBTC的价格虚高，最后利用极小的hEthWBTC作为抵押物，通过borrow掏空除了WBTC之外的所有标的资产。\r\n![](https://p3.ssl.qhimg.com/t0108534093d3d90070.png)\r\n首先，黑客从Aave中利用flashloan贷了2300 WBTC，向Hopelend中deposit了2000 WBTC。同时，WTBC对应的池子向黑客mint了2000 hEthWBTC作为存款凭证。随后，黑客通过Hopelend的flashloan贷了2000 WBTC，然后向Hopelend的池子中transfer转入2000 WBTC，接着从Hopelend中withdraw了1999.99999999 WBTC，剩下0.00000001 WBTC（最小单位）。向Hopelend的池子中transfer和withdraw的操作只有在第一次flashloan中进行。\r\n![](https://p4.ssl.qhimg.com/t01bae7ac03011ef073.png)\r\n🔹这是因为，黑客向Hopelend中deposit了2000 WBTC，Hopelend随后mint了2000对应的hToken（hEthWBTC）作为存款凭证。随后，黑客通过Hopelend的flashloan贷了2000 WBTC，随后向标的资产对应的池子内transfer了2000 WBTC后withdraw了1999.99999999 WBTC。因此，该池子未初始化，所以对应的liquidityIndex为1，因此，池子销毁了1999.99999999 hEthWBTC，剩余0.00000001 hEthWBTC。\r\n\r\n**我们看一下，Hopelend在flashloan中是如何更新liquidityIndex的**\r\n![](https://p1.ssl.qhimg.com/t0142b6b15b1a64d864.png)\r\n首先，IERC20(reserveCache.hTokenAddress).totalSupply() + reserve.accruedToTreasury 是hToken对应的总价值，premiumToLP为本次flashloan的收益，也就是新增价值。因为flashloan的贷款利率是0.09%，且池子获得利润的30%归项目方，70%归流动性提供方，所以每次黑·客通过flashloan借贷2000 WBTC后，给池子产生的利润为2000 * 0.09% * 70% = 1.26 WBTC。所以在上面的公式中，premiumToLP 为 1.26 WBTC。\r\n\r\n因为reserve.accruedToTreasury 为 0，所以简单来讲，flashloan后hToken的价值（liquidityIndex）=（池子新增的价值 / 池子hToken的总价值 + 1） * 池子当前hToken的价值。所以，黑客要抬高hToken的价值，就是让池子hToken的总价值尽可能小，这就解释了为什么黑客在flashloan借贷了2000 WBTC后要把2000 WBTC transfer转给池子。\r\n\r\n因为 withdraw 操作会销毁（burn）hToken，但是当前如果不给池子转账，那么池子没有资产（最开始存的2000WBTC目前由于进行闪电贷中所以已经锁定），黑客就无法通过withdraw来销毁对应的hToken。\r\n\r\n黑客为了让池子的hToken的总价值尽可能小，所以黑客withdraw了1999.99999999 WBTC，剩下0.00000001 WBTC（最小单位）。所以，池子也销毁了1999.99999999 hEthWTBC而剩下了0.00000001 hEthWTBC（最小单位）。由于WBTC对应了hEthWTBC的池子未初始化，所以此时的liquidityIndex为1，当前池子的总价值就是0.00000001 hEthWTBC（最小单位）。\r\n\r\n经过第一次flashloan后，hToken的价值liquidityIndex被更新为，（126000000 * 10^27 / 1 + 1）* 1*= 126000001000000000000000000000000000。此时，0.00000001 hEthWTBC价值为1.26000001 WBTC。\r\n\r\n第二次，黑客通过flashloan借贷2000 WBTC，因为第一次借贷后，liquidityIndex为126000001000000000000000000000000000，所以通过算法得到此时liquidityIndex为252000000999999999999999999948221218。\r\n\r\n🔹黑客通过重复执行flashloan，最终将hEthWTBC的liquidityIndex提升到7560000001000000000000000009655610336，也就0.00000001 hEthWTBC可以兑换75.60000001 WBTC。\r\n\r\n**下图是每次flashloan后hEthWTBC的liquidityIndex的值**\r\n![](https://p0.ssl.qhimg.com/t011219b583b972adce.png)\r\n因为，池子中仍有黑客的0.00000001 hEthWBTC（价值75.60000001 WBTC）且价值巨大。所以，黑客利池子中的抵押物（0.00000001 hEthWBTC），通过borrow借空了所有代币（HOPE，stHOPE，WETH，USDT，USDC）。\r\n![](https://p4.ssl.qhimg.com/t014412defa8f490af9.png)\r\n\r\n**步骤2详细分析**\r\n\r\n黑客利用rayDiv精度丢失问题，重复deposit和withdraw操作，掏空前期攻击投入的所有WBTC。\r\n![](https://p3.ssl.qhimg.com/t017fa2061f06217d9e.png)\r\n黑客首先存入了151.20000002 WBTC，随后取出了113.40000000 WBTC。因为，此时的liquidityIndex已经提升到7560000001000000000000000009655610336，所以黑客通过deposit存入151.20000002 WBTC后，池子同样mint了0.00000002 hEthWBTC。黑客通过withdraw取出了113.40000000 WBTC，应该销毁的hEthWBTC为0.000000019999999998015872个。但是，由于solidity截断导致只销毁了0.00000001 hEthWBTC。\r\n\r\n**具体看一下销毁（Burn）的代码**\r\n![](https://p5.ssl.qhimg.com/t01b09de8ad5571ee37.png)\r\n其中，_burnScaled为缩放需要销毁的hToken的函数。\r\n![](https://p1.ssl.qhimg.com/t015db9abb7afed060f.png)\r\n我们可以看到，需要销毁的hToken数量 =（取出标的资产的数量 / hToken的流动性指数）。但是这里为了减少数学运算的gas消耗，此处除法使用的是rayDiv，代码如下：\r\n![](https://p3.ssl.qhimg.com/t01694d0e4a6bed824c.png)\r\n因为，需要销毁的hToken的数量的计算公式为销毁的hToken数量 = (取出资产的数量 * 10^27 + (hToken的流动性指数 / 2) ) / hToken的流动性指数。此时，需要取出的资产数量为113.40000000 WBTC， 此时hEthWBTC的流动性指数为7560000001000000000000000009655610336。通过python计算我们可以得到\r\n![](https://p1.ssl.qhimg.com/t014c4fcd17a84ef82b.png)\r\n需要销毁的数量为(1.9999999998015872 * 10^-8)个hEthWBTC。但是因为此处为evm opcode div进行的整数除法运算，所以，此处相当于做了一个地板除（floor division）舍去了小数位。因此，最终得到需要销毁的数量为(1 * 10^-8)个hEthWBTC也就是0.00000001 hEthWBTC，相当于只销毁了75.6 WBTC价值的hEthWBTC。所以，至此黑客通过精度丢失漏洞获利（113.4 - 75.6 = 37.8 WBTC）。\r\n\r\n随后，黑客通过继续deposit存入75.60000001 WBTC获得池子mint的0.00000001 hEthWBTC（因为此时hEthWBTC的流动性指数为7560000001000000000000000009655610336，相当于1个最小单位的hEthWBTC，也就是0.00000001 hEthWBTC价值是75.60000001 WBTC）。因此，池子又剩下0.00000002 hToken。接着，黑客通过withdraw取出了113.40000000 WBTC，由于solidity的截断，导致只销毁了0.00000001 hToken。\r\n\r\n🔹也就是说，黑客通过deposit存入75.60000001 WBTC然后通过withdraw取出了113.40000000 WBTC，可以凭空得到37.8000000 WBTC，黑客通过持续重复此操作，最终掏空了之前投入的WBTC，从而归还了Aave的贷款。至此，所有攻击流程均已完成。\r\n\r\n# 总结\r\n\r\n黑客首先利用标的资产对应的池子流动差的原因，反复操作标的资产对应的hToken的流动性指数，使其价值失真。随后，通过极小的hToken做抵押借出其他所有的标的资产。接着，利用合约存在的除法精度丢失的漏洞，反复存取掏空黑客攻击中投入的标的资产。至此，黑客完成了一次针对DeFi项目Hopelend的一次复杂的攻击，掏空了HopeLend的所有资产。\r\n\r\n# 参考\r\n\r\nhttps://github.com/Dapp-Learning-DAO/Dapp-Learning/blob/main/defi/Aave/contract/9-LendingPool.mdhttps://docs.hope.money/lend/for-developers/contractshttps://docs.hope.money/lend/getting-started/understanding-hopelendhttps://www.metaverse456.com/453414.html"},"author":{"user":"https://learnblockchain.cn/people/52","address":null},"history":null,"timestamp":1700674023,"version":1}