CertiK: Analysis of the HopeLend Lending Attack Incident

CertiK
2023-10-21 16:41:21
Collection
The vulnerability of the attack lies in the incorrect integer division issue when destroying deposit certificates, but the hacker did not anticipate that "the mantis stalks the cicada, unaware of the oriole behind."

Author: CertiK


On October 18, 2023, at 19:48:59 Beijing time, the lending pool of Hope.money was attacked using a flash loan exploit.

Hope.money includes the lending platform HopeLend, the decentralized exchange HopeSwap, the stablecoin HOPE, and the governance token LT, providing users with a full-stack decentralized finance service.

The protocol involved in this attack is HopeLend, a decentralized lending platform where users can provide liquidity to the protocol or over-collateralize loans to earn returns.

Event Summary

In the implementation of HopeLend's code, there was a vulnerability in the lending pool that could be exploited. An integer division error occurred when destroying deposit receipts, resulting in the truncation of the decimal part, leading to fewer receipts being destroyed than expected while obtaining tokens of equivalent value.

The attacker exploited this flaw to drain various lending pools of funds on Hope.money.

Among them, the hEthWbtc lending pool was deployed 73 days ago, but it had no funds. Therefore, the hacker injected a large amount of funds into this lending pool to dramatically increase the discount rate, allowing them to quickly drain all other lending pools' funds within a single block transaction.

More dramatically, the hacker who executed the exploit did not obtain the funds from the exploit; their attack transaction was front-run by another actor, who mimicked the attack and successfully stole all the attack profit (527 ETH). Ultimately, 50% of the attack profit (263 ETH) was used by the front-runner to bribe the miner who packaged the block (payload).

The initial hacker created the attack contract in block 18377039 and called the attack contract in block 18377042. At this point, the front-runner monitored the transactions in the memory pool and simulated the attack contract, using it as input for their front-running contract, exploiting it in the same block 18377042. The initial hacker's transaction in block 18377042 failed due to being ordered after the front-runner's transaction.

Funds Flow

One hour after obtaining the profits, the front-runner transferred the funds to: 0x9a9122Ef3C4B33cAe7902EDFCD5F5a486792Bc3A

On October 20, 2023, at 13:30:23, a suspected official team contacted this address, allowing the front-runner to keep 26 ETH (10% of the profit) as a reward, and received a response from the front-runner.

Ultimately, the funds were transferred to a Gnosis Safe multi-signature vault after an hour of communication.

Below we will show the details of the actual vulnerability and how the hacker exploited it.

Preliminary Information

The lending protocol of HopeLend is a fork of Aave, so the core business logic related to the vulnerability is referenced from Aave's white paper.

0x00 Deposits and Loans

Aave is a pure DeFi platform where lending operations are realized through liquidity pools. When users deposit into Aave to provide liquidity, they expect to earn returns from the loans.

Loan income is not fully allocated to users; a small portion of the interest income is allocated to a risk reserve, which is relatively small, while most of the loan income is distributed to users providing liquidity.

When depositing and lending in Aave, Aave converts the deposit amounts at different time points into shares of the initial deposit amount in the liquidity pool through discounting. Therefore, the principal and interest corresponding to each share of the underlying asset can be directly calculated using amount (shares) * index (discount rate), greatly simplifying calculations and understanding.

This can be understood as a process similar to purchasing a fund, where the initial net asset value is 1. A user invests 100, obtaining 100 shares. Assuming that after a period of time, the net asset value becomes 1.03, when the user invests another 100, the shares obtained would be 97, making the total shares 197.

This essentially discounts the asset according to the index (net asset value). The reason for this treatment is that the user's actual principal and interest are calculated by multiplying the balance by the current index. When making the second deposit, the user's correct principal and interest should be 100 * 1.03 + 100 = 203. If no discounting is done, the principal and interest after the second deposit of 100 would become (100 + 100) * 1.03 = 206, which is incorrect. If discounting is applied, the principal and interest become (100 + 100 / 1.03) * 1.03 = 103 + 100 = 203, which is the correct result.

Attack Process

0x25126……403907 (hETHWBTC pool)

0x5a63e……844e74 (attack contract - cash out)

Borrow Initial Flash Loan Funds and Stake

The attacker first borrowed 2300 WBTC from Aave's flash loan, depositing 2000 WBTC into HopeLend, which would be transferred to HopeLend's hEthWbtc contract (0x251…907), while obtaining the corresponding 2000 hETHWBTC.

Manipulating Initial Discount Rate (liquidityIndex) Using Empty Lending Pool

The attacker borrowed 2000 WBTC from HopeLend via a flash loan.

The current value is 1 hETHWBTC = 1 WBTC.

Under normal circumstances, the operation of exchanging ETHWBTC back to WBTC will not affect the exchange ratio (only when interest is earned will it affect the exchange ratio, as 1 hETHWBTC will yield more WBTC).

At this point, the hacker began a series of complex operations to manipulate the discount rate:

  • The hacker directly transferred the 2000 WBTC obtained back to HopeLend's hEthWbtc contract (0x251…907); this step is not a loan repayment.
  • The hacker then withdrew (withdraw) the vast majority of the WBTC staked in step 1 (1999.999…), which is why the previous step required transferring WBTC back to replenish the pool's assets.
  • Finally, the hacker retained only the minimum unit (1e-8) of hEthWbtc; this cannot be fully withdrawn because a small amount needs to be left to calculate the discount rate (liquidityIndex). If it were zeroed out, the discount rate (liquidityIndex) would become 0, causing the pool's ratio to become unbalanced.
  • The hacker then destroyed most of the hEthWbtc obtained from the previous step in exchange for WBTC, along with the remaining WBTC from the flash loan, repaying the flash loan borrowed from the HopeLend pool, totaling 2001.8 WBTC (including 1.8 WBTC in interest).
  • The above process destroyed most of the hEthWbtc, leaving only 1 minimum unit (1e-8) of hEthWbtc in the hacker's account. As a result, the total amount of hETHWBTC decreased, while the lending pool had 2001.8 WBTC, causing the discount rate (liquidityIndex) to reach an astonishing 126,000,000.

This involves a concept where the interest of deposit users fundamentally comes from the growth of liquidity in the pool. The lending pool dynamically adjusts borrowing and deposit rates based on the deposit rate and utilization rate.

At this point, when the pool gains additional liquidity from the flash loan interest (1.8 WBTC), seventy percent (126,000,000) is counted into the liquidityIndex, which is used to calculate the discounted value of each unit deposit (hEthWbt).

Since the pool was empty before the hacker's operation, after repayment, the totalLiquidity was only 1, amount was 126000000, and the initial liquidityIndex was 1, resulting in 126000001.

Continue to Amplify the Discount Rate

The hacker continued to borrow 2000 WBTC from HopeLend via flash loans, repaying an additional 1.8 WBTC each time, allowing the LiquidityIndex to accumulate by 126,000,000 each time.

The hacker repeated this process 60 times, ultimately reaching a liquidityIndex of 7,560,000,001, making the discounted value of the 1 minimum unit of hEthWBTC held by the attacker reach 75.6 WBTC (approximately 2.14 million USD).

This allowed the hacker to manipulate hEthWBTC, distorting its value.

Draining Other Lending Pools with Existing Funds to Generate Profit

The attacker then used the 1 minimum unit of hEthWBTC as collateral to borrow large amounts of assets from five other token pools of HopeLend.

Including:

  • 175.4 - WETH
  • 145,522.220985 - USDT
  • 123,406.134999 - USDC
  • 844,282.284002229528476039 - HOPE
  • 220,617.821736563540747967 - stHOPE

These tokens were exchanged for WBTC and WETH through Uniswap as profit, and after deducting various fees, the hacker ultimately profited approximately 263 WETH (excluding the 263.9 WETH used for bribing the payload).

Why the Hacker Could Borrow Large Amounts from Other Pools:

When borrowing or withdrawing deposits, the lending contract checks the user's collateral status to ensure that the borrowed amount does not exceed the collateral.

Since the discount rate had already been manipulated by the hacker and the discount rate is included in the collateral value calculation using the normalizedIncome multiplier, the value of one unit of hEthWBTC in their possession reached 75.6 WBTC.

Each time the hacker borrowed from other pools, they easily passed the collateral asset verification.

At this point, the attacker had invested a total of 2000 + 1.8 * 60 WBTC into HopeLend to manipulate the liquidityIndex, leaving only 1 unit of hEtthWBTC.

Exploiting the Key Vulnerability (Integer Division Error) to Cash Out

To withdraw the previously invested WBTC, the attacker deployed another attack contract: 0x5a63e……844e74, and called the withdrawAllBtc() method:

The vulnerability process is as follows:

  1. First, deposit 151.20000002 WBTC. According to the current liquidityIndex (1 minimum unit hEthWBTC = 75.6 WBTC), the attacker obtains 2 minimum units of hEthWBTC.
  2. Withdraw 113.4 WBTC, reverse calculating the corresponding hEthWBTC shares, and perform a burn operation on hEthWBTC.
  3. 113.4 WBTC requires burning 1.9999999998 minimum units of hEthWBTC, but due to the precision issue of the div function, only one minimum unit of hEthWBTC is burned, thus creating an exploitable vulnerability, allowing the hacker to retain 1 minimum unit of hEthWBTC.

Key Vulnerability

The burn method of hEthWBTC calls a high-precision division rayDiv.

Here:

a = 11340000000 (the WBTC intended to be withdrawn)

b = 7560000001000000000000000009655610336 (discount rate)

Although (a * 1e27 + b / 2) / b = 1.9999999998, the built-in div method of Solidity truncates the return to 1, effectively truncating the decimal part of the division 11340000000 / 7560000001.

The attack contract (cash out) 0x5a63 continues to deposit 75.60000001 WBTC, perfectly obtaining another minimum unit of hEthWBTC, thus continuing to hold 2 minimum units of hEthWBTC.

This cycle of withdrawing 113.40000000 WBTC and depositing 75.60000001 WBTC allows the attacker to obtain 37.8 WBTC out of thin air.

After 58 cycles, the attacker withdrew all the previously invested WBTC and successfully repaid Aave's flash loan.

Conclusion

Due to the hEthWBTC lending pool not being initialized, the attacker was able to easily manipulate the liquidityIndex, increasing it to an extreme level. The withdrawal rate, as a divisor, was greatly amplified, and due to the truncation error of integer division, it became easier to withdraw the previously invested amounts within a single block.

In a well-functioning lending pool, since there is already liquidity in the pool, it is not easy for a small amount of loan interest to significantly increase the discount rate.

ChainCatcher reminds readers to view blockchain rationally, enhance risk awareness, and be cautious of various virtual token issuances and speculations. All content on this site is solely market information or related party opinions, and does not constitute any form of investment advice. If you find sensitive information in the content, please click "Report", and we will handle it promptly.
ChainCatcher Building the Web3 world with innovators