#10 Panoptisches Ereignis: XOR-Linearität bricht Positions-Fingerabdruck-Schema

#10 Panoptisches Ereignis: XOR-Linearität bricht Positions-Fingerabdruck-Schema

Am 25. August 2025 führte Panoptic mit Unterstützung von Cantina und Seal911 eine White-Hat-Rettungsaktion durch und sicherte etwa 400.000 US-Dollar an gefährdeten Geldern [1]. Die Grundursache war ein Fehler in der Konstruktion von s_positionsHash: Das Protokoll verwendete XOR, um Keccak256-Hashes von Positions-IDs zu einem einzigen Fingerabdruck zu aggregieren. Während einzelne Keccak256-Hashes kollisionsresistent bleiben, macht die mathematische Linearität von XOR den zusammengesetzten Fingerabdruck unsicher. Ein Angreifer kann eine Reihe gefälschter Positions-IDs generieren, deren XOR-aggregierter Hash mit jedem Ziel-Fingerabdruck übereinstimmt, wodurch die Positionsprüfung des Protokolls umgangen und Sicherheiten abgezogen werden, ohne Schulden zurückzuzahlen.

Hintergrund

Panoptic ist ein dezentralisiertes Protokoll für den Handel mit ewigen Optionen, das auf Ethereum aufgebaut ist und es Benutzern ermöglicht, Put- und Call-Optionen zu handeln.

Die Funktion withdraw() hat einen Eingabeparameter positionList, der aus einer Menge von tokenIds besteht. Jede tokenId repräsentiert eine Position. Die Funktion withdraw() prüft den Schuldenstatus jeder Position basierend auf s_positionsHash und ruft dann die Sicherheiten des Benutzers ab.

Um Gas zu sparen, speichert das Protokoll nicht jede Position (d. h. tokenId) des Benutzers. Stattdessen berechnet es einen Fingerabdruck basierend auf allen tokenIds des Benutzers und zeichnet ihn in s_positionsHash auf. Die 8 höchstwertigen Bits jedes s_positionsHash repräsentieren numLegs, während die unteren 248 Bits den user position hash repräsentieren.

Für jede übergebene tokenId berechnet das Protokoll deren Hash, nimmt die unteren 248 Bits und aktualisiert den user position hash, indem es einen bitweisen XOR mit den unteren 248 Bits des aktuellen s_positionsHash durchführt.

Gleichzeitig wird countLegs basierend auf dem numerischen Bereich der tokenId angepasst: Es bleibt unverändert, wenn tokenId kleiner als 2642^{64} ist, und erhöht sich um 1, 2 oder 3, wenn es in den Bereichen (264,2112)(2^{64}, 2^{112}), (2112,2168)(2^{112}, 2^{168}) bzw. (2168,2208)(2^{168}, 2^{208}) liegt. Dies wird schließlich in die obersten 8 Bits von s_positionsHash geschrieben, um numLegs zu aktualisieren.

Schwachstellenanalyse

Die Grundursache ist ein Fehler im Algorithmus des Vertrags zur Konstruktion von s_positionsHash, insbesondere die Verwendung der XOR-Operation zur Aggregation von Keccak256-Hash-Ergebnissen. Während eine einzelne Hash-Funktion sicher bleibt, macht die mathematische Linearität der XOR-Operation den gesamten Fingerabdruck-Algorithmus (d. h. die XOR-Summe von Hashes) unsicher [2].

Linearität impliziert, dass ein Angreifer die Hash-Funktion selbst nicht brechen muss (d. h. die tokenId aus dem Hash umkehren). Stattdessen können sie eine kombinatorische Strategie anwenden: Generieren einer großen Anzahl von zufälligen tokenIds, Berechnen ihres Keccak256(tokenId) und aus diesen Hash-Werten eine bestimmte Teilmenge auswählen, sodass die XOR-Summe dieser tokenId-Hashes exakt dem Ziel-Fingerabdruck des Opfers entspricht.

Dies ermöglicht es einem Angreifer, bei Aufruf von withdraw() eine Reihe gefälschter tokenIds zu übergeben, um den Gesundheitstest zu bestehen und alle Sicherheiten im Zusammenhang mit s_positionsHash abzurufen.

Theorie

Nehmen wir an, der Positionsfingerabdruck des Benutzers s_positionsHash hat einen user position hash von TT und numLegs von kk, mit Benutzer tokenIds {t1,t2,,tn}\{t_1, t_2, \dots, t_n\}. Somit gilt:

i=1k[Hash(ti)(mod2248)]=T\bigoplus_{i=1}^{k} [\text{Hash}(t_i) \pmod{2^{248}}] = T

Hier kann jeder 248-Bit-Hash-Wert als 248-dimensionaler Vektor betrachtet werden.

Hash(ti)(mod2248)=[b0,b1,,b247]T,wobei bi{0,1}\text{Hash}(t_i) \pmod{2^{248}} = [b_0, b_1, \dots, b_{247}]^T, wobei\ b_i \in \{0, 1\}

Daher liegt TT in einem 248-dimensionalen Raum aus 0en und 1en (F2248\mathbb{F}_2^{248}). In diesem Raum ist die XOR-Operation gleich der Vektoraddition (Anhang I).

Das Ziel des Angreifers ist es, nn 248-dimensionale Vektoren {v1,v2,,vn}\{v_1, v_2, \dots, v_n\} aus allen verfügbaren Vektoren zu finden, sodass die unteren 248 Bits ihrer XOR-Summe gleich TT sind. Somit können wir das Ziel des Angreifers als System linearer Gleichungen formulieren:

x1v1+x2v2++xnvn=Tx_1 v_1 + x_2 v_2 + \dots + x_n v_n = T

Insbesondere müssen wir TT nicht direkt konstruieren. Stattdessen wählen wir nn linear unabhängige Hash-Vektoren {v1,v2,,vn}\{v_1, v_2, \dots, v_n\} (wobei nn der Dimension 248 entspricht) und verwenden sie als Spaltenvektoren, um eine n×nn \times n Matrix AA zu konstruieren:

A=[v1,v2,,vn]A = [v_1, v_2, \dots, v_n]

Das Problem verwandelt sich dann in die Suche nach einem Koeffizientenvektor xx, so dass:

Ax=TA \cdot x = T

Wobei x=[x1,x2,,xn]Tx = [x_1, x_2, \dots, x_n]^T und xi{0,1}x_i \in \{0, 1\}.

Gemäß der linearen Algebra erstreckt sich die Matrix AA, solange sie vollen Rang hat, über den gesamten nn-dimensionalen Raum. Das bedeutet, dass für jedes Ziel TT das Gleichungssystem eine eindeutige Lösung hat. Nach der Lösung von xx behalten wir einfach diejenigen Vektoren viv_i bei, für die xi=1x_i=1; ihre XOR-Summe ist TT.

Beispiel

Zur besseren Verständlichkeit demonstrieren wir diesen Konstruktionsprozess anhand einer Fallstudie. Nehmen wir an, die Vektoren sind 3-dimensional, jede Dimension besteht nur aus 0 oder 1, und der Ziel-Hashwert ist 101, d. h. T=[1,0,1]TT = [1, 0, 1]^T.

Als Nächstes generieren wir zufällig 3 Hash-Werte:

  • v1=[1,1,0]Tv_1 = [1, 1, 0]^T
  • v2=[0,1,0]Tv_2 = [0, 1, 0]^T
  • v3=[0,1,1]Tv_3 = [0, 1, 1]^T

Wir konstruieren die Matrix AA mit diesen drei Vektoren als Spalten und stellen die Gleichung Ax=TAx=T auf:

[100111001][x1x2x3]=[101]\begin{bmatrix} 1 & 0 & 0 \\ 1 & 1 & 1 \\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x_1 \\ x_2 \\ x_3 \end{bmatrix} = \begin{bmatrix} 1 \\ 0 \\ 1 \end{bmatrix}

Durch Gaußsche Elimination erhalten wir x=[1,0,1]Tx = [1, 0, 1]^T. Das bedeutet, wir müssen v1v_1 und v3v_3 auswählen (entsprechend x1=1,x3=1x_1=1, x_3=1), und ihre XOR-Summe entspricht exakt TT.

Auswahl von n linear unabhängigen Vektoren

Wie bereits erwähnt, müssen wir zur Lösung von Ax=TAx=T eine Matrix AA mit vollem Rang konstruieren. Da wir es mit einem extrem großen Vektorraum (m=2248m = 2^{248}) zu tun haben, stellt sich die Kernfrage: Wie wählen wir schnell nn linear unabhängige Vektoren aus diesem riesigen Raum aus?

Betrachten wir die einfachste Methode: zufälliges Generieren von nn tokenIds und Auswählen ihrer Hash-Ergebnisse als Vektoren.

Über F2\mathbb{F}_2 ist die Wahrscheinlichkeit PP, dass nn zufällig ausgewählte nn-dimensionale Vektoren eine vollen Rangmatrix bilden:

P(n)=k=0n1(12k2n)P(n) = \prod_{k=0}^{n-1} \left(1 - \frac{2^k}{2^n}\right)

Wenn nn groß ist (in diesem Beispiel n=248n=248), konvergiert diese Wahrscheinlichkeit gegen eine Konstante (Anhang II):

limnP(n)0.28879\lim_{n \to \infty} P(n) \approx 0.28879

Das bedeutet, jeder zufällige Versuch hat eine Erfolgswahrscheinlichkeit von etwa 28,9 %. Im Durchschnitt benötigt ein Angreifer nur etwa 3,5 Versuche, um eine Menge linear unabhängiger Vektoren zu finden. Daher sind die Rechenkosten extrem niedrig, und der Angreifer kann schnell eine Matrix AA konstruieren, die die Bedingungen erfüllt.

Um countLegs in den obersten 8 Bits zu steuern, müssen wir nur den Bereich der zufällig generierten tokenId-Werte basierend auf dem Ziel countLegs anpassen.

Wenn wir zum Beispiel möchten, dass die numLegs im gefälschten Fingerabdruck 0 sind, stellen wir einfach sicher, dass die nn zufällig generierten tokenIds alle kleiner als 2642^{64} sind. Da die Bein-Inkremente für tokenIds in diesem Bereich 0 betragen, wird die endgültige akkumulierte numLegs unweigerlich 0 sein, unabhängig davon, welche Vektoren die Gaußsche Eliminationslösung xx auswählt.

Angriffsanalyse

Der White-Hat-Retter initiierte mehrere Rettungstransaktionen. Der Einfachheit halber basiert die folgende Diskussion auf nur einer dieser Transaktionen [3].

Die Kernlogik besteht aus den folgenden 5 Schritten:

  1. Leihen Sie 0,23e8 WBTC und 28e18 WETH über einen Flash-Loan von Aave.
  2. Zahlen Sie 0,23e8 WBTC und 28e18 WETH in den poWBTC-Vertrag und den 0x1f8d_poWETH-Vertrag ein.
  3. Rufen Sie mintOptions() des PanopticPool-Vertrags mit einer normalen positionIdList auf, um Gelder zu leihen und eine gehebelte Position zu eröffnen.
  4. Rufen Sie withdraw() mit den gefälschten tokenIds auf. Da diese gefälschten Positionen eine positionSize von 0 haben, gibt die Funktion tokenRequired als 0 zurück, was bedeutet, dass die erforderlichen Gesamtsicherheiten für alle Positionen fälschlicherweise als null berechnet werden. In der Zwischenzeit ist der von diesen tokenIds generierte s_positionsHash exakt derselbe wie der in Schritt 3 generierte, was es dem Retter ermöglicht, alle Sicherheiten abzurufen, ohne Schulden zurückzuzahlen.
  5. Zahlen Sie den Flash-Loan zurück und führen Sie die nächste Runde aus.

Zusammenfassung

Dieser Vorfall hebt zwei sich verstärkende Mängel hervor, die zusammen den unbefugten Abzug von Sicherheiten ermöglichten.

  • XOR ist keine sichere Aggregationsfunktion für Hashes. Keccak256 ist kollisionsresistent, aber die Linearität von XOR bedeutet, dass die XOR-Summe mehrerer Hashes diese Eigenschaft nicht erbt. Die Konstruktion einer Menge von Eingaben, deren Hashes zu jedem Zielwert XORen, reduziert sich auf die Lösung eines Systems linearer Gleichungen über F2\mathbb{F}_2, was rechnerisch trivial ist. Hash-Zusammensetzung erfordert Operationen, die die Kollisionsresistenz beibehalten, wie z. B. Verkettung gefolgt von erneuter Hashing.
  • Fehlende Validierung von Positions-IDs. Das Protokoll prüfte nicht, ob die übergebenen positionIds gültigen Optionspositionen entsprachen. Werte unter 2642^{64} haben ein countLegs von 0 und eine positionSize von 0, was bedeutet, dass die gefälschten Positionen keine Schulden verursachen. Dies ermöglichte es dem Angreifer, den Gesundheitstest mit null erforderlichen Sicherheiten zu bestehen und gleichzeitig den Ziel-Fingerabdruck abzugleichen.

Referenz

  1. https://x.com/Panoptic_xyz/status/1961187739866644524

  2. https://cseweb.ucsd.edu/~mihir/papers/inchash.pdf

  3. https://app.blocksec.com/explorer/tx/eth/0x67a45dfe5ff4b190058674d7c791bbdc48e889f319f937c24fa13a5f9093f088

Anhang

Die folgenden beiden Anhänge liefern eine mathematische Erklärung und einen Beweis für die Aussagen im Haupttext, nämlich dass die XOR-Operation der Vektoraddition entspricht (Anhang I) und die Wahrscheinlichkeit, dass eine zufällige Matrix vollen Rang hat (Anhang II).

Anhang I

Im endlichen Körper F2={0,1}\mathbb{F}_2 = \{0, 1\} ist die Addition als Addition modulo 2 definiert:

0+0=00+1=11+0=11+1=0(mod2)\begin{aligned} 0 + 0 &= 0 \\ 0 + 1 &= 1 \\ 1 + 0 &= 1 \\ 1 + 1 &= 0 \pmod 2 \end{aligned}

Die Beobachtung zeigt, dass dies identisch mit der logischen Operation Exclusive OR (XOR, Symbol \oplus) ist.

Für den nn-dimensionalen Vektorraum F2n\mathbb{F}_2^n (in diesem Fall n=248n=248) ist die Addition zweier Vektoren u=[u1,,un]Tu = [u_1, \dots, u_n]^T und v=[v1,,vn]Tv = [v_1, \dots, v_n]^T als komponentenweise Addition modulo 2 definiert:

u+v=[u1+v1(mod2)un+vn(mod2)]=[u1v1unvn]=uvu + v = \begin{bmatrix} u_1 + v_1 \pmod 2 \\ \vdots \\ u_n + v_n \pmod 2 \end{bmatrix} = \begin{bmatrix} u_1 \oplus v_1 \\ \vdots \\ u_n \oplus v_n \end{bmatrix} = u \oplus v

Daher ist im Vektorraum F2n\mathbb{F}_2^{n} die Vektoraddition äquivalent zur bitweisen XOR-Operation auf den Vektorkomponenten.

Anhang II

Damit die Matrix vollen Rang hat, müssen diese nn Vektoren linear unabhängig sein.

Der erste Vektor v1v_1 kann jeder Vektor in F2n\mathbb{F}_2^n außer dem Nullvektor sein, was 2n12^n - 1 Optionen bietet; der zweite Vektor v2v_2 darf nicht im von {v1}\{v_1\} aufgespannten Unterraum liegen, der 212^1 Vektoren enthält, was 2n22^n - 2 Optionen übrig lässt; diesem Logik folgend darf der kk-te Vektor vkv_k nicht im von den vorherigen k1k-1 Vektoren aufgespannten Unterraum liegen, was 2n2k12^n - 2^{k-1} mögliche Optionen ergibt.

Die Gesamtzahl solcher Matrizen (d. h. die Ordnung von GL(n,F2)GL(n, \mathbb{F}_2)) ist:

N=k=0n1(2n2k)=(2n1)(2n2)(2n4)(2n2n1)N = \prod_{k=0}^{n-1} (2^n - 2^k) = (2^n - 1)(2^n - 2)(2^n - 4)\cdots(2^n - 2^{n-1})

Und die Gesamtzahl aller möglichen n×nn \times n Matrizen beträgt (2n)n=2n2(2^n)^n = 2^{n^2}.

Daher ist die Wahrscheinlichkeit PP:

P=k=0n1(2n2k)2n2=k=0n1(2n2k2n)=k=0n1(12k2n)P = \frac{\prod_{k=0}^{n-1} (2^n - 2^k)}{2^{n^2}} = \prod_{k=0}^{n-1} \left( \frac{2^n - 2^k}{2^n} \right) = \prod_{k=0}^{n-1} (1 - \frac{2^k}{2^n})

Wenn wir j=nkj = n - k setzen, können wir diesen Ausdruck umschreiben als:

P=j=1n(112j)P = \prod_{j=1}^{n} \left(1 - \frac{1}{2^j}\right)

Wenn nn \to \infty, konvergiert dieses Produkt gegen eine Konstante:

P0.288788P \approx 0.288788

Das bedeutet, dass für große nn die Wahrscheinlichkeit, dass eine zufällige Matrix vollen Rang hat, etwa 28,9 % beträgt.


Über BlockSec

BlockSec ist ein Anbieter von Blockchain-Sicherheit und Krypto-Compliance aus einer Hand. Wir entwickeln Produkte und Dienstleistungen, die Kunden bei der Durchführung von Code-Audits (einschließlich Smart Contracts, Blockchain und Wallets), der Echtzeit-Abwehr von Angriffen, der Analyse von Vorfällen, der Rückverfolgung illegaler Gelder und der Erfüllung von AML/CFT-Verpflichtungen über den gesamten Lebenszyklus von Protokollen und Plattformen hinweg unterstützen.

BlockSec hat zahlreiche Blockchain-Sicherheitsarbeiten auf renommierten Konferenzen veröffentlicht, mehrere Zero-Day-Angriffe auf DeFi-Anwendungen gemeldet, mehrere Hacks blockiert, um mehr als 20 Millionen Dollar zu retten, und Kryptowährungen im Wert von mehreren Milliarden Dollar gesichert.

Sign up for the latest updates