而delegatecall函数有个有趣的特点:当使用 delegatecall 函数进行外部调用涉及到 storage 变量的修改时是根据插槽位置来修改的而不是变量名。 图片 漏洞分析 我们可以看到有两个合约,Lib 合约中只有一个 pwn 函数用来修改合约的 owner,在 HackMe 合约中存在 fallback 函数,fallback 函数的内容是使用 delegatecall 我们需要利用 HackMe.fallback() 触发 delegatecall 函数去调用 Lib.pwn() 将 HackMe 合约中的 owner 改成自己。 在使用 delegatecall 时应注意被调用合约的地址不能是可控的; 2. 在较为复杂的合约环境下需要注意变量的声明顺序以及存储位置。 因为使用 delegatecall 进行外部调时会根据被调用合约的数据结构来用修改本合约相应 slot 中存储的数据,在数据结构发生变化时这可能会造成非预期的变量覆盖。
delegatecall delegatecall与call类似,也可以调用合约的任何函数。然而,delegatecall不会创建一个新的执行环境,而是在调用合约的上下文中执行函数。 而delegatecall函数则可以改变调用合约的状态,因为它在调用合约的上下文中执行。3.用途:call函数通常用于调用其他合约的函数,而delegatecall函数则常用于实现合约的升级和库函数。 使用delegatecall的情况 delegatecall函数通常用于实现合约的升级和库函数。 不适用场景 在Solidity中,call和delegatecall都是低级函数,虽然它们在某些情况下非常有用,但在以下情况下应该避免直接使用它们: 1.安全性问题:call和delegatecall都可能导致重入攻击 2.异常处理:call和delegatecall不会自动处理被调用函数抛出的异常。如果被调用的函数抛出异常,那么整个交易都会被回滚,除非你在调用call或delegatecall时进行了检查。
如果是,说明你需要了解一下delegatecall了。 在智能合约的开发中,使用 delegatecall 是一种强大的技术手段,它允许一个合约在执行时调用另一个合约的代码。 本文将深入介绍 delegatecall 的概念,以及如何利用它来扩充合约功能。 二、什么是 delegatecall? 升级合约逻辑 通过 delegatecall,我们可以将新版本的合约逻辑部署为一个独立的合约,并通过调用旧合约的 delegatecall 函数来执行新逻辑。 功能模块化 使用 delegatecall 可以将合约的功能拆分为多个独立的模块,每个模块部署为一个独立的合约。主合约通过 delegatecall 调用这些模块,从而实现功能的动态组合和替换。 六、总结 delegatecall 是一项强大的技术,为智能合约提供了灵活性和可升级性。通过合理利用 delegatecall,开发者可以在不破坏原有合约结构的基础上,实现功能的动态扩展和升级。
本次,我们来讲一讲运用delegatecall函数时更复杂的合约漏洞案例。 HackMe.doSomething() 函数使用 delegatecall 函数带着传入的 Attack 合约的地址调用了 Lib.doSomething() 函数;3. 可以看到 Lib.doSomething() 函数将合约中存储位置为 slot0 的参数改为传入的值,这样当 HackMe 合约使用 delegatecall 调用 Lib.doSomething() 所以 HackMe 合约使用 delegatecall 调用 Attack.doSomething() 函数就会将合约中存储位置为 slot1 的变量 owner 修改为 msg.sender 也就是攻击者的地址 修复建议我们在合约的开发中使用delegatecall要时刻注意其被调用的合约地址要始终在我们设计的逻辑内运行,不能让其有可能超出我们设计时的适用范围,一旦出现了超出我们预期设计的情况,那么合约就有可能被不法之徒利用
Solidity[4]有三种合约间的调用方式 call、delegatecall 和 callcode。其中,delegatecall可作为智能合约升级的一个较好的途径。 关于合约间的调用方式 call、delegatecall 和 callcode,详见我另一篇文章<<图文并茂详细介绍Solidity的三种合约间的调用方式 call、delegatecall 和 callcode
二、漏洞原理 delegatecall的含义:
.delegatecall(...) returns (bool):issue low-level DELEGATECALL, returns call与delegatecall的功能类似,区别仅在于后者仅使用给定地址的代码,其它信息则使用当前合约(如存储,余额等等)。注意delegatecall是危险函数,他可以完全操作当前合约的状态。 黑客通过 delegatecall 调用 initWallet 函数, initWallet 没有检查以防止攻击者在合同初始化后调用到 initMultiowned ,漏洞使得黑客能通过 library required, uint _daylimit) { initDaylimit(_daylimit); initMultiowned(_owners, _required); } 2、 代码里使用了 delegatecall msg.value > 0) Deposit(msg.sender, msg.value); else if (msg.data.length > 0) _walletLibrary.delegatecall原文:https://betterprogramming.pub/preventing-smart-contract-attacks-on-ethereum-delegatecall-e864d0042188 与call不同,用DELEGATECALL进行函数调用时,其代码是在当前调用函数的环境里执行,因此,构建无漏洞自定义库并不像想象的那么简单。 current withdrawal user- // this sets calculatedFibNumber require(fibonacciLibrary.delegatecall users to call Fibonacci library functions function() public { require(fibonacciLibrary.delegatecall 请注意,在第 21 行,withdraw函数已经执行了fibonacciLibrary.delegatecall(fibSig,withdrawalCounter)。
二、call、callcode、delegatecall函数说明 call、callcode、delegatecall是以太坊智能合约编写语言Solidity提供的底层函数,用来与外部合约或者库进行交互 1、绕过权限检查,调用敏感函数,例如setOwer; 2、窃取合约地址持有的代币; 3、伪装成合约地址与其他合约进行交互; 三、安全建议 为了避免此类漏洞,成都链安科技建议: 1、谨慎使用call、delegatecall 成都链安科技自主研发的VaaS平台对call、delegatecall函数存在的安全问题可一键式自动检查并精确定位,VaaS(Verification as a Service)形式化验证平台,是一套针对多个区块链平台
最后,根据 delegatecall 的结果,决定是回滚交易并返回错误数据,还是返回成功的数据。 let result := delegatecall(gas(), implementation, ptr, calldatasize(), 0, 0)delegatecall 是一个 EVM 操作码, returndatasize() 返回上一个调用(即 delegatecall)返回的数据大小。这行指令的作用是将 delegatecall 的返回数据复制到内存中,从 ptr 开始存储。 result 是 delegatecall 的返回值,如果调用成功则为 1,失败则为 0。 使用 delegatecall 将调用转发到逻辑合约,并在当前合约的上下文中执行其代码。根据 delegatecall 的结果,决定是回滚交易并返回错误数据,还是返回成功的数据。
基于delegatecall的代理,逻辑和数据也被保存在不同的合约中,但数据合约(代理)通过 delegatecall调用逻辑合约。 数据分离模式的优点是简单。 它不需要像 delegatecall模式那样的底层的专业知识。不过,最近delegatecall模式受到了广泛的关注。开发人员可能倾向于选择这种解决方案,因为文档和例子更容易找到。 这个解决方案需要掌握delegatecall,以允许一个合约使用另一个合约的代码。 让我们回顾一下delegatecall是如何工作的。 delegatecall背景知识 delegatecall允许一个合约执行另一个合约的代码,同时保持调用者的上下文(包括存储)不会变。 让我们回顾一下基于 delegatecall的不同数据存储策略。
string resMes){testMem--;resMes = "try to modify testMem and has constant label";}}内部调用: call,callcode,delegatecall 除了直接调用函数,还可以通过call,callcode,delegatecall的方式调用其他合约的函数,区别如下:CALL:是在 被调用者 的上下文中执行,只能修改被调用者的storage;CALLCODE 和DELEGATECALL: 是在 调用者 的上下文中执行, 可以修改调用者的storage;CALLCODE 阻止msg.sender和msg.value传递; 而DELEGATECALL不阻止;在A 的函数中,B.callcode(c的函数): c看到msg.sender是B;在A的函数中,B.delegatecall(c的函数): c看到msg.sender是A;合约示例如下:contract D uint256)")), _n); } function delegatecallSetN(address _e, uint _n) { // D的storage被修改, E未修改 _e.delegatecall
delegatecall: 调用后内置变量 msg 的值不会修改为调用者,但执行环境为调用者的运行环境。 0x02 delegatecall 「滥用」问题 delegatecall: 调用后内置变量 msg 的值不会修改为调用者,但执行环境为调用者的运行环境。 利用模型 下面的例子中 B 合约是业务逻辑合约,其中存在一个任意地址的 delegatecall 调用。 除了上述 delegatecall 滥用的案例,在分析研究的过程中,发现有部分蜜罐合约利用 delegatecall的特性(拷贝目标到自己的运行空间中执行),在代码中暗藏后门,暗中修改转账地址,导致用户丢失货币 由于 callcode 同时包含了 call 和 delegatecall 的特性,通过上文对 call 和 delegatecall 的安全问题进行了分析和举例,可以得出的结论是 call 和 delegatecall
是否能改状态特点与用途 call ✅ 切换到被调用合约 ✅ 最通用的外部调用,可带 ETH,可调用任意函数delegatecall delegatecall:不切场景、能改状态。staticcall:切场景、不能改状态。2. delegatecall 与 call 的主要区别是:delegatecall 不会更改 msg.sender 和 msg.value。存储上下文(storage slot)不切换,直接写到当前合约。 ( abi.encodeWithSignature("setValue(uint256)", _v) ); require(success, "delegatecall delegatecall:让别人用你的钱包执行代码。staticcall:借别人的计算器算一算,不动任何钱。
在 Solidity 中提供了 call、delegatecall、callcode 三个函数来实现合约之间相互调用及交互。 delegatecall: 调用后内置变量 msg 的值不会修改为调用者,但执行环境为调用者的运行环境。 「滥用」问题 delegatecall: 调用后内置变量 msg 的值不会修改为调用者,但执行环境为调用者的运行环境。 利用模型 下面的例子中 B 合约是业务逻辑合约,其中存在一个任意地址的 delegatecall 调用。 由于 callcode 同时包含了 call 和 delegatecall 的特性,通过上文对 call 和 delegatecall 的安全问题进行了分析和举例,可以得出的结论是 call 和 delegatecall
关键点:Parity 多签钱包的逻辑代码存放在一个 库合约(WalletLibrary) 中,所有钱包合约通过 delegatecall 调用它。库合约本身没有正确初始化 owner。 (data); // 调用库合约函数 }}delegatecall 会在 调用者的存储上下文 中执行库合约代码。 delegatecall 必须小心使用,库合约最好是无状态(Stateless)。selfdestruct 是危险函数,应该避免在核心合约中出现。智能合约一旦部署,升级和错误修复极其困难。6. 这里的关键:即使库代码被清空,delegatecall 对不存在的地址 不会立即 revert,而是返回空数据,且在 Solidity >=0.8.0 时默认 success = true。 在旧 EVM 下,delegatecall 到不存在地址会直接 revert;在现代 EVM 下,delegatecall 为空代码仍然返回成功,但 res 为空。
关键技术:delegatecall,在代理合约中使用 delegatecall 调用逻辑合约的函数,使得代码在代理的存储上下文中执行。 3、代理模式的工作原理1. delegatecall 复习(bool success, bytes memory data) = implementation.delegatecall(msg.data );delegatecall 会在当前合约的存储和上下文中执行目标合约的代码。 风险调用外部不可信合约可能破坏存储 严格控制升级权限,禁止不可信代码执行 delegatecall 权限丢失 升级过程中可能被替换成恶意逻辑 在 代理合约模式下,逻辑合约通过 delegatecall 操作的是 代理合约的 storage slot,所以逻辑合约和代理合约的变量 slot 不能冲突,否则会覆盖状态。
05 合约调用合约 合约内部调用另外一个合约,有4种调用方式: CALL CALLCODE DELEGATECALL STATICALL 后面会专门写篇文章比较它们的异同,这里先以最简单的CALL为例, 合约调用合约有下面4种方式: CALL CALLCODE DELEGATECALL STATICCALL 8.1 CALL vs. DELEGATECALL 实际上,可以认为DELEGATECALL是CALLCODE的一个bugfix版本,官方已经不建议使用CALLCODE了。 CALLCODE和DELEGATECALL的区别在于:msg.sender不同。 具体来说,DELEGATECALL会一直使用原始调用者的地址,而CALLCODE不会。 ? 我们再调用一下inc_delegatecall(),观察一下log输出: ? 可以发现,msg.sender指向的是交易的发起者。
call(), callcode() 和 delegatecall() 函数 为了和非ABI协议的合约进行交互,可以使用call() 函数, 它用来向另一个合约发送原始数据,支持任何类型任意数量的参数, (这句我怕翻译的不准确,引用原文) 同样我们也可以使用delegatecall(),它与call方法的区别在于,仅仅是代码会执行,而其它方面,如(存储,余额等)都是用的当前的合约的数据。 delegatecall()方法的目的是用来执行另一个合约中的库代码。所以开发者需要保证两个合约中的存储变量能兼容,来保证delegatecall()能顺利执行。 上面的这三个方法call(),delegatecall(),callcode()都是底层的消息传递调用,最好仅在万不得已才进行使用,因为他们破坏了Solidity的类型安全。 .gas() 在call(), callcode() 和 delegatecall() 函数下都可以使用, delegatecall()不支持.value() 注解:所有合约都继承了address的成员
可注入call漏洞和delegatecall误用漏洞会导致什么风险? 何为合约外部调用 专业的来说,call与delegatecall 函数让 Ethereum开发者将他们的代码模块化(Modularise)。 误用漏洞 漏洞分析 DELEGATECALL会保持调用环境不变的属性表明,构建无漏洞的定制库并不像人们想象的那么容易。 案例 案例代码来源于Ethernaut第6关[5] 在主合约Delegation的fallback函数中,可通过delegatecall调用Delegate合约的函数,并在主合约环境下执行,如果msg.data 在使用时 DELEGATECALL 时要特别注意库合约和调用合约可能对状态变量进行修改,并且尽可能构建无状态library 。
但是其又引入了一个新的问题,即:回滚操作中事实上模拟了一遍新的实现合约地址中的upgradeTo操作,并且是通过delegatecall方式来进行调用。 通过delegatecall调用新合约地址的upgradeTo方法有什么问题呢? 查看黄皮书中关于delegatecall的定义为: Message-call into this account with an alternative accounts' code, but persisting POC 这里不能直接在malicious合约中的upgradeTo方法中写selfdestruct,而是应该利用ForceCall部分的delegatecall,并通过写入rollbackTesting.value 然而在remix中执行时,发现delegatecall之后的require语句还是执行了: 这是不对的,需要进一步理解黄皮书中关于selfdestruct这个opcode的定义: selfdestruct