zkLend Exploit Post-Mortem: Details zur 10-Mio.-Dollar-Flashloan-Attacke und Aufklärung von Missverständnissen

Dieser Blog bietet eine detaillierte Analyse des zkLend-Vorfalls zur Klärung von Missverständnissen.

zkLend Exploit Post-Mortem: Details zur 10-Mio.-Dollar-Flashloan-Attacke und Aufklärung von Missverständnissen

Am 12. Februar 2025 wurde zkLend [1], ein Kreditprotokoll auf StarkNet, durch eine ausgeklügelte Manipulation seines Akkumulator-Mechanismus für rund 10 Mio. US-Dollar ausgenutzt. Der Angreifer nutzte Flash-Kredite und Rundungsfehler, um die Werte der Sicherheiten künstlich aufzubläuen und andere Vermögenswerte aus dem Protokoll zu leihen, um Profit zu erzielen.

Es mangelt jedoch weiterhin an detaillierten und genauen technischen Analysen aus Sicherheitsperspektive. Trotz bestehender Analysen von anderen Sicherheitsforschern, die wertvolle Einblicke lieferten, bestehen einige Missverständnisse fort – insbesondere hinsichtlich der Angriffsanalyse. zkLends spätere Veröffentlichung des offiziellen Post-Mortems [2] bietet eine vereinfachte Beschreibung, es fehlt jedoch eine detaillierte technische Analyse. In diesem Blogbeitrag möchten wir eine umfassende Untersuchung zur Klärung des Vorfalls liefern.

Wichtigste Erkenntnisse (TL;DR)

  • Die Grundursache dieses Vorfalls liegt in der Kombination der folgenden drei Probleme:

    • Die leere Markteinführung ermöglicht beliebige Einzahlungen von Vermögenswerten.
    • Der spezifische Spendenmechanismus im Flash-Kredit von zkLend ermöglicht die Manipulation des Akkumulators, einer globalen Variable als Skalierungsfaktor zur dynamischen Anpassung der Salden der Sicherheiten von Benutzern.
    • Präzisionsverlust durch Abschneiden. Im Gegensatz zum klassischen Präzisionsverlust bei der Division beginnt der Nenner bei 1, wurde aber auf einen sehr großen Wert aufgebläht, was zu einer Unterschätzung bei der Verbrennung des Anteil-Tokens führte.
  • Der Angreifer profitierte nicht von wstETH, das von anderen Nutzern eingezahlt wurde. Stattdessen nutzte der Angreifer die Schwachstellen aus, um den Saldo der Sicherheiten zu manipulieren, indem er eine kleine Menge wstETH als Anfangskapital verwendete, um den Saldo der Sicherheiten auf über 7.000 wstETH zu erhöhen, was die Aufnahme anderer Vermögenswerte aus dem Markt ermöglichte.

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

0x1 Hintergrund: Verstehen des Kernprotokolls von zkLend

zkLend ist ein Kreditprojekt auf StarkNet, das gängige Kreditprotokolle wie besicherte Kredite und Flash-Kredite unterstützt. Tauchen wir in die Implementierungsdetails dieser beiden Protokolle ein.

0x1.1 Besicherte Kredite

Ein besicherter Kredit bezieht sich auf den Prozess, bei dem Benutzer bestimmte Vermögenswerte als Sicherheit im Protokoll hinterlegen, im Austausch für die Aufnahme anderer Vermögenswerte. Der Wert der Sicherheit bestimmt die Kreditkapazität. Es ist wichtig zu beachten, dass Kreditprotokolle den Wert der Sicherheit in der Regel nicht direkt speichern; stattdessen berechnen sie ihn anhand der Formel:

kollateral_saldo = lending_akkumulator * raw_balance

Insbesondere der lending_akkumulator ist ein Skalierungsfaktor, der den Wert der Sicherheit jedes Benutzers dynamisch anpasst, während raw_balance den tatsächlichen Anteil darstellt, den der Benutzer im Markt hält. raw_balance wird aus dem kollateral_saldo mithilfe des lending_akkumulator abgeleitet.

Was ist der Zweck dieses Designs? Es ermöglicht dem Protokoll, den Wert der Sicherheiten effizient zu verwalten und gleichzeitig die Benutzer zur Einzahlung von Vermögenswerten zu ermutigen. Durch die Zuweisung eines Teils der Einnahmen des Protokolls an die Sicherheitengeber erhöht sich der lending_akkumulator, wodurch der Wert der Sicherheiten aller Benutzer proportional und gleichzeitig vervielfacht wird.

0x1.2 Flash-Kredite auf zkLend

Ein Flash-Kredit ist eine Art unbesicherter Kredit, bei dem Benutzer Vermögenswerte für einen sehr kurzen Zeitraum, typischerweise innerhalb einer einzigen Transaktion, aus dem Protokoll leihen können. Wenn der Kreditnehmer den Kredit nicht zurückzahlen kann oder die angegebenen Bedingungen nicht erfüllt, wird die gesamte Transaktion rückgängig gemacht und der Kredit nicht ausgeführt.

In der Flash-Kredit-Implementierung von zkLend gibt es einen einzigartigen Spenden-Mechanismus. Speziell, wenn Benutzer Vermögenswerte zurückzahlen, leisten sie nicht nur den erforderlichen Mindestbetrag, sondern können auch zusätzliche Mittel als Spende beisteuern. Das Protokoll verfolgt diese gespendeten Mittel und aktualisiert den lending_akkumulator entsprechend. Dieser Prozess ist in der Funktion thesettle_extra_reserve_balance() implementiert. Die Formel zur Aktualisierung des lending_akkumulator lautet wie folgt:

new_akkumulator = (reserve_balance + totaldebt - amount_to_treasury) / ztoken_supply

  • reserve_balance: Der Gesamtbetrag des zugrunde liegenden Tokens (z. B. wstETH), der im Vertrag gehalten wird, einschließlich des von den Benutzern gespendeten Tokenbetrags.
  • totaldebt: Die Gesamtschuld aller Kreditnehmer.
  • amount_to_treasury: Der Betrag der Einnahmen des Protokolls.
  • ztoken_supply: Die gesamte Versorgung des Anteil-Tokens (z. B. zwstETH). Wenn Benutzer wstETH einzahlen, prägt der zkLend ztoken-Vertrag einen entsprechenden Betrag an zwstETH.

Nachdem das Kernprotokoll von zkLend verstanden wurde, werden wir nun formell erklären, wie der Angreifer seine Sicherheit Vermögenswerte durch die Manipulation der Variablen lending_akkumulator und raw_balance manipuliert hat.

0x2 Analyse des Angriffs

Der Angreifer nutzte die folgenden Mechanismen und Schwachstellen im zkLend-Vertrag aus, um den Wert der Sicherheit zu manipulieren:

  • Manipulation des lending_akkumulator
    • Leerer Markt: Vor dem Angriff war der zkLend-Markt für wstETH-Tokens leer, was die perfekte Bedingung für die Manipulation bot. Darüber hinaus erlaubt der zkLend Market-Vertrag jedem, beliebige Mengen von Vermögenswerten in einen leeren Markt einzuzahlen. Der Angreifer zahlte eine geringe Menge an Vermögenswerten ein, um den Wert des lending_akkumulator erheblich zu erhöhen.
    • Spendenmechanismus: Die flash_loan()-Funktion des zkLend Market-Vertrags verfügt über einen einzigartigen Spenden-Mechanismus. Insbesondere wenn ein Benutzer einen Flash-Kredit zurückzahlt, berechnet der Market-Vertrag die überschüssigen zurückgegebenen Mittel und erhöht die globale Variable lending_akkumulator, wodurch die Sicherheitenwerte für alle Benutzer im Vertrag vervielfacht werden.
  • Manipulation der raw_balance
    • Rundungsverhalten: Die Division während des Prozesses der Anteil-Token-Verbrennung verwendet Abschneiden, was zu einer Unterschätzung der Änderung der raw_balance des Benutzers bei Abhebungen führt.

Durch die Manipulation beider Variablen konnte der Angreifer den Saldo der Sicherheiten auf über 7.000 wstETH erhöhen und andere Vermögenswerte aus dem Markt leihen, um Profit zu erzielen.

0x2.1 Manipulation der Variablen lending_akkumulator

0x2.1.1 Leere Markteinführung

Durch die Untersuchung des Transaktionsprotokolls des Market-Vertrags vor dem Angriff können wir beobachten, dass der Angreifer zunächst 1 wei wstETH in den wstETH Market-Vertrag eingezahlt hat. Bei Überprüfung der internen Aufrufe dieser Transaktion ist ersichtlich, dass der wstETH Market-Vertrag 0 wstETH enthielt und die Gesamtmenge an zwstETH ebenfalls 0 war.

Daher können wir bestätigen, dass es keine vorherigen Einzahlungen oder Kredite im zkLend wstETH-Markt gab. Sowohl der reserve_balance als auch die ztoken_supply befanden sich in ihren Anfangswerten von 0, und der Anfangswert des lending_akkumulator war 1. Dieses leere Marktszenario schuf die Bedingungen für den nachfolgenden Angriff und ermöglichte es dem Angreifer, den lending_akkumulator mit einer minimalen Menge wstETH erheblich zu vervielfachen.

0x2.1.2 Manipulation des lending_akkumulator über Flash-Kredit

Als nächstes, in dieser Transaktion, ruft der Angreifer die flash_loan()-Funktion auf, leiht sich 1 wei wstETH und zahlt 1000 wei wstETH zurück. Die überschüssigen 999 wei werden als Spende behandelt und in den reserve_balance des Vertrags eingetragen.

Laut der Formel zur Berechnung des lending_akkumulator führt diese Transaktion dazu, dass der lending_akkumulator von 1 auf 851,0 ansteigt.

0x2.1.3 Wiederholte Ausführung von flash_loan()

Der Angreifer führt insgesamt 10 flash_loan()-Aufrufe aus, leiht sich jedes Mal nur 1 wei wstETH, zahlt aber einen größeren Betrag zurück. Infolgedessen steigt der lending_akkumulator auf einen astronomischen Wert von 4.069.297.906.051.644.020 (4,069 × 10^18), was zufällig mit der Dezimalgenauigkeit von wstETH übereinstimmt.

0x2.2 Manipulation der Variablen raw_balance

Nachdem der lending_akkumulator auf etwa 4,069 × 10^18 manipuliert wurde, rief der Angreifer die deposit()-Funktion des Market-Vertrags mit 4,069297906051644020 wstETH auf. Basierend auf dem neuesten Wert des lending_akkumulator wurde der raw_balance des Angreifervertrags 2.

0x2.2.1 Die erste Transaktion zur Manipulation von raw_balance

In dieser Transaktion rief der Angreifer die callflashloandraaan()-Funktion des Angreifervertrags auf. Obwohl dieser Vertrag nicht Open Source ist, kann basierend auf der internen Aufrufliste vermutet werden, dass die Logik dieser Funktion eine Schleife enthält, die folgende Aktionen ausführt:

  • Einzahlung: Der Angreifer zahlt eine bestimmte Menge wstETH in den Marktvertrag ein.
  • Abhebung: Der Angreifer hebt den spezifischen Betrag wstETH ab.

Analyse des Token-Übertragungsprotokolls

Es ist zu beobachten, dass der Betrag an wstETH, den der Angreifer einzahlt, immer ein ganzzahliges Vielfaches des lending_akkumulator ist, z. B. das 2-fache des Wertes (z. B. 8.13859) des lending_akkumulator.

Der abgehobene wstETH-Betrag ist jedoch das 1,5-fache des Wertes (z. B. 6.10394) des lending_akkumulator.

Durch Berechnungen können wir feststellen, dass der abgehobene wstETH-Betrag den eingezahlten Betrag übersteigt. Warum passiert das?

Rundungsverhalten

Durch die Überprüfung der Implementierung der deposit()- und withdraw()-Methoden können wir sehen, dass diese beiden Methoden die Prägung und Verbrennung von zwstETH beinhalten. Hier ist, wie das funktioniert:

`mint()`-Funktion im Market-Vertrag

`burn()`-Funktion im Market-Vertrag

Die Prozesse mint() und burn() beinhalten beide eine Skalierungslogik. Die Skalierungslogik beinhaltet Ganzzahldivision mit Floor-Rundung (Abrundung zur nächsten ganzen Zahl), die eine Schlüsselrolle beim Exploit spielt.

Wenn der Angreifer einen bestimmten Betrag an zwstETH verbrennt, wird die skalierte Logik angewendet. Aufgrund des manipulierten Wertes des lending_akkumulator, der außergewöhnlich hoch ist (ca. 4.069.297.906.051.644.020), führt diese Division dazu, dass der raw_balance des Angreifers nur um 1 Einheit sinkt, obwohl mehr als 6 zwstETH verbrannt wurden.

Die Änderungen am raw_balance des Angreifers sind in der folgenden Tabelle zusammengefasst:

Wir können beobachten, dass der Angreifer in dieser Transaktion die Einzahlungs-Abhebungs-Logik wiederholt ausführt und den Präzisionsverlust während der withdraw()-Funktion ausnutzt, was zu einer Unterschätzung der Differenz des raw_balance führt. Letztendlich stieg der raw_balance des Benutzers von 2 auf 3, wobei eine zusätzliche Einheit gewonnen wurde.

0x2.2.2 Nachfolgender Angriffsprozess

Anschließende Angriffstransaktionen folgten dem gleichen Muster wie der erste Angriff: Der Angreifer durchläuft wiederholt Einzahlungs-Abhebungs-Transaktionen, um wstETH zu erwerben.

Das erworbene wstETH wird wieder in den Markt eingezahlt, wodurch der raw_balance weiter erhöht wird, was dazu führt, dass der Wert der Sicherheit des Angreifers weiter steigt.

Beispielerklärung

Wir verwenden die folgende Transaktion zur Veranschaulichung.

  • Es wurden insgesamt 30 Einzahlungen getätigt, wobei jedes Mal 4,069 wstETH eingezahlt wurden.
  • Es wurden insgesamt 30 Abhebungen getätigt, wobei jedes Mal 6,104 wstETH abgehoben wurden.
  • Nach diesem Zyklus hat der Angreifer laut Berechnungen erfolgreich 61,39 wstETH extrahiert.

Darüber hinaus ist anzumerken, dass zwischen diesen Angriffstransaktionen mehrere increase()-Methoden aufgerufen wurden. Diese Methoden wurden verwendet, um eine bestimmte Menge wstETH vom Konto des Angreifers an den Angreifervertrag zu übertragen, der dann die Mittel für nachfolgende Einzahlungen in den Market-Vertrag bereitstellte.

Diese Aktionen steigern den Wert des raw_balance und ermöglichen es dem Angreifer, den Wert der Sicherheit weiter zu erhöhen. Schließlich erreichte der raw_balance des Angreifers 1.724 mit einem Wert von 7.015,4 wstETH, was ausreichte, um andere Vermögenswerte aus dem Markt zu leihen.

0x3 Gewinnanalyse

0x3.1 Leihe anderer Arten von Geldern

Nachdem der Wert der Sicherheit manipuliert worden war, lieh der Angreifer andere Arten von Geldern aus dem Markt und fuhr mit den folgenden Transaktionen fort (Auszug):

0x3.2 Überbrückung der geliehenen Gelder nach Layer1

Durch die Inspektion der Bridge-Transaktionen des Angreifervertrags kann beobachtet werden, dass der Angreifer einen Teil der geliehenen Gelder nach Layer 1 überbrückte.

0x4 Schlussfolgerung

Zusammenfassend unterstreicht dieser Angriff auf das zkLend-Protokoll mehrere wichtige Implikationen für das Design und die Sicherheit dezentraler Kreditprotokolle:

  • Markteinführung und Bedingungen für die Einzahlung von Vermögenswerten: Der leere Markt zu Beginn erlaubte dem Angreifer, eine kleine Menge wstETH einzuzahlen und den lending_akkumulator zu manipulieren, was ihm einen Hebel für den Exploit verschaffte. Die Gewährleistung einer ausreichenden Liquiditätsbasis oder die Begrenzung von Spendengeldern in frühen Marktphasen könnte helfen, ähnliche Angriffe zu verhindern.
  • Bedeutung angemessener Akkumulator-Mechanismen: Der Angreifer nutzte den Spendenmechanismus in der flash_loan()-Funktion aus, um den lending_akkumulator zu manipulieren und die Sicherheitenwerte aller Benutzer aufzublähen. Protokolle mit Akkumulator-basierten Mechanismen sollten vor leichter Manipulation von Skalierungsfaktoren geschützt sein.
  • Rundungsverhalten und Präzisionsverlust: Ein Rundungsproblem während der Verbrennung von zwstETH-Tokens führte zu Präzisionsverlust und Unterschätzung des raw_balance, was dem Angreifer die Manipulation des raw_balance ermöglichte. Protokolle sollten eine höhere Präzision oder Validierungsprüfungen verwenden, um solche Exploits zu verhindern.

Dieses Ereignis unterstreicht einmal mehr die Bedeutung von rechtzeitigen Benachrichtigungen bezüglich der Initialisierung und des operativen Status sowie proaktiver Bedrohungsprävention zur Abmilderung potenzieller Verluste.

Referenz

[1] https://zklend.com/

[2] zkLends Sicherheitsvorfall Post-Mortem: https://drive.google.com/file/d/10i1dh_J89tPPw7KRcmFIVM6iNrJZAyfi/view

Sign up for the latest updates