Analysis of the $9M Attack on Yearn

CN
15 hours ago

Preface

On December 1, 2025, the Yearn protocol suffered a meticulously designed multi-stage complex hacking attack, ultimately resulting in a loss of approximately $9 million in assets. The attack was not a single exploit but rather a series of maneuvers where the attacker leveraged funds through flash loans, gradually manipulated the state of the liquidity pool by exploiting multiple logical flaws in the protocol, and ultimately executed a malicious event that allowed for nearly unlimited minting of yETH-related LP tokens, draining the liquidity pool.

This attack used flash loans as the initial funding leverage, breaking through the protocol's defenses through multi-step operations. The core attack process can be divided into four key stages: fund preparation, state manipulation, unlimited minting, and profit realization. Each step was intricately linked, precisely exploiting the logical vulnerabilities in the protocol's design.

Attack tx: https://etherscan.io/tx/0x53fe7ef190c34d810c50fb66f0fc65a1ceedc10309cf4b4013d64042a0331156

Technical Analysis

First, two flash loans were taken out from Balancer and Aave to borrow wstETH, rETH, WETH, and 0xa35b_ETHx, rETH, wstETH, cbETH.

In the flash loan callback function, 1100 ETH was deposited into Tornado.Cash: 100 ETH mixing, and then 100 ETH was withdrawn to the malicious contract 0x3e8e7533dcf69c698Cf806C3DB22f7f10B9B0b97 using the Tornado.Cash: 100 ETH withdraw function, triggering its fallback function.

In the fallback function, it can be seen that the last exchange value matches the next remove_liquidity value, indicating that the previous steps were all aimed at converting the assets obtained from the flash loan into a large amount of yETH weighted stableswap pool LP tokens through exchange operations, preparing for the subsequent attack.

At this point, the core attack process officially began.

  1. First, the LP tokens obtained from the above exchange were completely destroyed through the remove_liquidity function of the yETH weighted stableswap pool, converting them into eight underlying assets distributed according to their shares.

The logic of the removeliquidity function can be understood as follows: assuming the pool has a total of 10,000 LP Tokens, if you destroy 416.37 LP Tokens, your proportion is: 416.37 / 10,000 = 4.16%._

Then for each asset, assuming there are 1,000 wstETH in the pool (virtual balance prevvb). The virtual balance you can withdraw: dvb = 1,000 * 416.37 / 10,000 = 41.637 wstETH, and then divided by the exchange rate to convert to the actual token amount._

  1. Next, by repeatedly calling add_liquidity, similar to injecting a single-sided pool, there are a total of 8 _amounts parameters corresponding to the quantities of different assets to be injected. It was observed that in the previous few loops, index3 [rETH token], index6 [wOETH token], and index7 [mETH token] all input 0, meaning that these three tokens were not added during each liquidity injection.

By injecting single-sided assets in this manner and extracting tokens from the pool, the quantity gap between rETH, w0 ETH, mETH, and other tokens in the pool was artificially widened.

  1. Immediately after, a massive amount of rETH tokens were injected, followed by a crucial step of remove_liquidity, but with _amount=0.

Why can 0 tokens be extracted? Through the internal implementation of remove_liquidity.

The removeliquidity function did not short-circuit for 0 amounts, still executing the complete vbprod calculation loop, and it was based on the artificially created token quantity gap in the pool that calculated and updated the global packedpoolvb state.

  1. Then, the updaterates function was called, which only updated the pool ratio of index6 [wOETH], and finally called removeliquidity to extract tokens from the pool, at which point the quantity of W0 ETH in the pool was already very low.

  1. Similarly, by using a similar method to drain the shares of index6 [w0 ETH] and index7 [mETH] in the pool, note that in the first two updates of index6, tokens were immediately extracted using remove_liquidity, while the last index7 [mETH] was only updated and not yet extracted.

At this point, through the above method of massively injecting single-sided pools and continuously extracting all pool tokens, the ratio of W0 ETH and mETH in the pool was nearly 0.

At this time, a new malicious contract 0xADbE952eBB9b3e247261d2E3b96835f00f721f8E was created, and all tokens were transferred to this contract. Note that in the previous step, the LP tokens obtained from the single-sided addition of rETH were not exchanged for underlying tokens but were also transferred to the new malicious contract.

The previous attack operations updated index7 [mETH] without extracting the tokens, and here they were extracted by calling remove_liquidity. At this point, the share of index6 [w0 ETH] in the pool was very small, and the share of index7 [mETH] was even smaller.

At this point, the token ratios in the pool were severely imbalanced, and the attacker called add_liquidity again to add liquidity, obtaining a massive amount of LP tokens in the ratio of [1, 1, 1, 1, 1, 1, 1, 9].

By this point, the attacker had already obtained a large amount of LP tokens, and the subsequent steps involved realizing profits through exchange, redeem, and other methods while repaying the fees for the flash loans.

Attack Review

This attack was a complex multi-stage combination attack, where the attacker exploited three core vulnerabilities in the pool.vy contract: Precision Loss, Yield Stripping, and Zero Supply Initialization.

Stage One: Creating Extreme Imbalance

  • Operation: The attacker repeatedly called add_liquidity but deliberately avoided index 3 (rETH), index 6 (wOETH), and index 7 (mETH).
  • Purpose: To artificially create an imbalance in the asset ratios within the pool.
  • Key Step: Subsequently, a massive single-sided injection of rETH.
  • Consequence: This greatly widened the quantity gap between rETH and other assets (especially wOETH and mETH), creating mathematical conditions for precision loss.

Stage Two: Triggering and Locking Errors

  • Operation: Called removeliquidity(amount=0).
  • Principle:

remove_liquidity did not short-circuit for 0 amounts.

Even without transferring funds, the contract still executes the complete vb_prod calculation loop.

Under extreme weight imbalance, the powdown function produces significant rounding down errors.

The contract writes this underestimated vbprod into the global state packedpool_vb.

  • Essence: This is a "zero-cost" state attack, where the attacker successfully manipulated the pool's book value without incurring any costs.

Stage Three: Yield Stripping and Share Extraction

  • Operation:

update_rates([6]) (update wOETH exchange rate).

remove_liquidity (extract assets).

update_rates([7]) (update mETH exchange rate).

  • Principle:

update_rates triggers _updatesupply. Due to vbprod being maliciously suppressed earlier, the system misjudges the pool's value as diminished, leading to the destruction of LP tokens held by the Staking contract to balance the accounts.

The attacker exploits remove_liquidity to arbitrage before and after the exchange rate updates, gradually draining the shares of wOETH and mETH in the pool.

  • Result: A large number of shares in the Staking contract are destroyed, passively increasing the LP share ratio held by the attacker, pushing the pool's Total Supply towards 0.

Stage Four: Zero Supply Unlimited Minting

  • Precondition: After the above operations, the pool has been drained, Total Supply is close to 0, and the balances of wOETH and mETH are extremely low.
  • Operation: add_liquidity, with parameters _amounts=[1, 1, 1, 1, 1, 1, 1, 9].
  • Principle:

When prev_supply ≈ 0, the iterative formula of _calc_supply fails when handling extremely small values (1 wei, 9 wei).

The contract incorrectly calculates an astronomical amount of LP Token minting.

  • Result: The attacker obtained 235,443… yETH LP Tokens out of thin air.

Summary

The Yearn attack incident exposed multiple deficiencies in DeFi protocols regarding edge case logic verification, numerical calculation precision control, and multi-vulnerability combination risk prevention. The attacker's use of flash loans as a tool, the exploitation of combined vulnerabilities as the core, and the obfuscation of funds as a cover highlight the trend of increasing specialization and complexity in current DeFi attacks. The core lessons from this attack include: first, protocols need to strengthen logical verification for edge cases such as "zero amounts" and "extreme imbalances" to avoid state manipulation risks due to lack of short-circuit handling; second, numerical calculations must pay attention to precision loss issues under extreme ratios, optimizing the calculation logic of key functions like powdown. The Balancer protocol previously experienced a security incident due to precision loss, serving as a cautionary tale; third, a multi-dimensional risk monitoring system should be established to provide early warnings for suspicious operations such as high-frequency single-sided liquidity injections and abnormal exchange rate updates. For the entire DeFi industry, this incident once again proves that protocol security requires not only the repair of individual vulnerabilities but also a comprehensive perspective to prevent attacks that exploit multiple vulnerabilities in combination, while enhancing tracking and interception of the attacker's fund flows to improve the overall security protection capabilities of the industry.

免责声明:本文章仅代表作者个人观点,不代表本平台的立场和观点。本文章仅供信息分享,不构成对任何人的任何投资建议。用户与作者之间的任何争议,与本平台无关。如网页中刊载的文章或图片涉及侵权,请提供相关的权利证明和身份证明发送邮件到support@aicoin.com,本平台相关工作人员将会进行核查。

Share To
APP

X

Telegram

Facebook

Reddit

CopyLink