今日运势评分

1

本月运势

丁丑月

震荡偏多

甲庚相冲,辰土劫财
己不破券二比并亡
卯不穿井水泉不香

开市,交易,立券,纳财,开池,补垣,嫁娶,纳采,纳畜,取渔,安床

修造,上梁,入宅,祈福,探病,掘井,动土,安门,安葬,作灶

月相

冲顶回落

日冲

Powered by RitMEX

PEPE0.00 3.78%

SUI5.20 9.23%

TON5.37 3.52%

TRX0.24 1.72%

DOGE0.33 4.05%

XRP2.34 2.23%

SOL188.01 1.16%

BNB693.38 0.88%

ETH3269.81 1.44%

BTC94785.13 2.44%

ETH Gas3.60 Gwei

中性
50

Solidity 编译器漏洞分析:ABI重编码的缺陷

该过程本身并没有大的逻辑问题,但是当和 Solidity 的 cleanup 机制结合时,由于 Solidity 编译器代码本身的疏漏,就导致了漏洞的存在。

总览

本文从源代码层面对 Solidity 编译器( 0.5.8<= version <0.8.16)在 ABIReencoding 过程中,由于对固定长度的 uint 和 bytes 32 类型数组的错误处理所导致的漏洞问题进行详细分析,并提出相关的解决方案及规避措施。

漏洞详情

ABI 编码格式是用在用户或合约对合约进行函数调用,传递参数时的标准编码方式。具体可以参考 Solidity 官方关于ABI 编码的详细表述。

在合约开发过程中,会从用户或其他合约传来的 calldata 数据中,获取需要的数据,之后可能会将获取的数据进行转发或 emit 等操作。限于 evm 虚拟机的所有 opcode 操作都是基于 memory、stack 和 storage,所以在 Solidity 中,涉及到需要对数据进行 ABI 编码的操作,都会将 calldata 中的数据根据新的顺序按照 ABI 格式进行编码,并存储到 memory 中。

该过程本身并没有大的逻辑问题,但是当和 Solidity 的cleanup 机制结合时,由于 Solidity 编译器代码本身的疏漏,就导致了漏洞的存在。

根据 ABI 编码规则,在去掉函数选择符之后,ABI 编码的数据分为 head 和 tail 两部分。当数据格式为固定长度的 uint 或 bytes 32 数组时,ABI 会将该类型的数据都存储在 head 部分。而 Solidity 对 memory 中 cleanup 机制的实现是在当前索引的内存被使用后,将下一个索引的内存置空,以防止下一索引的内存使用时被脏数据影响。并且,当 Solidity 对一组参数数据进行 ABI 编码时,是按照从左到右的顺序进行编码!!

为了便于后面的漏洞原理探索,考虑如下形式的合约代码:

contract Eocene {

event VerifyABI( bytes[], uint[ 2 ]);

function verifyABI(bytes[] calldata a, uint[ 2 ] calldata b) public {

emit VerifyABI(a, b); //Event 数据会按照 ABI 格式编码之后存储到链上

}

}

合约 Eocene 中 verifyABI 函数的作用,仅仅是将函数参数中的不定长 bytes[] a 和定长 uint[2 ] b 进行 emit。

这里需要注意,event 事件也会触发 ABI 编码。这里参数 a, b 会编码成 ABI 格式后再存储到链上。

我们使用 v 0.8.14 版本的 Solidity 对合约代码进行编译,通过 remix 进行部署,并传入verifyABI(['0x aaaaaa','0x bbbbbb'],[0x 11111, 0x 22222 ])

首先,我们看一看对verifyABI(['0x aaaaaa','0x bbbbbb'],[0x 11111, 0x 22222 ])的正确编码格式:

0x 5 2c d 1 a 9 c // bytes 4(sha 3("verify(btyes[], uint[ 2 ])"))

0000000000000000000000000000000000000000000000000000000000000060 // index of a

0000000000000000000000000000000000000000000000000000000000011111 // b[0 ]

0000000000000000000000000000000000000000000000000000000000022222 // b[1 ]

0000000000000000000000000000000000000000000000000000000000000002 // length of a

0000000000000000000000000000000000000000000000000000000000000040 // index of a[0 ]

0000000000000000000000000000000000000000000000000000000000000080 // index of a[1 ]

0000000000000000000000000000000000000000000000000000000000000003 // length of a[0 ]

aaaaaa 0000000000000000000000000000000000000000000000000000000000 // a[0 ]

0000000000000000000000000000000000000000000000000000000000000003 // length of a[1 ]

bbbbbb 0000000000000000000000000000000000000000000000000000000000 // a[1 ]

如果 Solidity 编译器正常,当参数a, b被 event 事件记录到链上时,数据格式应该和我们发送的一样。让我们实际调用合约试试看,并对链上的 log 进行查看,如果想自己对比,可以查看该TX

成功调用后,合约 event 事件记录如下:

!!震惊,紧跟 b[1 ]的,存储 a 参数长度的值被错误的删除了!!

0000000000000000000000000000000000000000000000000000000000000060 // index of a

0000000000000000000000000000000000000000000000000000000000011111 // b[0 ]

0000000000000000000000000000000000000000000000000000000000022222 // b[1 ]

0000000000000000000000000000000000000000000000000000000000000000 // length of a?? why become 0??

0000000000000000000000000000000000000000000000000000000000000040 // index of a[0 ]

0000000000000000000000000000000000000000000000000000000000000080 // index of a[1 ]

0000000000000000000000000000000000000000000000000000000000000003 // length of a[0 ]

aaaaaa 0000000000000000000000000000000000000000000000000000000000 // a[0 ]

0000000000000000000000000000000000000000000000000000000000000003 // length of a[1 ]

bbbbbb 0000000000000000000000000000000000000000000000000000000000 // a[1 ]

为什么会这样?

正如我们前面所说,在 Solidity 遇到需要进行 ABI 编码的系列参数时,参数的生成顺序是从左至,具体对 a, b 的编码逻辑如下

  1. Solidity 先对 a 进行 ABI 编码,按照编码规则,a 的索引放在头部,a 的元素长度以及元素具体值均存放在尾部。

  2. 处理 b 数据,因为 b 数据类型为 uint[2 ]格式,所以数据具体值被存放在 head 部分。但是,由于 Solidity 自身的 cleanup 机制,在内存中存放了 b[1 ]之后,将 b[1 ]数据所在的后一个内存地址(被用于存放 a 元素长度的内存地址)的值置 0 。

  3. ABI 编码操作结束,错误编码的数据存储到了链上,SOL-2022-6 漏洞出现。

在源代码层面,具体的错误逻辑也很明显,当需要从 calldata 获取定长 bytes 32 或 uint 数组数据到 memory 中时,Solidity 总是会在数据复制完毕后,将后一个内存索引数据置为 0 。又由于 ABI 编码存在 head 和 tail 两部分,且编码顺序也是从左至右,就导致了漏洞的存在。

具体漏洞的 Solidity 编译代码如下:

当源数据存储位置为 Calldata,且源数据类型为 ByteArray,String,或者源数组基础类型为 uint 或 bytes 32 时进入ABIFunctions::abiEncodingFunctionCalldataArrayWithoutCleanup()

进入之后,会首先通过fromArrayType.isDynamicallySized()对源数据是否为定长数组来对源数据进行判断,只有定长数组才符合漏洞触发条件。

isByteArrayOrString()判断结果传递给YulUtilFunctions::copyToMemoryFunction(), 根据判断结果来确定是否在 calldatacopy 操作完成后,对后一个索引位置进行 cleanup。

上诉几个约束条件结合,就只有位于 calldata 中的源数据格式为定长的 uint 或 bytes 32 的数组复制到内存时才能触发漏洞。也即是漏洞触发的约束条件产生的原因。

由于 ABI 进行参数编码时,总是从左到右的顺序,考虑到漏洞的利用条件,我们必须要明白,必须在定长的 uint 和 bytes 32 数组前,存在动态长度类型的数据被存储到 ABI 编码格式的 tail 部分,且定长的 uint 或 bytes 32 数组必须位于待编码参数的最后一个位置。

原因很明显,如果定长的数据没有位于最后一个待编码参数位置,那么对后一内存位置的置 0 不会有任何影响,因为下个编码参数会覆盖该位置。如果定长数据前面没有数据需要被存储到 tail 部分,那么即便后一内存位置被置 0 也没有关系,因为该位置并不背 ABI 编码使用。

另外,需要注意的是,所有的隐式或显示的 ABI 操作,以及符合格式的所有 Tuple(一组数据),都会受到该漏洞的影响。

具体的涉及到的操作如下:

  • event

  • error

  • abi.encode*

  • returns //the return of function

  • struct //the user defined struct

  • all external call

解决方案

  1. 当合约代码中存在上诉受影响的操作时,保证最后一个参数不为定长的 uint 或 bytes 32 数组

  2. 使用不受漏洞影响的 Solidity 编译器

  3. 寻求专业的安全人员的帮助,对合约进行专业的安全审计

欢迎加入深潮TechFlow官方社群

Telegram订阅群:https://t.me/TechFlowDaily
Twitter官方账号:https://x.com/TechFlowPost
Twitter英文账号:https://x.com/DeFlow_Intern
作者Eocene ResearchEocene Research
相关文章
2023.12.17 - 391 天前
探索 Solidity 1.0.0:智能合约关键里程碑
即将发布 0.9.0 和 1.0.0 升级,将推出几项备受期待的增强功能。
2022.12.15 - 758 天前
2023会是Move生态掘金元年么?
Move生态还处于一个早期阶段。
2025.01.10 - 11 小时前
Crypto Content Creator Campus (C4) 推出 KOL 孵化学校,培养新一代加密内容创作者
C4 KOL 孵化学校的诞生源于我们坚信,每一位创作者都有潜力在加密货币领域脱颖而出。
2025.01.10 - 12 小时前
MOMO.FUN:AI驱动的 Meme+ DeFi平台,引领去中心化交易新未来
MOMO.FUN:让每一次创意都更有价值,让每一次交易都更有趣!
2025.01.10 - 15 小时前
Berachain 主网在即,生态 AI 赛道与潜力项目全景指南
探讨Berachain生态中的17个AI项目以及11个值得关注的项目。
2025.01.10 - 19 小时前
Aiccelerate DAO:加速去中心化开源 AI 发展
ai16z 联创 Shaw、Virtuals 联创 Ethermage、Story Protocol 联创 Jason 站台,Aiccelerate DAO 究竟有何魔力?
AI
2025.01.10 - 19 小时前
链游勇闯 AI Agent 赛道,游戏角色 Freya 何以走红并入围 Binance Alpha?
在其它知名链游项目入局之前,类 Roguelike 奇幻风格RPG游戏 Starfall Chronicles已带着其游戏中的“头牌”Freya率先勇闯AI代理领域,并在日前入围Binance Alpha。
2025.01.10 - 21 小时前
Base 生态 AI 大掘金:Virtuals 之外,全面盘点那些你可能忽略的宝藏项目们
试图寻找更多 Base 上的 AI Agent 相关项目。
2025.01.08 - 3 天前
Solayer Chain 速读:毫秒交易时代到来?
1 毫秒交易确认、100 Gbps,Solayer Chain 究竟是什么?
2025.01.08 - 3 天前
AI 领衔主演,22 个加密项目集体下场布局 AI Agent
这些项目主要通过技术框架开发和应用场景落地等方式来加速AI Agent生态的构建与发展。