CertiK:HopeLend 遭借貸攻擊事件分析
作者:CertiK
北京時間 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% 的獲利)作為獎勵,並得到搶跑者的答覆。
最終資金在溝通一個小時後,轉移到 Gnosis Safe 的多簽金庫中。
下面我們將展示真實的漏洞和黑客進行利用的細節。
前置信息
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(攻擊合約 - 套現)
借出初始閃電貸資金,進行質押
攻擊者首先從 Aave 閃電貸借入 2300WBTC,將其中 2000 枚 WBTC 質押(deposit)到 HopeLend,資金將會被轉移至 HopeLend 的 hEthWbtc 合約(0x251…907),同時獲取相應的 2000 枚 hETHWBTC。
借助空借貸池操縱初始貼現率(liquidityIndex)
從 HopeLend 進行閃電貸借入 2000 枚 WBTC。
目前的價值是 1 hETHWBTC = 1 WBTC。
按照正常的存取 ETHWBTC 換回 WBTC 的操作,是不會影響兌換比例(只有當收入了利息才會影響兌換比例,1 hETHWBTC 會獲得更多 WBTC)。
此時黑客開始通過一系列複雜操作,操縱貼現率:
- 黑客直接又將得到的 2000 枚 WBTC 通過直接轉帳(transfer)的方式轉移資金至 HopeLend 的 hEthWbtc 合約 ( 0x251…907),這一步並不是還貸。
- 黑客隨後取出(withdraw)之前步驟 1 中質押(deposit)的絕大部分 WBTC(1999.999…),所以上一步才需要轉回 WBTC 以補充池子內的資產。
- 最後黑客手上僅保留最小單位 (1e-8) 的 hEthWbtc,這裡不能完全提完,是因為需要留下一點點,作為計算貼現率(liquidityIndex)時,會基於現有的加上新增的,如果清零的話,導致貼現率(liquidityIndex)變成 0,就不能讓池子裡的比例失衡。
- 把上一步銷毀掉絕大部分 hEthWbtc 換回來的 wBTC,加上之前閃電貸剩餘的 wBTC,歸還向 HopeLend 池子借出的閃電貸,共支付 2001.8 枚 WBTC( 其中包含利息 1.8 枚 wBTC)。
- 上面的過程銷毀掉大部分的 hEthWbtc,只留下 1 最小單位(1e-8)的 hEthWbtc 在黑客賬戶,這樣一來 hETHWBTC 總量就減少了,而借貸池裡卻有 2001.8 枚 wBTC,此時的貼現率(liquidityIndex)達到驚人的 126,000,000。
這裡涉及到一個知識,存款用戶的利息根本上來自池中流動性的增長,借貸池會根據存款率和使用率,動態調節借款和存款利率。
此處,當池子從閃電貸利息 (1.8WBTC) 獲得額外流動性時,百分之七十 (126,000,000) 被計入 liquidityIndex(liquidityIndex),這個數值用來計算每單位存款 (hEthWbt) 的貼現價值。
由於池子在黑客操作前為空,還款後 totalLiquidity 僅為 1,amount 是 126000000,初始 liquidityIndex 為 1,得出結果為 126000001。
繼續放大貼現率
黑客繼續從 HopeLend 進行閃電貸借入 2000 枚 WBTC,並每次歸還額外的 1.8 枚 WBTC,使得每次 LiquidityIndex 得以累加 126,000,000。
黑客重複執行了 60 次該過程,最終 liquidityIndex 達到 7,560,000,001,攻擊者持有的 1 個最小單位的 hEthWBTC 貼現價值可達 75.6WBTC(約為 214 萬美元)。
這也就使得黑客操控了 hEthWBTC,使之價值失真。
掏空其他存在資金的借貸池,形成收益
攻擊者接著將 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.6WBTC。
每一次從其他池借款,黑客都輕鬆通過了抵押資產校驗。
此時, 攻擊者總共在 HopeLend 投入了 2000+1.8*60 枚 WBTC 用於操縱 liquidityIndex,只留存了 1 單位的 hEtthWBTC。
利用關鍵漏洞點(整數除法錯誤)套現
為了取出之前的投入 wBTC,攻擊者部署了另一個攻擊合約:0x5a63e……844e74,並調用其中的 withdrawAllBtc() 方法:
漏洞過程如下:
- 首先存入 151.20000002 枚 wBTC,根據當前的 liquidityIndex(1 最小單位 hEthWBTC=75.6wBTC),攻擊者獲得 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.60000001WBTC 恰好又獲得 1 個最小單位 hEthWBTC,從而繼續持有 2 個最小單位 hEthWBTC。
如此循環取出 113.40000000wBTC,存入 75.60000001wBTC 的操作,每次攻擊者可以憑空獲取 37.8 枚 wBTC。
循環 58 次後,攻擊者取出了所有前期投入的 wBTC ,並順利歸還 Aave 的閃電貸。
結論
由於 hEthWBTC 借貸池未被初始化,攻擊者得以輕易操縱 liquidityIndex,將其增至極大,提現率作為除數極大放大後,由於整數除法的截斷誤差,使得取出之前的投入更容易在一個區塊內實現。
在運轉良好的借貸池中,由於池中已有流動性,不容易因為少量的貸款利息增加而極大增加貼現率。