Preservation
1.题目要求
1.1 该合约利用库合约保存 2 个不同时区的时间戳。合约的构造函数输入两个库合约地址用于保存不同时区的时间戳。
通关条件:尝试取得合约的所有权(
owner
)。可能有帮助的注意点:
- 深入了解
Solidity
官网文档中底层方法delegatecall
的工作原理,它如何在链上和库合约中的使用该方法,以及执行的上下文范围。 - 理解
delegatecall
的上下文保留的含义 - 理解合约中的变量是如何存储和访问的
- 理解不同类型之间的如何转换
- 深入了解
1.2 题目代码:
1 | // SPDX-License-Identifier: MIT |
2. 分析
参考文章1 参考文章2
第一类分析方法
2.1 目标:声明对给定实例的所有权
2.2 它有一个
uint256 storedTime
状态变量和一个 setter 函数setTime
,可以根据用户的输入更新状态变量。
1 | // Simple library contract to set the time |
2.3 分析主函数的代码
它有五个不同的状态变量
address public timeZone1Library
第一时区图书馆地址address public timeZone2Library
第二时区图书馆地址address public owner
所有者的地址uint256 storedTime
两个时区库之一存储的时间bytes4 constant setTimeSignature``setTime
时区库中函数的签名。constant
由于关键字,这不是真正的状态变量
合约的the
constructor
of the contract 采用两种address
类型的输入来设置两个库地址并将所有者设置为msg.sender
.然后我们有两个不同的功能
function setFirstTime(uint256 _timeStamp) public
function setSecondTime(uint256 _timeStamp) public
它们是相同的,它们只是在两个不同的时区库上执行相同的代码
当
Preservation
合约执行时setFirstTime(100)
,它会调用LibraryContract.setTime(100)
viadelegatecall
。执行的代码来自合约
LibraryContract
,但使用的上下文是执行操作码的上下文delegatecall
。当我们谈论上下文时,我们指的是存储、当前发送者(msg.sender
) 和当前值(msg.value
)。如果
**LibraryContract**
修改状态,它不会修改自己的状态,而是修改调用者(**Preservation**
)的状态!这意味着当LibraryContract.setTime
更新storedTime
状态变量时,不是从它自己的合约更新变量,而是更新调用者合约的slot0中的变量,即timeZone1Library
地址。setSecondTime
执行函数时会发生同样的事情,它将更新合约slot0Preservation
中的变量。我们如何利用这个漏洞?有没有办法修改
delegatecall
存储状态变量信息的第三个存储槽owner
?好吧,不是直接来自
setFirstTime
,否则会修改slot0setSecondTime
变量的值。但是,如果我们将slot0地址替换为我们已部署的合约的地址,这将模拟相同的布局存储并且确实会更新slot3变量怎么办?
第二类分析方法
保存合约使用地址中分配的合约
timeZone1Library
和timeZone2Library
库合约。因此,对这些合约的所有调用都是在保存合约中完成的delegatecall
,不会触及每个合约的存储LibraryContract
,而是触及保存合约的存储。当我们
storedTime
通过库合约中的函数修改变量时,我们不是在库或保存合约中修改delegatecall
,而是在保存合约中占用相应存储槽的变量。setTime()``storedTime
storedTime
因此,调用
setFirstTime()
orsetSecondTime()
将timeZone1Library
使用我们作为 传递的任何值进行修改_timeStamp
。因此,为了利用合约并成为owner
,我们需要部署一个与Preservation 具有相同存储布局的合约,这意味着我们的攻击者合约应该定义:1
2
3address public timeZone1Library;
address public timeZone2Library;
address public owner;与保存的顺序完全相同。
此外,攻击者合约中必须定义两个附加功能:
- 一个
setTime()
带有uint256
参数的函数,在攻击者合约的情况下,它将修改其第三个内存槽中的变量,因此owner
。这个变量的名称无关紧要,因为我们只对修改 Preservation 中的第 3 个内存槽感兴趣,但为了保持一致,我也将其命名为owner
。
1
2
3function setTime(uint256) public {
owner = tx.origin;
}- 在保护中调用的函数
setFirstTime()
,以使timeZone1Library
攻击者签订合同。如果每个 LibraryContract 都使用与 Preservation 相同的布局进行正确编码,则 Preservation 合约不会以这种方式受到攻击。
1
2
3function setFirstTimeExploit() external {
preservationContract.setFirstTime(uint256(address(this)));
}preservationContract
保存合同的接口在哪里。因此流程如下:
- 通过从攻击者合约调用来创建攻击者合约
timeZone1Library
地址setFirstTime()
。 - 使用
setFirstTime()
任何无符号整数作为参数调用保存合约,该合约setTime()
在攻击者合约中执行,生成owner
我们的原始地址。
- 一个
参考视频 写的攻击合约
1 | contract Hack { |
3. 解题
- 3.1 获取关卡实例地址:0x5E4d571ae5dAA2A4D530a1a5B89C11d6fEae8440
- 3.2 部署攻击合约,调用Hack 合约中的attack() 函数,形参是实例地址
- 3.3 提交案例
- 3.4 成功!!!!