Back to Blog

Weitere Tragödie durch Präzisionsverlust: Tiefgehende Analyse des KyberSwap-Vorfalls

Code Auditing
December 5, 2023
12 min read

Am 23. November 2023 beobachteten wir eine Reihe von Angriffen auf KyberSwap. Diese Angriffe führten zu einem Gesamtschaden von über 48 Mio. US-Dollar. Unsere erste Analyse deutete darauf hin, dass der Exploit auf Tick-Manipulation und doppelte Liquiditätszählung zurückzuführen war. Aufgrund von Platzbeschränkungen können wir jedoch nicht auf die ausführlichen Details in diesem Beitrag eingehen. Trotz anschließender aufschlussreicher Analysen anderer Sicherheitsexperten blieb die Grundursache des Problems – Präzisionsverlust – unbeleuchtet.

Interessanterweise verdichtete sich die Handlung mehrere Tage später. Am 30. November 2023, nach mehreren Diskussionsrunden mit den Beamten, sandte der Angreifer eine Nachricht, die nach außen hin provokativ und nach vollständiger Kontrolle verlangend erschien. Davon abgesehen enthüllte der Angreifer auch eine entscheidende Information: Das Problem hängt tatsächlich mit Präzisionsverlust zusammen, wie die nachstehende Abbildung zeigt. Diese Enthüllung stärkt die Beweise für unsere Untersuchung. Daher ist es unser Ziel, in diesem Bericht eine umfassende Analyse zu präsentieren.

Wichtigste Erkenntnisse (TL;DR)

  • Unsere Untersuchung zeigt, dass das grundlegende Problem aus falscher Rundungsrichtung während des Reinvestitionsprozesses von KyberSwap resultiert. Dies führt anschließend zu einer fehlerhaften Tick-Berechnung und schließlich zur doppelten Liquiditätszählung.

  • Dieser Vorfall unterstreicht die komplexe und heimtückische Natur von Präzisionsverlustproblemen innerhalb von DeFi-Protokollen und stellt eine erhebliche Herausforderung für die gesamte Gemeinschaft dar.

  • Die Häufigkeit dieser Angriffe dient als eindringliche Erinnerung an die kritische Notwendigkeit proaktiver Bedrohungsvorbeugungsmaßnahmen, die erheblich dazu beitragen könnten, zukünftige Verluste zu reduzieren.

In den folgenden Abschnitten werden wir zunächst einige wichtige Hintergrundinformationen zu KyberSwap geben. Anschließend werden wir eine eingehende Analyse der Schwachstelle und des damit verbundenen Angriffs durchführen.

0x1 Hintergrund

KyberSwap[1] ist eine dezentrale Automated Market Maker (CLAMM)-Plattform. Um der Marktnachfrage nach konzentrierter Liquidität gerecht zu werden, wurde KyberSwap Elastic[3] auf Basis von Uniswap V3[2] gestartet, mit mehreren Verbesserungen, einschließlich der Reinvestitionskurve, um das automatische Compounding der Erträge aus der Liquiditätsbereitstellung zu ermöglichen.

0x1.1 Tick und Quadratwurzelpreis

Tick in Uniswap V3-ähnlichen CLAMMs wird verwendet, um den Preis diskret zu markieren, so dass LPs Liquidität innerhalb eines festen Bereichs anstelle des gesamten Bereichs (daher der Begriff "konzentriert") bereitstellen können)[4].

Um LPs die Festlegung von Liquiditätspositionen mit kundenspezifischen Preisintervallen zu ermöglichen, benötigte das Protokoll eine Möglichkeit, die aggregierte Liquidität über verschiedene Preispunkte hinweg zu verfolgen. Uniswap V3 erreichte dies durch die Partitionierung des Raums möglicher Preise in diskrete „Ticks“, wobei LPs zwischen beliebigen zwei Ticks Liquidität beisteuern konnten.

Laut [5] kann Liquidität in einem Bereich zwischen zwei beliebigen Ticks (die nicht nebeneinander liegen müssen) platziert werden, d. h. ein Paar von Tick-Indizes (ein unterer Tick und ein oberer Tick). Konkret ist der Preis jedes Ticks (bei einem ganzzahligen Index i) wie folgt definiert:

In der Praxis wird die Quadratwurzel des Preises (bezeichnet als sqrtP oder sqrtPrice) verwendet:

Es ist auch möglich, den aktuellen Tick basierend auf dem aktuellen Quadratwurzelpreis zu berechnen:

Die Verwendung des Quadratwurzelpreises zusammen mit der Liquidität L ist eine praktische Methode, um gleichzeitige Änderungen zu vermeiden. Insbesondere ändert sich der Preis beim Tauschen innerhalb eines Ticks; die Liquidität ändert sich beim Überqueren eines Ticks oder beim Minten oder Verbrennen von Liquidität. Eine detailliertere Erklärung finden Sie im Whitepaper von Uniswap V3[5].

Offensichtlich können, obwohl für einen gegebenen Tick nur ein einziger Quadratwurzelpreis berechnet wird, mehrere Quadratwurzelpreise auf denselben Tick verweisen.

0x1.2 Reinvestitionskurve

Uniswap V3-basierte CLAMMs leiden unter der Poolauslastung von LP-Gebühren und erheblichen Gasgebühren, die für die Reinvestition erforderlich sind. Daher hat KyberSwap die Reinvestitionskurve[6] übernommen, um das Problem zu lösen:

Die Reinvestitionskurve wurde mit dem alleinigen Zweck entwickelt, die sonst ungenutzten LP-Gebühren im konzentrierten Liquiditätsmodell nativ zu reinvestieren. Das bedeutete, dass LP-Gebühren für konzentrierte Liquiditätspositionen automatisch und ohne Gas oder manuellen Verwaltungsaufwand aufgestockt wurden. Darüber hinaus haben LPs immer noch die Möglichkeit, ihre automatisch aufgestockten Gebühreneinnahmen jederzeit separat abzuholen.

Der Schlüssel zur Reinvestitionskurve ist, dass die in jedem Tausch gesammelten Gebühren als zusätzliche Liquidität im Pool als Reinvestitionsliquidität im unendlichen Bereich angesammelt werden. Die Reinvestitionstoken werden an die LPs gemintet und die angesammelte Reinvestitionsliquidität wird entsprechend den LPs zugewiesen. Darüber hinaus nimmt die Reinvestitionsliquidität auch am Tausch- und Preisberechnungsprozess teil.

Genauer gesagt, anstelle der konstanten Produktformel:

werden die Gebühren in jedem Tausch in ΔL angesammelt:

Die Berechnung von ΔL kann vereinfacht werden (unter der Annahme, dass die Preisabweichung niedriger als ein Schwellenwert ist):

Dann können der Tauschbetrag und der Endpreis aus der modifizierten konstanten Produktformel abgeleitet werden:

Der entsprechende Code für die oben eingeführten Berechnungen ist in der Funktion computeSwapStep im folgenden Code-Snippet des entsprechenden Pools zu sehen.

Es ist zu beachten, dass aufgrund der Reinvestitionsliquidität die liquidity in dieser Funktion eine Summe aus zwei Komponenten ist: baseL für die Basisliquidität und reinvestL für die angesammelte Liquidität für die Reinvestition.

0x1.3 Swap in KyberSwap

Der Kontrollfluss eines Swaps in Uniswap V3 kann wie folgt dargestellt werden[5]:

Entsprechend kann die Implementierung der swap-Funktion des zuvor diskutierten KyberSwap-Pools als das folgende Diagramm abstrahiert werden:

Die entscheidende Logik, die die Tick-Berechnung betrifft, befindet sich innerhalb der Swap-While-Schleife, wie im blauen Rechteck hervorgehoben. Insbesondere beinhaltet die Hauptlogik die Funktion computeSwapStep und die Funktion _updateLiquidityAndCrossTick. Erstere berechnet wichtige Zustände wie Eingabe- und Ausgabemengen für den gegebenen Swap und nextSqrtP, während letztere Fälle behandelt, in denen ein Tick-Übergang auftritt.

Traditionell bezeichnen wir, wenn der Preis steigt, dies als Verschiebung des Ticks nach rechts/oben; andernfalls sagen wir, der Tick bewegt sich nach links/unten.

Um die später zu diskutierende Schwachstelle besser zu verstehen, ist es unerlässlich, die relevante Code-Logik der Funktion computeSwapStep zu untersuchen, wie in der folgenden Abbildung dargestellt:

Zunächst werden in den Zeilen 50 bis 57 die calcReachAmount-Funktion aufgerufen, um die Menge des benötigten Eingabetokens zur Erreichung des targetSqrtP (nächster Tick oder benutzerdefinierter Zielpreis) zu berechnen.

Als nächstes wird zwischen den Zeilen 59 und 62 ein Test durchgeführt, um festzustellen, ob der Tick überquert werden soll oder nicht.

Insbesondere wenn die verwendete Menge (usedAmount) größer ist als die vom Benutzer angegebene Menge (specifiedAmount) beim exakten Eingabe-Swap (der im Angriff verwendete Fall), bedeutet dies, dass der Tick nicht überquert werden sollte und der nextSqrtP aus der inkrementellen Liquidität (deltaL, d. h. der Delta-Liquidität) abgeleitet werden muss.

  • Anschließend werden in den Zeilen 70 bis 79 die ΔL (deltaL) aus der Eingabemenge, der aktuellen Liquidität und dem Preis unter Verwendung der Funktion estimateIncrementalLiquidity abgeleitet. Schließlich wird der Endpreis nach dem Swap nextSqrtP basierend auf dem deltaL, der Eingabemenge, dem aktuellen Preis und der Liquidität unter Verwendung der Funktion calcFinalPrice berechnet.

Umgekehrt, wenn die benötigte Menge kleiner ist als die vom Benutzer angegebene Menge (was bedeutet, dass nextSqrtP > 0 ist), wird deltaL unter Verwendung des aktuellen und des Ziel-sqrtP berechnet, und der nextSqrtP ist der sqrtP des nächsten Ticks. Die Details werden weggelassen, da dieser Zweig im Angriff nicht verwendet wird.

Die oben genannten Schritte verdeutlichen, dass, wenn der Tick nicht überquert wird, der von computeSwapStep zurückgegebene nextSqrtP nicht größer sein sollte als der sqrtP des nächsten Ticks. Aufgrund der Abhängigkeit des Preises von der Liquidität (Basisliquidität und Delta-Liquidität) und des Präzisionsverlusts ist es dem Angreifer jedoch möglich, den nextSqrtP so zu manipulieren, dass er größer wird, während der Tick nicht überquert wird.

0x2 Schwachstellenanalyse

Die Grundursache liegt in der fehlerhaften Tick-Berechnung, die durch die falsche Rundungsrichtung innerhalb der Delta-Liquiditätsberechnung (d. h. der Funktion estimateIncrementalLiquidity) des SwapMath-Vertrags (der von der Funktion computeSwapStep aufgerufen wird) verursacht wird. Dies beeinträchtigt wiederum die spätere Tick-Berechnung fehlerhaft.

Interessanterweise stellen wir bei der Untersuchung des Kommentars in Zeile 188 (hervorgehoben durch das blaue Rechteck) fest, dass deltaL aufgerundet werden soll, um den nextSqrtP abzurunden. Aufgrund der Verwendung der Funktion mulDivFloor in Zeile 189 wird deltaL jedoch versehentlich abgerundet. Folglich wird nextSqrtP falsch aufgerundet.

0x3 Angriffsanalyse

Die Angreifer initiierten mehrere Angriffstransaktionen, wobei jede Transaktion mehrere Pools leerte. Der Einfachheit halber basiert die folgende Diskussion auf dem ersten Angriff innerhalb der Angriffstransaktion.

Die Kernangriffslogik besteht aus den folgenden sechs Schritten:

  1. Ausleihen von 2.000 WETH über einen Flash-Loan von AAVE.

  2. Tausch von 6,850 WETH gegen 6,371 frxETH im Opferpool 0xfd7b. Dieser Schritt dient dazu, den aktuellen Tick und currentSqrtP an eine Stelle zu verschieben, an der derzeit keine Liquidität vorhanden ist.

  • currentSqrtP scheint vom Angreifer zufällig gewählt zu werden, und der Tausch stoppt genau bei diesem Preis.
  • Die Basisliquidität (baseL) ist nach diesem Schritt null, aber die Reinvestitionsliquidität (reinvestL) ist ungleich null.
  1. Hinzufügen von Liquidität zum Pool und anschließendes Entfernen eines Teils der Liquidität. Dieser Schritt dient zur Steuerung des Bereichs und der Gesamtliquidität auf einen gewünschten Betrag.
  • Der Tick-Bereich wird basierend auf dem currentSqrtP gewählt.
  • Die gewünschte Liquidität für den Angriff könnte aus dem Tick-Bereich abgeleitet werden, obwohl die entsprechende Berechnungslogik weiter untersucht werden muss.
  1. Tausch von 387,170 WETH gegen 0,06 frxETH im Pool. Dieser Schritt dient zur Manipulation des aktuellen Ticks, so dass nextTick == currentTick.
  • Die Eingabemenge wird basierend auf der Liquidität und dem currentSqrtP gewählt.
  1. Tausch von 0,06 frxETH gegen 396,244 WETH im Pool. Beachten Sie, dass die Tauschrichtung im Vergleich zum vorherigen Schritt entgegengesetzt ist. In diesem Schritt wird die Liquidität doppelt gezählt, um den Tausch profitabel zu machen und folglich den Pool zu leeren.

  2. Rückzahlung des Flash-Loans und Ernte von 6,364 WETH und 1,117 frxETH.

Offensichtlich sind die letzten beiden Tauschgeschäfte (Schritt 4 und Schritt 5) die entscheidenden Angriffsschritte zur Manipulation der Tick-Berechnung und zur Profitabilität des Tauschs zur Entleerung des Pools. Wir werden uns im Folgenden mit den Details befassen.

Es ist wichtig zu beachten, dass Schritt 3 entscheidend für die Manipulation der Liquidität ist. Aufgrund der Notwendigkeit einer präzisen Tick-Manipulation durch die Rundungsoperation ist es nicht praktikabel, das Ziel durch direktes Hinzufügen von Liquidität zu erreichen. Die Liquiditätsentnahme dient der präzisen Steuerung der Liquidität im gewünschten Bereich des Angreifers.

0x3.1 Schritt 4: Manipulation des aktuellen Ticks und currentSqrtP

Nach den vorherigen Schritten (Schritt 1 und 2) hat der Angreifer den Tick-Bereich und die Liquidität für die Manipulation vorbereitet. Insbesondere:

  • currentSqrtP befindet sich an einer gewünschten Stelle
  • aktueller Tick = 110.909 und nächster Tick = 111.310, die den currentSqrtP umgeben

Dieser Schritt tauscht WETH gegen frxETH. In der Funktion computeSwapStep haben wir die folgende Ausführungsspur:

Wie in der obigen Abbildung gezeigt, wird die zu erreichende Menge (d. h. der nächste Tick) durch Aufruf der Funktion calcReachAmount berechnet:

  • usedAmount = calcReachAmount(liquidity, currentSqrtP, targetSqrtP)

Beachten Sie, dass diese Berechnung vor dem Tausch abgeleitet werden kann. Durch sorgfältige Auswahl des specifiedAmount (usedAmount = specifiedAmount + 1) hat der Angreifer den Tausch so gesteuert, dass das Ziel (d. h. der nächste Tick 111.310) nicht erreicht wird, was dazu führt, dass nextSqrtP = 0 ist.

In dieser Situation, da der Tick nicht überquert wird, muss nextSqrtP (d. h. der Endpreis) aus der Delta-Liquidität (angesammelt als Tauschgebühren) abgeleitet werden.

Zuerst wird die inkrementelle Liquidität deltaL aus den Gebühren berechnet durch:

  • deltaL = estimateIncrementalLiquidity(absDelta, currentSqrtP)

Dann der Endpreis nextSqrtP:

  • nextSqrtP = calcFinalPrice(absDelta, liquidity, deltaL, currentSqrtP)

Wenn wir auf den Fehler der Rundungsrichtung zurückkommen, der im vorherigen Abschnitt diskutiert wurde, werden hier deltaL fälschlicherweise abgerundet, was dazu führt, dass nextSqrtP aufgerundet wird. Insbesondere in diesem Fall ergeben sich bei gleicher absDelta (387.170.294.533.119.999.999) aufgrund unterschiedlicher Rundungsrichtungen unterschiedliche Berechnungsergebnisse:

Daher sind nach der Tick-Manipulation in Schritt 4 die aktuellen Zustände wie folgt zusammengefasst:

  • currentSqrtP ist 20.693.058.119.558.072.255.665.971.001.964, geringfügig größer als der sqrtP bei Tick 111.310 (sqrtP bei 111.310 = 20.693.058.119.558.072.255.662.180.724.088).
  • aktueller Tick = 111.310 und nächster Tick = 111.310

Wie in der obigen Abbildung dargestellt, täuscht der Swap in Schritt 4 den Pool geschickt vor, dass Tick 111.310 nicht überquert wurde. In Wirklichkeit ist der currentSqrtP jedoch tatsächlich größer als der sqrtP von Tick 111.310.

0x3.2 Schritt 5: Doppelte Liquiditätszählung

Basierend auf der Manipulation in Schritt 4 ist die Angriffslogik in Schritt 5 recht einfach. In diesem Stadium orchestrierte der Angreifer einen umgekehrten Tausch von frxETH zu WETH, der den Tick und den currentSqrtP nach links verschieben würde. Insbesondere wird die Funktion computeSwapStep innerhalb der Schleife zweimal aufgerufen, was letztendlich auf unvorhergesehene Weise die doppelte Liquiditätszählung[7] auslöst und folglich zusätzliche Gewinne generiert.

Wie im obigen Trace gezeigt:

  • Bei der ersten Aufrufung der Funktion computeSwapStep wurde der currentSqrtP auf den sqrtP von Tick 111.310 verschoben. Dies ist ein winziger Tausch, der nur 3 Wei frxETH verwendet, um tatsächlich Tick 111.310 zu erreichen. Anschließend sollte innerhalb der Funktion _updateLiquidityAndCrossTick der aktuelle Tick Tick 111.310 überqueren (sich nach links/unten bewegen), obwohl er in Schritt 4 Tick 111.310 in Richtung rechts/oben nicht wirklich durchlaufen hat. Dies führt dazu, dass die Liquidität bei Tick 111.310 zweimal gezählt wird.

  • Bei der zweiten Aufrufung der Funktion computeSwapStep kann die vorherige doppelte Zählung der Liquidität zu zusätzlichen Gewinnen führen. Insbesondere durch Ausnutzung dieser doppelten Liquiditätszählung wird der Tauschpreis im abschließenden Schritt verzerrt, was zu einem größeren WETH-Tausch führt und somit einen Gewinn generiert.

0x4 Zusammenfassung der Angriffe und Gewinne

Zum Zeitpunkt der Erstellung dieses Berichts haben wir eine Reihe von Angriffen auf verschiedenen Chains (einschließlich Ethereum, Optimism, Polygon, Arbitrum, Avalanche und Base) in freier Wildbahn beobachtet, die zu Verlusten von über 48 Mio. US-Dollar führten. Diese Angriffe wurden von verschiedenen Angreifern durchgeführt, wie folgt:

Eine vollständige Liste dieser Angriffstransaktionen wurde in einem von uns erstellten Dokument gesammelt. Bitte beziehen Sie sich für detailliertere Informationen darauf.

0x5 Schlussfolgerung

Zusammenfassend lässt sich sagen, dass es sich hier um eine subtile Schwachstelle handelt, die aus einer fehlerhaften Rundungslogik resultiert. Der Exploit ist unglaublich raffiniert. Tatsächlich haben wir in diesem Jahr eine Reihe von Sicherheitsvorfällen im Zusammenhang mit Präzisionsverlustproblemen beobachtet, die erhebliche Herausforderungen für die Gemeinschaft darstellen.

Diese kontinuierlichen Angriffe zeigen einmal mehr die Bedeutung proaktiver Bedrohungsvorbeugung, einer Strategie, die potenzielle Verluste wirksam mindern kann.

Referenz

[1] https://docs.kyberswap.com/

[2] https://blog.uniswap.org/uniswap-v3

[3] https://docs.kyberswap.com/liquidity-solutions/kyberswap-elastic

[4] https://docs.kyberswap.com/liquidity-solutions/kyberswap-elastic/concepts/tick-range-mechanism

[5] https://uniswap.org/whitepaper-v3.pdf

[6] https://docs.kyberswap.com/liquidity-solutions/kyberswap-elastic/concepts/reinvestment-curve

[7] https://100proof.org/kyberswap-post-mortem.html

Sign up for the latest updates
The Decentralization Dilemma: Cascading Risk and Emergency Power in the KelpDAO Crisis
Security Insights

The Decentralization Dilemma: Cascading Risk and Emergency Power in the KelpDAO Crisis

This BlockSec deep-dive analyzes the KelpDAO $290M rsETH cross-chain bridge exploit (April 18, 2026), attributed to the Lazarus Group, tracing a causal chain across three layers: how a single-point DVN dependency enabled the attack, how DeFi composability cascaded the damage through Aave V3 lending markets to freeze WETH liquidity exceeding $6.7B across Ethereum, Arbitrum, Base, Mantle, and Linea, and how the crisis forced decentralized governance to exercise centralized emergency powers. The article examines three parameters that shaped the cascade's severity (LTV, pool depth, and cross-chain deployment count) and provides an exclusive technical breakdown of Arbitrum Security Council's forced state transition, an atomic contract upgrade that moved 30,766 ETH without the holder's signature.

Weekly Web3 Security Incident Roundup | Apr 13 – Apr 19, 2026
Security Insights

Weekly Web3 Security Incident Roundup | Apr 13 – Apr 19, 2026

This BlockSec weekly security report covers four attack incidents detected between April 13 and April 19, 2026, across multiple chains such as Ethereum, Unichain, Arbitrum, and NEAR, with total estimated losses of approximately $310M. The highlighted incident is the $290M KelpDAO rsETH bridge exploit, where an attacker poisoned the RPC infrastructure of the sole LayerZero DVN to fabricate a cross-chain message, triggering a cascading WETH freeze across five chains and an Arbitrum Security Council forced state transition that raises questions about the actual trust boundaries of decentralized systems. Other incidents include a $242K MMR proof forgery on Hyperbridge, a $1.5M signed integer abuse on Dango, and an $18.4M circular swap path exploit on Rhea Finance's Burrowland protocol.

Weekly Web3 Security Incident Roundup | Apr 6 – Apr 12, 2026
Security Insights

Weekly Web3 Security Incident Roundup | Apr 6 – Apr 12, 2026

This BlockSec weekly security report covers four DeFi attack incidents detected between April 6 and April 12, 2026, across Linea, BNB Chain, Arbitrum, Optimism, Avalanche, and Base, with total estimated losses of approximately $928.6K. Notable incidents include a $517K approval-related exploit where a user mistakenly approved a permissionless SquidMulticall contract enabling arbitrary external calls, a $193K business logic flaw in the HB token's reward-settlement logic that allowed direct AMM reserve manipulation, a $165.6K exploit in Denaria's perpetual DEX caused by a rounding asymmetry compounded with an unsafe cast, and a $53K access control issue in XBITVault caused by an initialization-dependent check that failed open. The report provides detailed vulnerability analysis and attack transaction breakdowns for each incident.

Best Security Auditor for Web3

Validate design, code, and business logic before launch. Aligned with the highest industry security standards.

BlockSec Audit