北京時間2023 年10 月18 日19: 48: 59 ,Hope.money 的借貸池受到了基於閃電貸實施的攻擊。
Hope.money 建構了包含HopeLend 借貸平台、HopeSwap 去中心化交易所、穩定幣$HOPE、治理代幣$LT,為用戶提供去中心化金融全端服務。
本次攻擊涉及的協議是HopeLend,是一個去中心化借貸平台,用戶可以為協議提供流動性或超額抵押借貸賺取收益。
事件始末
在HopeLend 的代碼實現中,借貸池存在可被利用的漏洞,由於在銷毀存款憑證時,出現了錯誤的整數除法問題,導緻小數點部分被截斷,實現了銷毀比預期少的憑證數量,獲得和預期一致的價值代幣。
攻擊者利用這個缺陷掏空了Hope.money 上存在資金的多種借貸池。
其中hEthWbtc 借貸池於73 天前部署,但是其中沒有資金,因此黑客通過往該借貸池注入大量資金來達到讓貼現率戲劇性地暴漲,從而實現了在一個區塊交易內快速掏空了所有其他借貸池子的資金。
更戲劇性的是,實現利用的黑客沒有獲得漏洞利用的資金,他的攻擊交易被搶跑者發現,搶跑者模仿其攻擊行為並成功搶走了所有攻擊收益資金( 527 ETH),最終有50 % 的攻擊收益資金( 263 ETH)被搶跑者用於賄賂打包區塊的礦工(payload)。
發現漏洞的初始黑客,在區塊18377039 創建了攻擊合約,並在區塊18377042 進行了攻擊合約的調用,此時搶跑者監控到內存池裡的交易,並將其攻擊合約進行模擬,作為搶跑合約的輸入,在同樣的18377042 區塊進行利用,而初始黑客在18377042 區塊的交易由於排序在搶跑者後面,從而執行失敗了。
資金去向
搶跑者在獲得收益後的一小時,將資金轉移到:0x9a9122Ef3C4B33cAe7902EDFCD5F5a486792Bc3A
在10 月20 日13: 30: 23 ,疑似官方團隊聯繫了該地址,允許搶跑者留下26 ETH(10% 的獲利)作為獎勵,並得到搶跑者的答复。
最終資金在溝通一小時後,轉移到GnosisSafe 的多簽金庫。
以下我們將展示真實的漏洞和駭客進行利用的細節。
前置訊息
*HopeLend 的借貸協議實現Fork 自Aave,因此涉及到漏洞相關的核心業務邏輯參考自Aave 的白皮書。
0x00 存款和借貸
Aave 是純粹的DeFi,借貸業務透過流動性池實現,用戶在Aave 存款提供流動性時,期望獲得借貸所獲得的收益。
貸款收益不會完全分配給用戶,有少數利息收入會被計入風險儲備金,此部分比例較少,大部分貸款收益會分發給提供流動性的用戶。
在Aave 中進行存款放款時,Aave 是透過折現的方式,將不同時間點的存款數量轉化成流動性池初始時間點的存款數量份額,因此每數量份額的底層資產對應的本息和,就可以直接用amount(份額) * index(貼現率)算出來,大大方便了計算和理解。
可以理解為類似購買基金的過程,基金的初始淨值是1 ,用戶投入100 塊錢獲得100 的份額,假設經過一段時間獲得收益,淨值變成1.03 ,此時用戶再次投入100 塊錢,獲得的份額是97 ,用戶的總份額是197 。
這其實是將該資產依照index(淨值)進行折現處理。之所以這麼處理,是因為使用者實際的本息和是用balance 去乘以當前的index。當第二次存款的時候,用戶正確的本息和應該是100 * 1.03 + 100 = 203 ,如果不做折現處理,第二次用戶存入100 後的本息和就變成了( 100+ 100) * 1.03 = 206 ,是錯誤的,如果進行了折現,本息和就變成了(100 + 100 / 1.03) * 1.03 = 103 + 100 = 203 , 203 的結果是正確的。
攻擊過程
0x25126......403907(hETHWBTC pool)
0x5a63e......844e74(攻擊合約-套現)
攻擊者:
1. 借出初始閃電貸資金,進行質押
攻擊者先從Aave 閃電貸借入2300 WBTC,將其中2000 枚WBTC 質押(deposit)到HopeLend,資金將轉移至HopeLend 的hEthWbtc 合約( 0x251 … 907),同時取得對應的2000 元。
2. 利用空借貸池操縱初始貼現率(liquidityIndex)
從HopeLend 進行閃電貸借2000 枚WBTC。
目前的價值是1 hETHWBTC = 1 WBTC。
依照正常的訪問ETHWBTC 換回WBTC 的操作,是不會影響兌換比例(只有當收入了利息才會影響兌換比例, 1 hETHWBTC 會獲得更多WBTC)。
此時黑客開始透過一系列複雜操作,操縱貼現率:
• 駭客直接再將得到的2000 枚WBTC 通過直接轉帳(transfer)的方式轉移資金至HopeLend 的hEthWbtc 合約( 0x 251 … 907),這一步並不是還款。
• 駭客隨後取出(withdraw)之前步驟1 中質押(deposit)的絕大部分WBTC(1999.999 …),所以上一步才需要轉回WBTC 以補充池子內的資產。
• 最後黑客手上僅保留最小單位( 1 e-8)的hEthWbtc,這裡不能完全提完,是因為需要留下一點點,作為計算貼現率(liquidityIndex)時,會基於現有的加上新增的,如果清零的話,導致貼現率(liquidityIndex)變成0 ,就不能讓池子裡的比例失衡。
• 把上一步銷毀掉絕大部分hEthWbtc 換回來的wBTC,加上先前閃電貸剩餘的wBTC,歸還向HopeLend 池子借出的閃電貸,共支付2001.8 枚WBTC(其中包含利息1.8 枚wBTC)。
• 上面的過程銷毀掉大部分的hEthWbtc,只留下1 最小單位(1 e-8)的hEthWbtc 在黑客賬戶,這樣一來hETHWBTC 總量就減少了,而藉貸池裡卻有2001.8 枚wBTC,此時的折現率(liquidityIndex)達到驚人的126, 000, 000 。這裡涉及到一個知識,存款用戶的利息根本上來自池中流動性的增長,借貸池會根據存款率和使用率,動態調節借款和存款利率。
此處,當池子從閃電貸利息( 1.8 WBTC)獲得額外流動性時,百分之七十( 126, 000, 000)被計入liquidityIndex(liquidityIndex),這個數值用來計算每單位存款(hEthWbt)的貼現價值。
由於池子在黑客操作前為空,還款後totalLiquidity 僅為1 ,amount 是126000000 ,初始liquidityIndex 為1 ,得出結果為126000001 。
3. 繼續放大貼現率
黑客繼續從HopeLend 進行閃電貸借入2000 枚WBTC,並每次歸還額外的1.8 枚WBTC,使得每次LiquidityIndex 得以累加126, 000, 000 。
駭客重複執行了60 次該過程,最終liquidityIndex 達到7, 560, 000, 001 ,攻擊者持有的1 個最小單位的hEthWBTC 折現價值可達75.6 WBTC(約為214 萬美元)。
這也讓駭客操控了hEthWBTC,使其價值失真。
4. 掏空其他存在資金的借貸池,形成收益
攻擊者接著將1 個最小單位的hEthWBTC 為抵押,從HopeLend 的其他五個代幣池借出了大量資產。
包括:
175.4 - WETH
145, 522.220985 - USDT
123, 406.134999 - USDC
844, 282.284002229528476039 - HOPE
220, 617.821736563540747967 - stHOPE
這些代幣被作為收益透過Uniswap 兌換為WBTC 和WETH,扣除各種費用後,最終黑客獲利約263 枚WETH(除去賄賂payload 的263.9 枚WETH)。
為什麼駭客可以從其他池子借走大量資金:
借款或取走存款時,借貸合約會檢驗用戶的抵押資產狀況,確保借出不超過抵押。
由於先前折現率已被黑客操縱且貼現率會以normalizedIncome 乘數計入抵押價值計算,其手中的一單位hEthWBTC 抵押價值高達75.6 WBTC。
每次從其他池借款,駭客都輕鬆通過了抵押資產校驗。
此時, 攻擊者總共在HopeLend 投入了2000+ 1.8* 60 枚WBTC 用於操縱liquidityIndex,只留存了1 單位的hEtthWBTC。
5. 利用關鍵漏洞點(整數除法錯誤)套現
為了取出先前的投入wBTC,攻擊者部署了另一個攻擊合約:0x 5 a 63 e......844 e 74 ,並呼叫其中的withdrawAllBtc()方法
漏洞流程如下:
① 先存入151.20000002 枚wBTC,根據目前的liquidityIndex( 1 最小單位hEthWBTC= 75.6 wBTC),攻擊者獲得2 個最小單位的hEthWBTC。
② 取出(withdraw) 113.4 個wBTC,反算出其對應的hEthWBTC 份額,對hEthWBTC 進行burn 操作。
③ 113.4 個wBTC 需要銷毀1.9999999998 最小單位的的hEthWBTC,但是由於div 函數精度問題,僅一個最小單位的hEthWBTC 被銷毀,因此變成可被利用的漏洞,黑客仍可保留1 個最小單位的hEthWBTC。
關鍵漏洞
hEthWBTC 的burn 方法呼叫了高精度除法rayDiv。
此處:
a= 11340000000(打算取出的WBTC)
b=7560000001000000000000000009655610336(折現率)
雖然(a* 1e27+b/2)/b = 1.9999999998 ,solidity 自帶的div 方法截斷回傳1, 相當於11340000000 / 7560000001 除法後小數位被截斷了。
0x5a63(攻擊合約-套現)繼續存入75.60000001 WBTC 恰好又獲得1 個最小單位hEthWBTC,從而繼續持有2 個最小單位hEthWBTC。
如此循環取出113.40000000 wBTC,存入75.60000001 wBTC 的操作,每次攻擊者可以憑空取得37.8 枚wBTC。
循環58 次後,攻擊者取出了所有前期投入的wBTC ,並順利歸還Aave 的閃電貸。
結論
由於hEthWBTC 借貸池未被初始化,攻擊者得以輕易操縱liquidityIndex,將其增至極大,提現率作為除數極大放大後,由於整數除法的截斷誤差,使得取出之前的投入更容易在一個區塊內實現。
在運作良好的借貸池中,由於池中已有流動性,不容易因為少量的貸款利息增加而大幅增加折現率。