Black Hat USA 2025 | Breaking Control Flow Integrity by Abusing Modern C++

“Coroutine Frame-Oriented Programming: Breaking Control Flow Integrity by Abusing Modern C++” by Marcos Bajo:

Overview
The presentation introduces a novel exploitation technique called Coroutine Frame-Oriented Programming (CFOP). It demonstrates how attackers can leverage C++20 coroutines to completely bypass modern Control Flow Integrity (CFI) defenses (such as Intel CET and Microsoft CFG) that are designed to prevent code-reuse attacks like ROP (Return-Oriented Programming).

Key Concepts & Background

  • Control Flow Integrity (CFI): A defense mechanism that prevents attackers from redirecting a program’s execution flow by enforcing valid transition paths for indirect jumps and calls.
  • C++20 Coroutines: Coroutines are functions that can pause (suspend) and resume execution (using keywords like co_await, co_yield, and co_return). Because they must remember their state between suspensions, C++ compilers implement them as stackless coroutines, storing all local variables, parameters, and special control pointers (the resume and destroy pointers) in a heap-allocated Coroutine Frame.

The Vulnerability
The core weakness lies in how compilers currently generate code for coroutines:

  1. Writable Metadata: The Coroutine Frame is allocated on the heap, meaning all of its vital data—including function pointers and local variables—resides in writable memory.
  2. Lack of CFI Instrumentation: Modern CFI schemes often fail to instrument the resume and destroy pointers generated by the compiler inside the coroutine frame. As a result, indirect jumps using these pointers are completely unchecked by defenses like Intel CET or CFG.

Attack Techniques (CFOP)
Assuming an attacker has bypassed ASLR and has a memory corruption vulnerability (like an arbitrary memory write or a heap-based overflow), they can launch two main types of attacks:

  • Data-Only Attacks (DOAs): Because coroutine local variables are moved from the stack to the heap-allocated frame, they are stripped of typical stack protections (like stack canaries). An attacker can overwrite local variables, parameters, or internal state indices to manipulate program logic or achieve arbitrary memory frees/reads without ever hijacking the control flow.
  • Control Flow Hijacking (Infinite Coroutine Chaining – ICC): By injecting fake coroutine frames into memory or overwriting existing ones, an attacker can hijack the uninstrumented resume or destroy pointers.
    • Chaining: Attackers can abuse “continuation points” (how one coroutine knows to resume another after an await finishes) to chain an infinite sequence of arbitrary function calls.
    • Argument Passing: Because the Coroutine Frame pointer is naturally passed via the rdi register (which is also used as the this pointer in C++ class methods), attackers can easily pass arbitrary arguments to their hijacked functions by creating memory “collisions” with fake objects.

Demonstration & Defenses
The speaker successfully demonstrated the CFOP attack against the SerenityOS browser, chaining uninstrumented coroutine calls to gain remote code execution despite CFI protections being active.

Proposed Mitigations:
To fix this architectural flaw, compilers must be updated. The presenter suggests:

  • Moving resume and destroy function pointers into read-only memory and accessing them via a read-only jump table using a coroutine identifier.
  • Relying on HALO (Heap Allocation Elision Optimization), an existing but highly fragile compiler optimization that moves coroutine frames from the heap back to the stack. While HALO inadvertently breaks this attack, it currently requires strict conditions (like LTO enabled) to trigger and is poorly supported across major compilers (GCC, Clang, MSVC).

Leave a Reply

Your email address will not be published. Required fields are marked *