On October 18, 2023, at 11:48:59 AM (UTC), the lending pool of Hope.money was exploited by an attacker who leveraged a flash loan to attack the protocol. Hope.money comprises the HopeLend lending platform, HopeSwap decentralized exchange, the stablecoin $HOPE, and the governance token $LT to offer a comprehensive suite of decentralized financial services to users. The focal point of this attack was the HopeLend protocol, a decentralized lending platform where users can either provide liquidity or earn income from over-collateralized loans.
CertiK provided security reviews of HopeMoney’s LightDAO. The incident described here was the result of a vulnerability in the HopeLend lending pool, and as such was not within the scope of our audit.
HopeLend harbored a vulnerability within its lending pool, which became apparent during the process of burning deposit certificates. An issue with incorrect integer division led to the truncation of the decimal point section, and consequently, fewer certificates were destroyed than initially intended. This discrepancy between the expected and actual values proved to be exploitable.
An attacker seized upon this flaw to drain multiple lending pools on Hope.money.
Noteworthy among these was the hETHwBTC lending pool, established 73 days prior, which had no funds. The attacker injected a substantial amount of funds into this lending pool, significantly elevating the discount rate, and consequently was able to drain all other funds within a single block transaction.
A twist in the narrative emerged when it was revealed that the original hacker who exploited the vulnerability did not actually reap the benefits of the exploit. Instead, a front-runner detected the attacker's transaction, mimicked the attack, and successfully profited 527 ETH. Half of the attack’s total proceeds were spent by front-runners to bribe the miners who packaged the blocks.
The primary hacker initiated an attack contract in block 18377039 and invoked the attack contract in block 18377042. Concurrently, the front-runner was monitoring the transactions within the memory pool and replicated the attack contract as a front-runner. The exploit input was executed in the same block 18377042, yet the initial hacker's transaction in block 18377042 failed to execute as it was sequenced behind the front-runner.
Following the exploit, the front-runner transferred the funds to the address 0x9a9122Ef3C4B33cAe7902EDFCD5F5a486792Bc3A roughly an hour after acquiring the profits.
At 05:30:23 AM (UTC), the Hope.money team engaged with the aforementioned address, granting permission to the front-runner to retain 26ETH (representing a 10% profit) as a bounty. This engagement elicited a response from the front-runner.
After an hour-long dialogue, the remaining funds were securely relocated to a GnosisSafe multi-signature vault.
(HopeLend's lending protocol is a fork of Aave, hence the core business logic concerning vulnerabilities aligns with what's detailed in Aave's white paper.)
When users deposit liquidity into Aave, they anticipate earning income. However, not all loan proceeds are distributed to users. A small fraction of the interest income is allocated to a risk reserve, ensuring a level of protection. The majority of the loan proceeds are, however, disbursed to the liquidity providers, enhancing the incentive to participate.
In managing deposits, Aave employs a discounting mechanism to equate the number of deposits at varying time junctures to the count at the liquidity pool's initiation point. This facilitates a straightforward computation of the sum of principal and interest corresponding to the underlying assets for each share using the formula: amount (share) * index (discount rate).
This process mirrors fund purchasing dynamics. For instance, with an initial net value of 1, a user's investment of $100 procures 100 shares. Suppose the net value escalates to 1.03 over time; a subsequent investment of $100 will yield approximately 97 shares, totaling 197 shares for the user.
Essentially, this is an asset discounting exercise based on the index (net value), aimed at accurate principal and interest summation for the user, employing the current index in balance calculations. For instance, on a second deposit, the accurate sum of principal and interest should be 100 * 1.03 + 100 = 203. Without discounting, the sum after the second deposit erroneously becomes (100+100) * 1.03 = 206. With discounting, the sum is correctly adjusted to (100 + 100 / 1.03) * 1.03 = 103 + 100 = 203, validating the discounting process.
0x25126F207Db7dC427415eA640ce0187767403907 (hETHWBTC pool) 0x5a63e21ebded06dbfcb7271fbcef13c9f0844e74 (Attack Contract)
liquidityIndex
) via Empty Lending Pool:liquidityIndex
) during calculations.liquidityIndex
by 126,000,000.- After repeating this process 60 times, the `liquidityIndex` soared to 7,560,000,001, elevating the discounted value of the attacker's smallest hEthWBTC unit to 75.6WBTC (~$2.14M).
Utilizing 1 minimum unit of hETHwBTC as collateral, the attacker borrowed assets from HopeLend's other five token pools.
Acquired assets included 175.4 WETH, 145,522.220985 USDT, 123,406 USDC, 844,282 HOPE, and 220,617 stHOPE, which were later converted into WBTC and WETH via Uniswap.
withdrawAllBtc()
function.liquidityIndex
. Subsequently, 113.4 wBTC were withdrawn, necessitating the destruction of 1.9999999998 minimum units of hETHwBTC. However, due to the div function's precision, only one minimum unit of hETHwBTC was destroyed, exposing an exploitable vulnerability. This allowed the hacker to retain 1 minimum unit of hETHwBTC, capitalizing on the integer division error to cash out.When borrowing or withdrawing deposits, the lending contract will check the user's mortgage asset status to ensure that the loan does not exceed the mortgage. Since the discount rate has been manipulated by hackers before and the discount rate will be included in the mortgage value calculation with the normalizedIncome
multiplier, the mortgage value of one unit of hETHwBTC in their hand was as high as 75.6 WBTC.
liquidityIndex
) ManipulationThe attacker manipulated the discount rate through a series of complex operations:
liquidityIndex
) computation is based on the aggregate of existing and new values. A complete withdrawal would reset the discount rate to zero, disrupting the pool's proportionality.liquidityIndex
) increased to126,000,000.The hEthWBTC contract invokes the high-precision division function rayDiv
.
Within this function, the variables are assigned as follows:
While the precise calculation (a*1e27+b/2)/b results in 1.9999999998, Solidity's inherent div function truncates this to 1, effectively rendering it as 11340000000 / 7560000001. The decimals are discarded post-division. The attacker's contract 0x5a63 proceeded to deposit 75.60000001WBTC and acquired exactly 1 minimum unit of hETHwBTC, hence maintaining 2 minimum units of hETHwBTC.
In this revolving cycle of withdrawing 113.4 wBTC and depositing 75.60 wBTC, the attacker manages to generate 37.8 wBTC each time.
After 58 cycles, the attacker retrieved all initially invested wBTC and successfully repaid the Aave flash loan.
Given that the hETHwBTC lending pool was uninitialized, the attacker could easily manipulate the liquidityIndex
. Once the withdrawal rate was significantly heightened as a divisor, the truncation error inherent in integer division facilitated withdrawal of funds within a single block.
In a secure lending pool, a minor increase in loan interest doesn't substantially elevate the discount rate since the pool already possesses liquidity.