Fibers: User-Mode Cooperative Threads
Objective: Understand the internals of Windows fibers — how they relate to the TEB, the undocumented
FIBERstructure, Fiber Local Storage, and the cooperative context switch performed entirely in user mode — so defenders can recognize and detect adversarial use of fiber APIs for stealthy in-process execution.
Contents
- 1 1. Cooperative vs. Preemptive Scheduling
- 2 2. The Fiber Execution Model
- 3 3. TEB Layout and the FIBER Structure
- 4 4. The Core Fiber API
- 5 5. Fiber Lifecycle: A Minimal Example
- 6 6. Context Switching Internals
- 7 7. Fiber Local Storage (FLS)
- 8 8. Building a Round-Robin Cooperative Scheduler
- 9 9. Legitimate Use Cases and Pitfalls
- 10 10. Common Attacker Techniques
- 11 11. Defensive Strategies & Detection
- 12 12. Tools for Fiber Analysis
- 13 13. MITRE ATT&CK Mapping
- 14 Summary
- 15 Related Tutorials
- 16 References
1. Cooperative vs. Preemptive Scheduling
A thread is the Windows kernel’s unit of execution. The scheduler picks ready threads, slices CPU time, and preempts them at quantum boundaries — all driven from ntoskrnl.exe. A fiber is different: it is a unit of execution that the kernel does not know about. Fibers run inside threads, and the application — not the OS — chooses when one fiber yields and another runs.
Two consequences follow immediately:
- A fiber switch never crosses the user/kernel boundary. No syscall is issued.
SwitchToFiberlives inKernelBase.dlland returns without touchingntoskrnl. - From the kernel’s perspective, all activity performed by a fiber is attributed to the thread that runs it. Accessing TLS from a fiber accesses the thread’s TLS, not a per-fiber slot.
This is the root of both the elegance and the security relevance of fibers: they are coroutines built directly into the Win32 ABI, with stack pivots and register saves the kernel cannot see.
2. The Fiber Execution Model
A fiber consists of three things: a stack, a saved CPU context (registers, instruction pointer, SEH frame), and a start routine that receives an opaque parameter. A thread becomes “fiber-aware” by calling ConvertThreadToFiber, at which point that thread is permanently a fiber host until it calls ConvertFiberToThread.
| Rule | Behavior |
|---|---|
| Must convert first | You cannot call SwitchToFiber from a thread until ConvertThreadToFiber runs. |
| Fiber function returning | If a fiber’s start routine returns, the host thread calls ExitThread and terminates. |
| Self-delete | If the currently running fiber calls DeleteFiber on itself, the host thread exits. |
| Cross-thread delete | Deleting a fiber that is the selected fiber of another thread will likely crash that thread — its stack just disappeared. |
| Cross-thread switch | SwitchToFiber accepts a fiber created by a different thread; the caller becomes the new host. |
These rules are load-bearing — most fiber bugs (and several known abuse primitives) come from violating them.
3. TEB Layout and the FIBER Structure
The Thread Environment Block (TEB) tracks the per-thread fiber state. Three fields matter:
| Field | Type | Role |
|---|---|---|
NtTib.FiberData | PVOID | Pointer to the current fiber’s FIBER structure |
HasFiberData | USHORT : 1 | Bitfield set by ConvertThreadToFiberEx; indicates the thread hosts fibers |
FlsData | PVOID | Pointer to the FLS slot array for the current fiber |
ConvertThreadToFiberEx calls NtCurrentTeb(), checks Teb->HasFiberData, and if the thread is already a fiber returns with ERROR_ALREADY_FIBER. Otherwise it allocates a FIBER structure on the process heap via RtlAllocateHeap and stores its address in NtTib.FiberData.
The FIBER struct itself is not officially documented. The shape below is reconstructed from ReactOS sources and public symbols and is subject to change across Windows versions:
// Reconstructed from public symbols / ReactOS — illustrative only.
typedef struct _FIBER {
PVOID FiberData; // lpParameter passed at creation
PVOID ExceptionList; // Top of SEH chain (NT_TIB.ExceptionList)
PVOID StackBase; // High end of the fiber stack
PVOID StackLimit; // Low end (guard page)
PVOID DeallocationStack; // Original VirtualAlloc base
CONTEXT FiberContext; // Saved CPU state: RIP, RSP, RBP, RBX, ...
ULONG FiberFlags; // FIBER_FLAG_FLOAT_SWITCH, etc.
PVOID ActivationContext; // Per-fiber activation context stack
PVOID FlsSlots; // Per-fiber FLS slot array
} FIBER, *PFIBER;You must never read or write this structure directly. The Win32 fiber functions manage its contents; treating the returned LPVOID as opaque is part of the contract.
4. The Core Fiber API
The full surface is small. Most of winbase.h and fibersapi.h boils down to these functions:
| Function | Purpose |
|---|---|
ConvertThreadToFiber | Promote the calling thread into a fiber; required first |
ConvertThreadToFiberEx | As above; accepts FIBER_FLAG_FLOAT_SWITCH |
CreateFiber | Allocate stack + FIBER struct; record entry point and parameter |
CreateFiberEx | As above; accepts dwStackCommitSize and flags |
SwitchToFiber | Cooperative context switch to the supplied fiber |
DeleteFiber | Free the fiber’s stack, context, and FIBER data |
ConvertFiberToThread | Demote back to a plain thread; required to avoid leaks |
GetCurrentFiber | Returns the current FIBER address (intrinsic — no CALL) |
GetFiberData | Returns the lpParameter value (intrinsic — no CALL) |
The exact CreateFiber signature, per MSDN:
LPVOID CreateFiber(
SIZE_T dwStackSize, // 0 = default, grows up to 1 MB
LPFIBER_START_ROUTINE lpStartAddress, // void StartRoutine(LPVOID lpParameter)
LPVOID lpParameter // passed to the fiber function
);GetCurrentFiber and GetFiberData are compiler intrinsics on MSVC — they inline directly to a gs:[0x20]/fs:[0x10] read of NtTib.FiberData. They produce no import thunk and no CALL instruction, which has direct consequences for IAT-based detection.
5. Fiber Lifecycle: A Minimal Example
This walks the canonical create → switch → yield → delete sequence. Note how g_mainFiber is the fiber identity of the original thread, returned by ConvertThreadToFiber.
#include <windows.h>
#include <stdio.h>
LPVOID g_mainFiber = NULL;
LPVOID g_workFiber = NULL;
VOID CALLBACK WorkerFiberProc(LPVOID lpParam) {
printf("[worker] running on fiber %p, param=%p\n",
GetCurrentFiber(), lpParam);
// Cooperative yield — control returns to the main fiber.
SwitchToFiber(g_mainFiber);
printf("[worker] resumed; returning will ExitThread()\n");
SwitchToFiber(g_mainFiber); // never let the routine return
}
int main(void) {
// Promote thread; TEB->HasFiberData becomes 1.
g_mainFiber = ConvertThreadToFiber(NULL);
// 64 KiB stack; entry = WorkerFiberProc; param = 0xDEADBEEF.
g_workFiber = CreateFiber(0x10000, WorkerFiberProc, (LPVOID)0xDEADBEEF);
SwitchToFiber(g_workFiber); // first run of worker
printf("[main] back from worker\n");
SwitchToFiber(g_workFiber); // resume worker
DeleteFiber(g_workFiber); // safe: not the running fiber
ConvertFiberToThread(); // demote; release fiber bookkeeping
return 0;
}Forgetting ConvertFiberToThread leaks the main fiber’s FIBER allocation on the process heap. Forgetting to yield back before the worker returns terminates the host thread via ExitThread.
6. Context Switching Internals
SwitchToFiber is the heart of the API. Conceptually, it performs:
- Save the current CPU state (
RBX,RBP,RDI,RSI,R12–R15,RSP,RIPon x64) into the current fiber’sFiberContext. - Save the SEH chain head (
NtTib.ExceptionList) and stack bounds (StackBase,StackLimit) into the currentFIBER. - If
FIBER_FLAG_FLOAT_SWITCHis set, save theXMM/MMX/x87state. - Update
NtTib.FiberDatato point at the targetFIBER. - Restore the target fiber’s stack bounds, SEH chain, FLS pointer, and CPU registers.
- Return to the saved instruction pointer of the target — execution resumes there on the target’s stack.
Critically, this is a pure user-mode operation. No syscall, no int 2e, no ETW event from Microsoft-Windows-Kernel-Process. The host thread’s kernel-visible state (KTHREAD, ETHREAD) is unchanged; only RIP/RSP move from the kernel’s view.
; Conceptual sketch — SwitchToFiber x64 prologue
mov gs:[0x20], rcx ; NtTib.FiberData = target
mov [rax + FiberContextOff + Rsp], rsp
mov [rax + FiberContextOff + Rip], <return addr>
; ... restore target ...
mov rsp, [rcx + FiberContextOff + Rsp]
jmp qword [rcx + FiberContextOff + Rip]
7. Fiber Local Storage (FLS)
TLS is per-thread. During a fiber switch the TEB’s TLS array is not swapped, so two fibers sharing a thread share TLS — a classic source of corruption when porting thread-based libraries to fibers. FLS solves this: it is per-fiber, and SwitchToFiber updates TEB->FlsData to the incoming fiber’s slot array.
| Function | Purpose |
|---|---|
FlsAlloc(PFLS_CALLBACK_FUNCTION) | Allocate an FLS index; optional destructor callback |
FlsSetValue(DWORD, PVOID) | Store a per-fiber value at the given index |
FlsGetValue(DWORD) | Read the current fiber’s value at the given index |
FlsFree(DWORD) | Release the index; callbacks fire for live fibers |
The destructor callback pointers are kept process-wide in PEB->FlsCallback. They fire on fiber deletion and thread exit, and — as covered below — they are a known abuse target.
DWORD g_flsIndex;
VOID WINAPI OnFlsDestroy(PVOID p) {
HeapFree(GetProcessHeap(), 0, p);
}
VOID CALLBACK FiberA(LPVOID _) {
char *buf = (char*)HeapAlloc(GetProcessHeap(), 0, 32);
lstrcpyA(buf, "fiber-A-private");
FlsSetValue(g_flsIndex, buf);
SwitchToFiber(g_mainFiber);
printf("[A] still mine: %s\n", (char*)FlsGetValue(g_flsIndex));
SwitchToFiber(g_mainFiber);
}
int wmain(void) {
g_mainFiber = ConvertThreadToFiber(NULL);
g_flsIndex = FlsAlloc(OnFlsDestroy);
// ... create FiberA, FiberB, switch between them ...
// Each fiber sees its own FlsGetValue(g_flsIndex) result.
}
8. Building a Round-Robin Cooperative Scheduler
Fibers shine when modeling cooperative pipelines: parsers, generators, state machines. A trivial scheduler is a dispatcher fiber that round-robins through worker fibers, each of which yields back via SwitchToFiber(g_mainFiber).
#define N 3
LPVOID g_workers[N];
LPVOID g_mainFiber;
VOID CALLBACK Worker(LPVOID id) {
for (int i = 0; i < 4; ++i) {
printf("[worker %llu] step %d\n", (ULONG_PTR)id, i);
SwitchToFiber(g_mainFiber); // yield
}
// Final yield — never return from a fiber routine.
SwitchToFiber(g_mainFiber);
}
int main(void) {
g_mainFiber = ConvertThreadToFiber(NULL);
for (ULONG_PTR i = 0; i < N; ++i)
g_workers[i] = CreateFiber(0, Worker, (LPVOID)i);
for (int round = 0; round < 4; ++round)
for (int i = 0; i < N; ++i)
SwitchToFiber(g_workers[i]);
for (int i = 0; i < N; ++i) DeleteFiber(g_workers[i]);
ConvertFiberToThread();
return 0;
}This is the same pattern Microsoft SQL Server used for its historical “lightweight pooling” / fiber mode — one OS thread, many SQL user contexts.
9. Legitimate Use Cases and Pitfalls
| Use Case | Reason |
|---|---|
| Coroutines / generators | Native stack switching with no setjmp tricks |
| Porting cooperative legacy code | UNIX swapcontext-style schedulers map cleanly |
| Database engines | SQL Server fiber mode for high-concurrency workloads |
| Game engines / scripting hosts | Per-script execution context with explicit yield |
Pitfalls are sharp:
- COM is apartment-affinitive to threads, not fibers. Initializing COM on one fiber and using it from another corrupts COM bookkeeping.
- CRT and many MS libraries stash state in TLS. Switching fibers leaves that state behind, producing subtle corruption.
- Critical sections record the thread as the owner — a different fiber on the same thread re-enters without blocking.
- Stack-cookies and
__try/__exceptrely on SEH chain integrity;SwitchToFiberhandles this, but rawRtlInstallFunctionTableCallbackon a fiber stack must use the fiber’sStackBase/StackLimit.
10. Common Attacker Techniques
Fibers are attractive to adversaries because the entire execution primitive lives in user mode — no NtCreateThread, no CreateRemoteThread, no kernel ETW event for the act of switching execution. The patterns below are documented in public threat-research literature; described conceptually here for detection engineers.
| Technique | Description |
|---|---|
In-process shellcode via SwitchToFiber | Allocate PAGE_EXECUTE_READWRITE memory, copy a payload, call ConvertThreadToFiber then CreateFiber with the payload as lpStartAddress, then SwitchToFiber — execution begins with no new thread |
| Fiber-based ROP staging | A fiber’s saved CONTEXT includes RIP and RSP; manipulating a FIBER struct’s context fields lets an attacker pivot the stack on SwitchToFiber |
PEB->FlsCallback overwrite | Overwrite an entry in the process-wide FLS callback array; on the next FlsFree or fiber/thread teardown the attacker-controlled pointer is invoked with attacker-controlled data |
| TLS evasion via FLS | Hide per-task state in FLS slots that defensive tooling enumerating TLS will miss |
| API hiding via intrinsics | GetCurrentFiber/GetFiberData produce no IAT entry; static analysis missing gs:[0x20] reads will not see fiber-aware code |
The base ATT&CK parent for fiber-based in-process execution is T1055 Process Injection; MITRE has not assigned a fiber-specific sub-technique, so the closest analogue is T1055.004 (APC) which shares the “queue execution to a thread’s user-mode context” model.
11. Defensive Strategies & Detection
There is no kernel event for SwitchToFiber. Detection must focus on the setup that precedes fiber-based execution (RWX allocation, suspicious entry points) and on memory forensics of fiber state at rest.
Sysmon coverage for the surrounding behavior:
| Event ID | Signal |
|---|---|
1 | Process Create — establish baseline lineage |
8 | CreateRemoteThread — co-occurs with cross-process fiber staging |
10 | ProcessAccess — reflective loaders reading remote memory before fiber dispatch |
17/18 | Named-pipe create/connect — common multi-stage loader IPC |
25 | ProcessTampering — image-region tampering in a fiber host |
ETW providers worth subscribing:
Microsoft-Windows-Threat-Intelligence— flagsVirtualAlloc/VirtualProtectwithPAGE_EXECUTE_*, the precursor to fiber shellcode staging.Microsoft-Windows-Kernel-Process— does not see fiber switches but covers process/thread lifecycle.- A user-mode consumer hooking
NtAllocateVirtualMemory+NtProtectVirtualMemorygives the strongest pre-execution signal.
Memory forensics indicators:
- Walk
TEB.NtTib.FiberDataon every thread. Threads withHasFiberData == 1in processes that have no business using fibers are immediately interesting. - Use Volatility
malfindto surface private, executable, non-image-backed pages — the target of a fiber-staged payload. - Dump
PEB->FlsCallbackand verify every entry resolves to an expected module’s.textsection.
Sigma sketch for the cross-process precursor to fiber-based payload staging:
title: Suspicious ProcessAccess Preceding User-Mode Fiber Execution
id: 8f5c1d6e-3c7b-4b1f-9e1e-7e3e6e2b0a1f
logsource:
product: windows
service: sysmon
detection:
selection:
EventID: 10
GrantedAccess:
- '0x1fffff' # PROCESS_ALL_ACCESS
- '0x1f0fff'
TargetImage|endswith:
- '\explorer.exe'
- '\svchost.exe'
filter_legit:
SourceImage|endswith:
- '\MsMpEng.exe'
- '\SenseIR.exe'
condition: selection and not filter_legit
level: high
tags:
- attack.t1055
- attack.t1106Hardening:
SetProcessMitigationPolicywithProcessDynamicCodePolicy(Arbitrary Code Guard) blocks creation of new executable pages, defeating fiber shellcode staging.- Control Flow Guard restricts indirect-call targets, narrowing
SwitchToFiberand FLS-callback abuse to valid entry points. - HVCI / memory integrity prevents kernel-side tampering of
FIBERstructures via vulnerable drivers. - WDAC / AppLocker policies that deny
PAGE_EXECUTE_*allocations on non-JIT processes raise the cost of any in-process execution primitive.

12. Tools for Fiber Analysis
| Tool | Description | Link |
|---|---|---|
| WinDbg | Dump TEB, walk NtTib.FiberData, inspect FIBER.FiberContext | microsoft.com |
| Process Hacker | Enumerate threads, inspect TEB, examine private RWX regions | processhacker.sf.io |
| Process Monitor | Capture VirtualAlloc/VirtualProtect sequences preceding fiber dispatch | sysinternals.com |
| Volatility 3 | windows.malfind, TEB plugins, FLS callback inspection | volatilityfoundation.org |
| pykd / WinDbg JS | Scripted walks of FIBER chains across all threads | githomelab.ru/pykd |
| x64dbg | User-mode debugging of fiber-aware binaries; trace gs:[0x20] reads | x64dbg.com |
| Ghidra | Static analysis; recognize GetCurrentFiber intrinsic pattern | ghidra-sre.org |
| Sysmon | Surrounding telemetry (Events 1, 8, 10, 25) | sysinternals.com |
A minimal WinDbg recipe to surface fiber-hosting threads in a captured process:
0:000> !teb
TEB at 000000abcd123000
...
NtTib.FiberData: 0000020fabcde000
...
0:000> dt ntdll!_TEB @$teb HasFiberData
0:000> dq 0000020fabcde000 L40 ; raw FIBER bytes — layout version-dependent13. MITRE ATT&CK Mapping
| Technique | MITRE ID | Detection |
|---|---|---|
| Process Injection | T1055 | Memory scan for private RWX regions; ETW TI on NtAllocateVirtualMemory |
| Process Injection: Asynchronous Procedure Call | T1055.004 | Closest published sub-technique to fiber-based in-process execution |
| Native API | T1106 | API-call auditing of CreateFiber/SwitchToFiber/FlsAlloc |
| Reflective Code Loading | T1620 | Image-load anomalies; fiber entry point in non-image-backed memory |
| Impair Defenses: Disable or Modify Tools | T1562.001 | ETW/AMSI hook integrity checks; user-mode hook auditing |
MITRE ATT&CK does not currently list a “Fiber Injection” sub-technique (current as of v16.1). Vendor research treats fiber-based execution as a variant of
T1055; map accordingly.
Summary
- A fiber is a user-mode cooperative thread invisible to the kernel scheduler —
SwitchToFiberperforms a stack and register swap entirely inKernelBase.dllwith no syscall. - The TEB exposes the fiber state via
NtTib.FiberData,HasFiberData, andFlsData; theFIBERstructure itself is undocumented and version-dependent. - TLS is per-thread and is not swapped on a fiber switch; FLS is per-fiber and is swapped, with destructor callbacks tracked in
PEB->FlsCallback. - Adversaries abuse fibers for in-process shellcode execution, ROP staging via the saved
CONTEXT, and code execution viaPEB->FlsCallbackoverwrites — none of which trigger thread-creation telemetry. - Detect via pre-execution signals (ETW TI on RWX allocations, Sysmon Event IDs
8/10/25), memory forensics on private executable regions andFlsCallbackintegrity, and hardening with ACG, CFG, and HVCI.
Related Tutorials
- System Calls and SSDT: How User Mode Reaches the Kernel
- User Mode vs Kernel Mode: Privilege Rings and the Boundary
- Threads and the TEB (Thread Environment Block)
- Access Tokens and Privileges: The Kernel’s Security Context
- SIDs and Security Descriptors: Identity in Windows Security
References
- Fibers – Win32 apps | Microsoft Learn
- Using Fibers – Win32 apps | Microsoft Learn
- CreateFiber function (winbase.h) – Win32 apps | Microsoft Learn
- ConvertThreadToFiber function (winbase.h) – Win32 apps | Microsoft Learn
- Process Injection, Technique T1055 – Enterprise | MITRE ATT&CK®
- About Processes and Threads – Win32 apps | Microsoft Learn
Get new drops in your inbox
Windows internals, exploit dev, and red-team write-ups — no spam, unsubscribe anytime.