Hundred Finance 攻击事件分析

2023-11-12 02:15

本文主要是介绍Hundred Finance 攻击事件分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

背景知识

Hundred Finance 是 fork Compound 的一个借贷项目,在2023/04/15遭受了黑客攻击。攻击者在发起攻击交易之前执行了两笔准备交易占据了池子,因为发起攻击的前提是池子处于 empty 的状态(发行的 hToken 数量为 0)。

准备交易:

  1. https://optimistic.etherscan.io/tx/0xf479b1f397080ac01d042311ac5b060ceccef491867c1796d12ad16a8f12a47e

  2. https://optimistic.etherscan.io/tx/0x771a16e02a8273fddf9d9d63ae64ff49330d44d31575af3dff0018b04da39fcc

攻击交易:Phalcon || Tendery

交易分析

两次准备交易一共存入 63816 + 30000000 = 30063816 wei WBTC,获得 3190800 + 1499976495 = 1503167295 wei hWBTC

WBTC decimal = 8,hWBTC decimal = 8

执行攻击交易,首先从 AAVE V3 闪电贷出来 500 WBTC

通过 tendery 的模拟交易可以查询到,在攻击交易执行前,池子中存在 30064194 wei WBTC

首先 redeem 之前存入的所有 WBTC,将池子还原到 empty 的状态。

redeem 之后池子中存在 378 wei WBTC(其中1wei为留存资金,377wei为reserve资金),发行 0 hWBTC。empty状态仅代表 hWBTC 的 totalsupply 为 0。(如果先入为主地认为 WBTC的数量也为0,那么当你看到后面的时候会发现凭空多redeem出来了1 wei WBTC)

创建合约 0xd340 并往其中发送 50030063816 wei WBTC

合约 0xd340:

首先存入 4 WBTC,mint 200 hWBTC

redeem 19999999998 wei hWBTC,收到 4 WBTC。此时合约持有 2 wei hWBTC

向池子转入 50030063816 wei WBTC,然后借出 1021.915074492787011273 ETH

调用 redeemUnderlying 函数取出 50030063815 wei WBTC,消耗 1 wei hWBTC。此时合约持有 1 wei hWBTC。

攻击合约调用 liquidateBorrow 函数对创建的 0xd340 合约的债务进行清算。支付 0.000000267919888739 ETH,获得 1 wei hWBTC。

redeem 1 wei hWBTC,获得 2 wei WBTC,此时池子重新回到 empty 状态。这样做的目的是为了可以再次掏空其他池子。

把 50030063817 wei WBTC 转移走

随后攻击者又再进行了6次相同的操作来掏空其他的池子完成获利,文章篇幅有限就不再展开说明。

漏洞代码分析

合约 0xd340 在进行 redeem 操作时利用了精度丢失的漏洞,获取超额的 WBTC 。漏洞的发生在于 redeemFresh 函数中。

进入到 trace 分析,发现在 truncate 函数中进行了精度丢失。

跟进代码查看 truncate 函数的具体实现方法,在对输入参数 exp 除 1e18 的时候发生了精度丢失。

攻击细节分析

在分析攻击的过程中,对一些细节的部分存在着困惑,尝试着用生疏的技巧浅浅的分析一下。

为什么要先 mint 再 redeem,剩余 2 wei hWBTC

因为 mint 函数只能根据抵押物的数量来 mint hToken。也就是说在 initialExchange = 0.02 WBTC/hWBTC 的情况下,即使是传入 1 wei 的 WBTC,也会 mint 出 50 wei hWBTC。想要得到 2 wei hWBTC的剩余,没办法通过直接 mint 2 hWBTC 的方式(因为你无法提供 0.04 wei WBTC),所以只能先 mint 出大量的 hWBTC,然后再 redeem 使其剩余 2 wei。

所以按道理来说是不是先 mint 出 50 wei hWBTC,再 redeem 48 wei hWBTC 也可以?

为什么要剩余 2 wei hWBTC,而不是其他数量

剩余一定数量的 hWBTC 是为了后续构造精度丢失的攻击,使得合约从 hWBTC 的数量来计算抵押率是满足的,从而批准这笔 redeem,而实际上借出的 WBTC 数量是不满足抵押率要求的。而攻击者构造 2 wei 的这个数量就是为了通过精度丢失,是的超额借出的 WBTC 数量最大化。

假设在 2 wei 的情况下,borrow 了一半价值(1 wei)的资产:

赎回价值 1.99… wei hWbtc 的 WBTC,实际销毁 1 wei hWbtc。此时超额部分为 0.99… wei hWBTC,获得的 WBTC 占总资金的 1.99 / 2 。

在 20 wei 的情况下,borrow 了一半价值(10 wei)的资产:

赎回价值 10.99… wei hWbtc 的 WBTC,实际销毁 10 wei hWbtc。此时超额部分为 0.99… wei hWBTC,获得的 WBTC 占总资金的 10.99 / 20 。

通过上面两个例子我们可以得出,剩余的 hWBTC 数量越少,攻击者通过精度丢失所获得的超额 WBTC 比例就越大。

剩余的 hWBTC 数量可不可以为 1 wei 呢?

假设剩余 1 wei hWBTC,攻击者可以借出 100% 价值的资产,此时赎回价值 0.99… wei hWBTC 的 WBTC,利用精度丢失实际 burn 0 hWBTC。这样构造最大的好处是借出的资产可以达到 100%,而 2 wei 的方案借出资产只能借出 50%。

攻击者是如何构造获利场景的

攻击者只消耗 1 wei hWBTC ,然后 redeemUnderlying 出了 50030063815 wei WBTC

攻击者先 deposit 50030063816 wei WBTC,然后 redeem 50030063815 wei WBTC,希望通过 redeem (deposit amount - 1) 的方式构造精度丢失的场景:redeem 出价值 1.999… wei hWBTC 的 WBTC,最终会 burn 1 wei hWBTC,超额收益 0.999... hWBTC。

但是由于在攻击执行前池子里剩余有 1 wei WBTC,所以攻击者直接 redeem 50030063816 wei WBTC 也是可以达到 burn 1 wei hWBTC 的目的的。也就是说只有当 redeem 50030063817 wei WBTC 的时候才会 burn 2 wei hWBTC。

这个精度缺失攻击的前提是池子中 WBTC 的数量大于 hWBTC 的数量

假设 3 WBTC,2 hWBTC,可得 exchangeRateStoredInternal = 3 / 2 = 1.5

赎回 2 WBTC,计算需要 burn 的 hWBTC 数量:2 / 1.5 = 1.333… → 1 hWBTC

可以通过一个公式来计算出攻击者持有部分 hWBTC 的情况下通过精度丢失得到最大获利的情况吗?

  1. 假设池子持有 x WBTC,总共发行了 y hWBTC。攻击者持有 z hWBTC (z < y),赎回 kx WBTC (0 < k < 1)

  1. exchangeRateStoredInternal = x / y

  1. 由 1 和 2 可得,要 burn 的 hWBTC 数量 = kx / (x / y) = ky

  1. 攻击者为了获取尽可能大的超额收益,需要通过精度丢失漏洞构造 burn z + 0.999… hWBTC → burn z hWBTC

  1. 由 3 和 4 可得,ky = z + 0.999 → k = (z + 0.999) / y

举例说明:

  1. 假设池子持有 20000 WBTC,总共发行了 100 hWBTC,exchangeRateStoredInternal = 200。攻击者持有 50 hWBTC

  2. k = (z + 0.999) / y = (50 + 0.999) / 100 = 0.5099

  3. 赎回 kx = 0.5099 * 20000 = 10198 WBTC

  4. burn 的 hWBTC 数量为 kx / (x / y) = 10198 / 200 = 50.99 → 50

如何计算出清算所需要的 token 数量

通过 liquidateCalculateSeizeTokens 函数,计算得出提供 267919888739 wei ETH,能够清算获得 1 wei hWBTC

然后攻击者执行 liquidateBorrow 函数,提供 267919888739 wei ETH 进行清算,获得 1 wei hWBTC 。具体的计算过程以及涉及的参数如下图所示:

文章转载自: 阿菜种菜的菜园子

原文链接:https://www.cnblogs.com/ACaiGarden/p/17810494.html

这篇关于Hundred Finance 攻击事件分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/394393

相关文章

Java字符串操作技巧之语法、示例与应用场景分析

《Java字符串操作技巧之语法、示例与应用场景分析》在Java算法题和日常开发中,字符串处理是必备的核心技能,本文全面梳理Java中字符串的常用操作语法,结合代码示例、应用场景和避坑指南,可快速掌握字... 目录引言1. 基础操作1.1 创建字符串1.2 获取长度1.3 访问字符2. 字符串处理2.1 子字

Python 迭代器和生成器概念及场景分析

《Python迭代器和生成器概念及场景分析》yield是Python中实现惰性计算和协程的核心工具,结合send()、throw()、close()等方法,能够构建高效、灵活的数据流和控制流模型,这... 目录迭代器的介绍自定义迭代器省略的迭代器生产器的介绍yield的普通用法yield的高级用法yidle

C++ Sort函数使用场景分析

《C++Sort函数使用场景分析》sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变,如果某些场景需要保持相同元素间的相对顺序,可使... 目录C++ Sort函数详解一、sort函数调用的两种方式二、sort函数使用场景三、sort函数排序

kotlin中const 和val的区别及使用场景分析

《kotlin中const和val的区别及使用场景分析》在Kotlin中,const和val都是用来声明常量的,但它们的使用场景和功能有所不同,下面给大家介绍kotlin中const和val的区别,... 目录kotlin中const 和val的区别1. val:2. const:二 代码示例1 Java

Go标准库常见错误分析和解决办法

《Go标准库常见错误分析和解决办法》Go语言的标准库为开发者提供了丰富且高效的工具,涵盖了从网络编程到文件操作等各个方面,然而,标准库虽好,使用不当却可能适得其反,正所谓工欲善其事,必先利其器,本文将... 目录1. 使用了错误的time.Duration2. time.After导致的内存泄漏3. jsO

C#如何动态创建Label,及动态label事件

《C#如何动态创建Label,及动态label事件》:本文主要介绍C#如何动态创建Label,及动态label事件,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C#如何动态创建Label,及动态label事件第一点:switch中的生成我们的label事件接着,

Spring事务中@Transactional注解不生效的原因分析与解决

《Spring事务中@Transactional注解不生效的原因分析与解决》在Spring框架中,@Transactional注解是管理数据库事务的核心方式,本文将深入分析事务自调用的底层原理,解释为... 目录1. 引言2. 事务自调用问题重现2.1 示例代码2.2 问题现象3. 为什么事务自调用会失效3

找不到Anaconda prompt终端的原因分析及解决方案

《找不到Anacondaprompt终端的原因分析及解决方案》因为anaconda还没有初始化,在安装anaconda的过程中,有一行是否要添加anaconda到菜单目录中,由于没有勾选,导致没有菜... 目录问题原因问http://www.chinasem.cn题解决安装了 Anaconda 却找不到 An

Spring定时任务只执行一次的原因分析与解决方案

《Spring定时任务只执行一次的原因分析与解决方案》在使用Spring的@Scheduled定时任务时,你是否遇到过任务只执行一次,后续不再触发的情况?这种情况可能由多种原因导致,如未启用调度、线程... 目录1. 问题背景2. Spring定时任务的基本用法3. 为什么定时任务只执行一次?3.1 未启用

C++ 各种map特点对比分析

《C++各种map特点对比分析》文章比较了C++中不同类型的map(如std::map,std::unordered_map,std::multimap,std::unordered_multima... 目录特点比较C++ 示例代码 ​​​​​​代码解释特点比较1. std::map底层实现:基于红黑