1. مقدمهای بر فازینگ (An Introduction to Fuzzing)
فازینگ (Fuzzing) یکی از مؤثرترین روشهای خودکار برای شناسایی باگها (Bug) و آسیبپذیریهای نرمافزاری است که با تولید و ارسال ورودیهای تصادفی، نیمهتصادفی یا ساختیافته، رفتار برنامه را در شرایط غیرعادی مورد بررسی قرار میدهد. هدف اصلی این تکنیک، وادار کردن نرمافزار به پردازش دادههای غیرمنتظره و تحلیل واکنش آن در برابر این ورودیها است تا خطاهایی مانند کرش (Crash)، نشت حافظه (Memory leaks)، رفتارهای غیرمنتظره (unexpected behaviors) یا سایر نقصهای امنیتی آشکار شوند. فازینگ امروزه به عنوان یکی از مهمترین ابزارهای تحلیل امنیت نرمافزار شناخته میشود و نقش کلیدی در کشف باگها از جمله آسیبپذیریهای مرتبط با حافظه ایفا میکند.
1.1 گذار از SCA به فازینگ (Transitioning from SCA to Fuzzing)
تحلیل ایستا (Static Code Analysis یا SCA) روشی برای بررسی کد منبع یا باینری برنامه بدون اجرای آن است. هدف این روش، شناسایی باگها، الگوهای برنامهنویسی ناامن و آسیبپذیریهای احتمالی در مراحل اولیه توسعه است. با استفاده از تحلیل ایستا، توسعهدهندگان میتوانند بسیاری از مشکلات امنیتی و کیفی کد را پیش از اجرای برنامه شناسایی و برطرف کنند.
با وجود مزایای فراوان، تحلیل ایستا محدودیتهایی نیز دارد. از آنجا که این روش برنامه را اجرا نمیکند، همواره قادر به پیشبینی دقیق رفتار آن در زمان اجرا (Runtime) نیست. در نتیجه، ممکن است برخی از آسیبپذیریهای پیچیده مانند سرریز هیپ (Heap Overflow)، شرایط رقابتی (Race Condition)، خطاهای وابسته به وضعیت برنامه (State-Dependent Bugs) یا مشکلات ناشی از تعامل اجزای مختلف سیستم را تشخیص ندهد.
در چنین شرایطی، فازینگ (Fuzzing) بهعنوان مکمل تحلیل ایستا وارد عمل میشود. فازینگ با اجرای واقعی برنامه و تزریق حجم زیادی از ورودیهای متنوع، تصادفی یا هدفمند، رفتار نرمافزار را در شرایط مختلف ارزیابی میکند. این فرآیند میتواند باگهای زمان اجرا، کرشها و آسیبپذیریهایی را آشکار کند که از دید تحلیل ایستا پنهان ماندهاند. به همین دلیل، در بسیاری از فرایندهای مدرن امنیت نرمافزار، این دو تکنیک بهصورت مکمل و در کنار یکدیگر استفاده میشوند:
SCA → شناسایی نقاط ضعف اولیه → Fuzzing برای یافتن آسیبپذیریهای واقعی در زمان اجرا
به بیان دیگر، تحلیل ایستا دیدی جامع از کیفیت و امنیت کد ارائه میدهد، در حالی که فازینگ با اجرای عملی برنامه، وجود و قابلیت بهرهبرداری از بسیاری از آسیبپذیریها را در محیط واقعی ارزیابی و اثبات میکند.
1.2 دسته بندی فازرها بر اساس سطح آگاهی از برنامه هدف (Classification of Fuzzers Based on Their Knowledge of the Target Program)
- فازینگ جعبه سیاه (Black-box Fuzzing): بدون دسترسی به سورس یا اطلاعات داخلی برنامه، فقط ورودی/خروجی بررسی میشود.
- فازینگ جعبه سفید (White-box Fuzzing): با دسترسی به کد و استفاده از تحلیلهای ایستا/پویا (مثل symbolic execution). مثل:
- Z3 مایکروسافت که به عنوان (satisfiability modulo theories) SMT حل کننده (solvers) مسائل در نظر گرفته میشود و هنگامی که برنامه بزرگ شود با چالش روبرو خواهند شد.
- SAGE مایکروسافت از تکنیکهایی مانند اجرای نمادین پویا (Dynamic Symbolic Execution) و حل محدودیتها (Constraint Solving)، به شبیهسازی مسیرهای مختلف اجرایی برنامهها میپردازد و ورودیهای جدیدی را برای پوششدهی بیشتر مسیرهای کد تولید میکند.
- KLEE یک ابزار اجرای نمادین (symbolic execution) برای آزمون خودکار برنامههای نوشته شده به زبان ++C/C برای تولید ورودیهایی که پوشش کد را افزایش میدهند.
- Frama-C یک پلتفرم تحلیل کد منبع C که از روشهای مختلف ایستا و پویا استفاده میکند.
- ANGR یک چارچوب تحلیل باینری پایتون برای اجرای نمادین و تحلیل مسیر.
- Microsoft SLAM یک ابزار تحلیل ایستا و اثبات کد برای برنامههای ویندوز.
- فازینگ جعبه خاکستری (Grey-box Fuzzing): حالت میانی؛ فازر با استفاده از اطلاعاتی مثل پوشش کد (Code Coverage) ورودیها را بهینه میکند (نمونه معروف: AFL).
1.3 تکنیکهای مورد استفاده در دسته بندی های مختلف فازینگ (Techniques Used in Different Fuzzing Categories)
تکنیکهای مورد استفاده در آزمون و تحلیل نرمافزار را میتوان بر اساس میزان دسترسی تحلیلگر به ساختار داخلی برنامه، در سه دسته اصلی جعبه سفید (White-box)، جعبه سیاه (Black-box) و جعبه خاکستری (Gray-box) طبقهبندی کرد. هر یک از این رویکردها با توجه به سطح آگاهی از کد منبع، معماری و منطق داخلی برنامه، مزایا، محدودیتها و کاربردهای خاص خود را دارند که در ادامه به معرفی آنها میپردازیم.
تکنیکهای جعبه سفید (White-box Testing/Analysis): همانطور که پیشتر اشاره شد، رویکرد جعبه سفید (White-box) به روشهایی از آزمون و تحلیل نرمافزار گفته میشود که در آن تحلیلگر به کد منبع، ساختار داخلی، منطق برنامه و جریان اجرای آن دسترسی کامل دارد. این سطح از دسترسی امکان بررسی دقیق عملکرد اجزای مختلف نرمافزار و شناسایی آسیبپذیریها را فراهم میکند. در رویکرد جعبه سفید، تکنیکهای متعددی برای ارزیابی کیفیت، صحت و امنیت نرمافزار به کار گرفته میشوند که مهمترین آنها عبارتاند از:
- اجرای نمادین (Symbolic Execution):
- به جای استفاده از ورودیهای عددی مشخص، ورودیها به صورت نمادین (متغیرهای منطقی) در نظر گرفته میشوند.
- مسیرهای مختلف برنامه به صورت نمادین اجرا میشوند و محدودیتهای مسیر جمعآوری میشوند.
- با استفاده از SMT solver مثل Z3، تعیین میشود که کدام مسیرها قابل اجرا هستند.
- کاربرد: یافتن خطاها، باگهای منطقی، و تولید دادههای تست پوششدهنده مسیرهای مختلف.
- تحلیل ایستای کد (Static Code Analysis):
- بررسی کد منبع بدون اجرای برنامه.
- پیدا کردن خطاهای احتمالی، کدهای ناکارآمد، باگهای امنیتی و ناسازگاریها.
- ابزارهایی مثل SonarQube، Coverity، PVS-Studio نمونه هستند.
- بررسی مدل (Model Checking):
- مدلسازی رسمی سیستم به صورت یک مدل ریاضی (معمولاً اتوماتا یا گراف وضعیت).
- بررسی خودکار تمام حالتهای ممکن سیستم برای اثبات صحت یا یافتن باگ.
- کاربرد در نرمافزارهای امنیتی، سیستمهای ایمنی بحرانی.
- آزمایش پوشش کد (Code Coverage Testing):
- اندازهگیری اینکه چه درصدی از کد منبع توسط تستها اجرا شده است.
- تکنیکهای مختلف پوشش مثل پوشش خط، پوشش شرط، پوشش مسیر.
- هدف: اطمینان از اینکه تستها تمام بخشهای مهم کد را پوشش میدهند.
تکنیکهای جعبه سیاه (Black-box Testing): رویکرد جعبه سیاه (Black-box) به روشهایی از آزمون و تحلیل نرمافزار گفته میشود که در آن تحلیلگر هیچگونه دسترسی به کد منبع، ساختار داخلی یا منطق پیادهسازی برنامه ندارد. در این رویکرد، نرمافزار به عنوان یک «جعبه سیاه» در نظر گرفته میشود و ارزیابی آن صرفاً بر اساس ورودیهای ارسال شده، خروجیهای تولیدشده و رفتار قابل مشاهده سیستم انجام میگیرد. هدف اصلی در آزمون جعبه سیاه، بررسی صحت عملکرد، شناسایی خطاها و کشف آسیبپذیریها از دید یک کاربر یا مهاجم خارجی، بدون اطلاع از جزئیات داخلی برنامه است. به همین دلیل، این رویکرد یکی از متداولترین روشها در ارزیابی امنیت نرمافزار و آزمون نفوذ به شمار میرود. مهمترین تکنیکهای مورد استفاده در رویکرد جعبه سیاه عبارتاند از:
- تست تابعی (Functional Testing):
- تمرکز روی بررسی رفتار سیستم مطابق با نیازمندیها.
- بررسی ورودیها و خروجیها بدون توجه به ساختار داخلی.
- تست مبتنی بر معیارها (Boundary Value Testing):
- تمرکز روی بررسی رفتار سیستم در اطراف نقاط مرزی ورودیها (مثلاً حداقل و حداکثر مقادیر).
- تست تصادفی یا فازینگ (Fuzz Testing):
- ارسال ورودیهای تصادفی یا نامناسب به برنامه برای پیدا کردن کرشها یا خطاها.
- ابزاری مانندAFL، Peach Fuzzer کاربرد دارد.
- آزمون سازگاری (Compatibility Testing):
- بررسی عملکرد نرمافزار در محیطهای مختلف، سختافزارها، مرورگرها و سیستمعاملها.
- آزمون پذیرش کاربر (User Acceptance Testing):
- ارزیابی سیستم توسط کاربر نهایی برای تایید تطابق با نیازهای واقعی.
تکنیکهای جعبه خاکستری (Gray-box Testing): رویکرد جعبه خاکستری (Gray-box) ترکیبی از آزمون جعبه سفید و جعبه سیاه است. در این رویکرد، تحلیلگر به بخشی از اطلاعات داخلی سیستم، مانند معماری، مستندات، رابطهای برنامهنویسی (API)، قالب دادهها یا برخی اجزای کد، دسترسی دارد؛ اما این دسترسی کامل نیست. در کنار این اطلاعات، رفتار خارجی نرمافزار نیز از طریق تعامل با ورودیها و خروجیهای سیستم مورد ارزیابی قرار میگیرد. این رویکرد تلاش میکند مزایای هر دو روش را با یکدیگر ترکیب کند؛ بهگونهای که آگاهی نسبی از ساختار داخلی، فرآیند تحلیل را هدفمندتر و کارآمدتر سازد، در حالی که آزمون همچنان از دیدگاه عملکرد واقعی برنامه انجام میشود. به همین دلیل، آزمون جعبه خاکستری در ارزیابی امنیت برنامههای تحت وب، سرویسهای API، نرمافزارهای سازمانی و آزمون نفوذ کاربرد گستردهای دارد. مهمترین تکنیکهای مورد استفاده در رویکرد جعبه خاکستری عبارتاند از:
- تست مبتنی بر معماری (Architecture-based Testing):
- استفاده از اطلاعاتی مانند دیاگرامهای معماری سیستم برای طراحی تستها.
- تمرکز روی نقاط مهم و حساس سیستم.
- تست مسیرهای بحرانی (Critical Path Testing):
- در این تکنیک، تحلیلگر با بهرهگیری از دانش محدود درباره ساختار داخلی نرمافزار، مسیرهای اجرایی و بخشهایی از کد را که نقش مهمی در عملکردهای کلیدی برنامه دارند، شناسایی و بررسی میکند.
- تست امنیتی نیمهمستقیم (Semi-transparent Security Testing):
- استفاده از برخی اطلاعات داخلی برای طراحی حملات و تستهای نفوذ، بدون دسترسی کامل به کد.
- اجرای نمادین محدود (Concolic Testing):
- ترکیبی از اجرای نمادین و اجرای واقعی برنامه.
- بخشهایی از کد با ورودیهای مشخص اجرا میشود، سپس مسیرهای نمادین برای کشف مسیرهای بیشتر تحلیل میشود.
- ابزارهایی مثل SAGE و Dr. Memory این روش را به کار میبرند.
جدول ۱- تکنینکهای مورد استفاده در دستهبندیهای مختلف فازینگ
دستهبندی | دسترسی به کد منبع | تکنیکها و مثالها | هدف اصلی |
White-box | کامل | اجرای نمادین، تحلیل استاتیک، مدلچکینگ، پوشش کد | تست دقیق و کامل کد، پوشش مسیرها |
Black-box | صفر | تست تابعی، تست مرزها، Fuzzing، تست پذیرش | بررسی رفتار سیستم از بیرون بدون کد |
Gray-box | محدود | تست معماری، تست مسیر بحرانی، Concolic Testing | ترکیب دید داخلی و بیرونی برای تست بهتر |
1.4 تکنیکهای مهم فازینگ بر اساس نوع آزمون (Key Fuzzing Techniques Based on Testing Type)
1. فازینگ جعبه سفید (White-box Fuzzing): آزمون با دسترسی کامل به کد، تحلیل مسیر و ساختار برنامه.
- Symbolic Execution Fuzzing:
- ورودیها به صورت متغیرهای نمادین تعریف میشوند و مسیرهای مختلف برنامه به صورت نمادین اجرا میشوند.
- با حل محدودیتها (constraints) ورودیهای جدید ساخته میشوند تا مسیرهای بیشتری پوشش داده شوند.
- مثال: ابزار SAGE، KLEE.
- Concolic Testing (Concrete + Symbolic Execution):
- ترکیبی از اجرای نمادین و اجرای واقعی کد (Concrete Execution).
- ابتدا برنامه با ورودیهای واقعی اجرا میشود و مسیرهای نمادین استخراج و تحلیل میشود.
- ورودیهای جدید بهینهسازی شده بر اساس حل محدودیتها ساخته میشود.
- ابزار: SAGE, Dr. Memory, angr.
- Dynamic Taint Analysis-based Fuzzing:
- دنبال کردن تأثیر دادههای ورودی روی مسیرهای برنامه در زمان اجرا.
- کمک میکند تا نقاط حساس برنامه برای ایجاد ورودیهای جدید بهتر شناسایی شوند.
2. فازینگ جعبه سیاه (Black-box Fuzzing): تست بدون دسترسی به کد، فقط بر اساس ورودی و خروجی.
- Mutation-based Fuzzing:
- با گرفتن ورودیهای معتبر موجود، تغییرات (mutation) تصادفی روی آنها اعمال میشود تا ورودیهای جدید تولید شود.
- ساده و سریع اما گاهی محدود در کشف حالات جدید.
- ابزارها: AFL (American Fuzzy Lop)، zzuf.
- Generation-based Fuzzing:
- ورودیها بر اساس یک مدل یا دستور زبان (Grammar) مشخص ساخته میشوند.
- اجازه میدهد ورودیهای دقیق و هدفمند بر اساس پروتکلها یا قالبها تولید شود.
- ابزارها: Peach Fuzzer, BooFuzz.
- Protocol-aware Fuzzing:
- فازینگ مبتنی بر دانش پروتکل ارتباطی سیستم.
- ورودیها بر اساس قواعد پروتکل ساخته یا دستکاری میشوند.
3. فازینگ جعبه خاکستری (Gray-box Fuzzing): دسترسی محدود به اطلاعات داخلی سیستم؛ ترکیب تحلیل خارجی و داخلی.
- Coverage-guided Fuzzing:
- فازری که بر اساس میزان پوشش کد هدایت میشود.
- با استفاده از اطلاعاتی مثل پوشش خطوط یا بلوکهای کد (که از اجرای برنامه به دست میآید)، ورودیهای جدیدی ساخته میشود که پوشش را افزایش میدهند.
- ابزار معروف: AFL (American Fuzzy Lop)، libFuzzer.
- Concolic Fuzzing:
- همان ترکیب اجرای نمادین و اجرای واقعی که در رویکرد جعبه سفید (white-box) استفاده میشود، اما با دسترسی محدودتر.
- ابزارهایی مثل SAGE در برخی موارد جعبه خاکستری (gray-box) هم حساب میشوند.
- Feedback-driven Fuzzing:
- استفاده از فیدبکهایی مانند کرشها، هشدارها، و پوشش برای بهبود و هدایت فرایند فازینگ.
1.5 تکنیکهای تخصصی دیگر در فازینگ (Other Specialized Fuzzing Techniques)
- Grammar-based Fuzzing:
- تولید ورودیها بر اساس دستور زبان رسمی (Grammar) که ورودیها را توصیف میکند.
- استفاده برای تست پروتکلها، فایلها، قالبهای داده.
- Model-based Fuzzing:
- استفاده از مدلهای رسمی (مثلاً State Machines) برای تولید ورودیهایی که کل حالات سیستم را پوشش میدهند.
- Evolutionary/Genetic Fuzzing:
- استفاده از الگوریتمهای تکاملی برای تولید ورودیهای جدید به صورت هوشمند و بهینه شده.
به طور خلاصه:
جدول ۲- تکنیکهای مهم فازینگ بر اساس نوع آزمون
نوع فازینگ | تکنیکهای مهم | توضیح مختصر |
White-box | Symbolic Execution، Concolic Testing، Taint Analysis | اجرای نمادین، اجرای کانکولیک، تحلیل دادهها |
Black-box | Mutation-based، Generation-based، Protocol-aware | تولید ورودی بر اساس ورودیهای قبلی یا مدلها |
Gray-box | Coverage-guided، Feedback-driven، Concolic | استفاده از پوشش کد، بازخورد، ترکیب اجرای واقعی و نمادین |
1.6 دسته بندی فازرها بر اساس محیط اجرا (Classification of Fuzzers Based on the Execution Environment)
- فازینگ سطح برنامه (Application-level Fuzzing): آزمون مستقیم روی نرمافزار یا API.
- فازینگ سطح پروتکل (Protocol-level Fuzzing): مخصوص پروتکلهای شبکه (TCP/IP, HTTP و …).
- فازینگ قالببندی فایل (File-format Fuzzing): مخصوص قالببندیهای فایل (مثلاً PDF، PNG).
- فازینگ هسته/درایور (Kernel/Driver Fuzzing): آزمون بر روی کرنل یا درایورهای سیستمعامل.
- فازینگ وب (Web Fuzzing): مخصوص برنامههای وب (مثلاً پیدا کردن XSS، SQLi).
1.7 دسته بندی فازرها بر اساس آگاهی از ساختار داده (Classification of Fuzzers Based on Data Structure Awareness)
- فازینگ کور (Black-box Fuzzing): بدون دانش از ساختار ورودیها، دادههای تصادفی یا نیمهتصادفی تولید میشود.
- فازینگ آگاه از قالببندی (Format-aware / Grammar-based Fuzzing): ورودیها بر اساس قواعد و دستور زبان مشخص تولید میشوند.
- فازینگ مبتنی بر جهش (Mutation-based Fuzzing): روی ورودیهای معتبر، تغییرات (mutation) یا جهش انجام میشود.
- فازینگ مبتنی بر تولید (Generation-based Fuzzing): ورودیها از پایه و با پیروی از قالب یا پروتکل ساخته میشوند.
1.8 دسته بندی فازرها بر اساس روش جستجو و اکتشاف (Classification of Fuzzers Based on Search and Exploration Strategies)
- فازینگ تصادفی (Random Fuzzing): ورودیها کاملاً تصادفی تولید میشوند.
- فازینگ هدایتشده مبتنی بر پوشش (Coverage-Guided Fuzzing): فازر تلاش میکند ورودیهایی تولید کند که بیشترین بخش از کد را پوشش دهند.
- فازینگ نمادین/کانکولیک (Concolic/Symbolic Fuzzing): ترکیب اجرای واقعی با تحلیل نمادین برای کشف مسیرهای جدید.
2. فازر ++AFL و تاریخچه آن (AFL++ Fuzzer and Its History)
محصول ++AFL (یا American Fuzzy Lop Plus Plus) یک فازینگ مبتنی بر پوشش (Coverage-Guided) یا جعبه خاکستری (Greybox) است. یعنی هدف اصلی آن کشف مسیرهای جدید در برنامه و پیدا کردن ورودیهایی است که باعث کرش (Crash) یا رفتار غیرعادی شوند. AFL مسیرهای برنامه را با استفاده از ابزارگذاری (instrumentation) دنبال میکند و تلاش میکند ورودیهای جدیدی تولید کند که مسیرهای بیشتری را فعال کنند. ++AFL یک نسخه پیشرفته و توسعه یافته از AFL است که یکی از معروفترین فازرهای جعبه خاکستری در دنیا محسوب میشود.
2.1 نحوه کار فازر ++AFL
الگوریتم گام به گام ساده شده:
- ابتدا، ++AFL موارد آزمایشی را از مجموعه اولیه در صف موارد آزمایشی بارگذاری میکند؛
- سپس ورودی بعدی را در صف موارد آزمایشی بارگذاری میکند؛
- ورودی را تغییر میدهد و هدف را با ورودی تغییر یافته اجرا میکند؛
- بازخورد اجرا را مشاهده میکند؛
- اگر ورودی تغییر یافته مسیر جدیدی در برنامه پیدا نکرد، کنار گذاشته میشود. اگر مسیر جدیدی پیدا شد، در صف موارد آزمایشی قرار میگیرد. و اگر باعث خرابی برنامه شد، برای تجزیه و تحلیلهای آینده ذخیره میشود؛
- به مرحله ۲ برمیگردد.
++AFL به صورت دورهای صف موارد آزمون را تجزیه و تحلیل میکند تا ورودیهای منسوخشدهای را که میتوانند با ورودیهای جدید با پوشش بیشتر جایگزین شوند، حذف کند و همچنین مراحل دیگری را با هدف بهینهسازی فرآیند فازینگ انجام دهد.
2.2 تاریخچه کوتاه (Brief History)
- AFL اصلی توسط Michal Zalewski[8] توسعه یافت و به دلیل توانایی در پیدا کردن باگهای امنیتی و سرعت بالایش، بسیار محبوب شد.
- AFL از Instrumented Binary استفاده میکند: یعنی برنامه باید طوری کامپایل شود که AFL بتواند مسیرهای اجرایی و شاخههای برنامه را زیر نظر بگیرد.
- ++AFL نسخهای متنباز و بهینهشده است که علاوه بر تمام قابلیتهای AFL، پشتیبانی از کامپایلرهای مختلف، سیستمهای عامل متنوع، و فازینگ پیچیدهتر را ارائه میدهد.
2.3 قابلیتها (Capabilities)
فازر فوق شامل قابلیتهایی است که در زیر به برخی از آنها اشاره شده است:
- Persistent Mode: در AFL و AFL++، Persistent Mode یک تکنیک بسیار مهم است که برای کاهش هزینه اجرای برنامهها (execution overhead) طراحی شده است، در حالت عادی بعد از تست یک ورودی فاز مجددا برنامه از اول اجرا می شود.
- QEMU Mode: در ++AFL برای اجرای برنامههای بدون سورس در یک شبیهساز برای فازینگ، بدون نیاز به کامپایل مجدد یا دسترسی به کد منبع.
- LLVM-based Instrumentation: به طور خلاصه این تکنیک برای افزایش دقت و کارایی استفاده می شود. در ++AFL با استفاده از LLVM به سورس کد برنامه هدف کد هایی جهت مانیتورینگ با ابزارگذاری (Instrumentation) هنگام کامپایل اضافه می گردد تا مسیرهای اجرا شده و کرشها با دقت و سرعت بالا ثبت شوند.
- unicorn_mode: به شما اجازه میدهد یک تکه باینری (مثلاً یک تابع خاص از firmware یا یک روتین اسمبلی) را مستقیماً در emulator بارگذاری کنید و ++AFL روی آن فازینگ کند.
- MOpt mutators: یک مکانیزم پیشرفته برای بهینهسازی جهشها (mutation) است که به جای استفاده از مجموعه ثابت و پیشفرض جهشها، به صورت خودکار یاد میگیرد کدام جهشها بیشتر احتمال تولید ورودیهای جدید و مؤثر را دارند. به زبان ساده، این سیستم میبیند کدام تغییرات ورودی (جهشها) مفید هستند و بیشتر از آنها استفاده میکند و تغییراتی که بیفایدهاند را کنار میگذارد.
- AFLfast++ power schedules: در ++AFL، مفهوم Power Schedules مربوط به نحوهٔ اختصاص انرژی (Energy) به هر ورودی (seed) برای جهش (mutation) است. به زبان ساده، هر بذر چقدر باید جهش داده شود و چند بار باید توسط فازر امتحان گردد، را Power Schedule تعیین میکند. ++AFLFast، نسخه بهینهشده AFL، مجموعهای از Power Schedules پیشرفته دارد که رفتار fuzzing را بهینه میکنند.
- Redqueen: در AFL++، RedQueen یک الگوریتم پیشرفته برای کشف مسیرهای پیچیده و branch-heavy در برنامهها است.
- enhanced laf-intel: در AFL++، Enhanced LAF-Intel یکی از تکنیکهای ابزارگذاری (instrumentation) پیشرفته برای فازینگ است که مخصوصاً برای برنامههایی با branchهای comparison-heavy طراحی شده است.
- Collision-free coverage: یعنی یک سیستم رهگیری مسیرها بدون برخورد hash. به جای اینکه مسیرهای مختلف به یک مقدار hash مشابه منجر شوند، هر مسیر یک شناسه یکتا دارد.در نتیجه: ++AFL میتواند بهتر مسیرهای جدید و واقعی را تشخیص دهد و ورودیهای تکراری کاذب تولید نکند.
- Dictionary Support: امکان استفاده از دیکشنریهای ورودی برای افزایش شانس کشف مسیرهای جدید. مخصوصاً برای پروتکلها و فایلفرمتهای پیچیده مفید است.
- Nyx mode: در AFL++، Nyx mode یک حالت ویژهٔ فازینگ است که برای باینریهای ابزارگذاری شده (instrumented binaries) طراحی شده و امکان اجرای بسیار سریع و بازگردانی اسنپشات (snapshotting) را فراهم میکند. این حالت به ویژه مناسب فازینگ سیستمهای بزرگ، کرنلها یا برنامههای پیچیده است که اجرای سریع و پایدار نیاز دارند،ولی پیشنیاز آن این است که باینری هدف با ابزارهای ++AFL یا سازگار، ابزارگذاری شده باشد تا اطلاعات پوشش کد جمعآوری شود.البته مخزن KVM-Nyx هم وجود دارد که برای فازینگ hypervisor استفاده میشود که برای این امر از KVM به همراه فعال سازی قابلیت پردازنده اینتل بنام Intel-PT tracing استفاده می کند.
2.4 نقاط ضعف (Weaknesses)
- وابستگی به coverage-guided fuzzing:
- ++AFL مسیرهای جدید برنامه را با بررسی پوشش (coverage) شناسایی میکند.
- ضعف: اگر کرنل یا برنامهای مسیرهای زیادی دارد که به ندرت فعالسازی (trigger) میشوند، ++AFL ممکن است نتواند آنها را کشف کند.
- مثال: مسیرهایی که نیاز به ورودی بسیار خاص یا ترتیب پیچیدهای دارند.
- محدودیت در پیدا کردن باگهای زماندار یا وضعیتمند (stateful):
- ++AFL برای برنامههای stateful یا وابسته به زمان (مثل شبکه یا پروتکلها) سختتر عمل میکند.
- دلیل: ++AFL بیشتر روی فایل یا ورودیهای ساده تمرکز دارد و توانایی مدیریت وضعیت پیچیده یا فازینگ پروتکل محدود است.
- عدم مدیریت پیچیده قالببندیهای وروردی:
- ++AFL بهتر از AFL کلاسیک ورودیهای پیچیده را مدیریت میکند، اما هنوز نیاز به بذر معتبر و شناخت قالببندی ورودی (input format) دارد.
- اگر قالببندی ورودی (input format) پیچیده باشد و بذر اولیه مناسب نداشته باشیم، فازینگ ناکارآمد میشود.
- محدودیت در کشف باگهای بدون کرش:
- ++AFL بر کرش برنامه تمرکز دارد.
- ضعف: رفتارهای غیرعادی یا باگهای غیرمنطقی که باعث کرش (crash) نمیشوند، توسط ++AFL شناسایی نمیشوند.
- مصرف بالای منابع در حالتهای پیشرفته:
- حالتهای پیشرفته مثل QEMU mode یا Unicorn mode برای fuzz کردن باینریها:
- به شدت کندتر هستند.
- مصرف حافظه و CPU بیشتری دارند.
- در برخی سیستمها، ممکن است اجرای طولانی نیاز به مدیریت منابع داشته باشد.
- حالتهای پیشرفته مثل QEMU mode یا Unicorn mode برای fuzz کردن باینریها:
- حساسیت به بذر اولیه:
- همانطور که قبلاً گفتیم، بذر اولیه باید معتبر باشد.
- چنانچه بذر خراب یا ناکامل باشد، ++AFL ممکن است:
- مسیرهای مهم برنامه را کشف نکند.
- زمان زیادی را صرف وصلههای بیفایده کند.
2.5 مقایسه فازر ++AFL با چند فازر معروف دیگر (Comparison of AFL++ with Several Other Well-Known Fuzzers)
در جدول زیر فازر ++AFL را با چند فازر دیگر از نظر ویژگیها به صورت کلی مقایسه کردهایم.
جدول ۳. مقایسه فازر ++AFL با چندین فازر معروف دیگر
ویژگی / فازر | AFL (کلاسیک) | AFL++ | libFuzzer | honggfuzz | Syzkaller | OSS-Fuzz | Radamsa |
نوع fuzzing | Coverage-guided, mutation-based | Coverage-guided, mutation-based (با استراتژیهای مدرن) | Coverage-guided, mutation-based, in-process | Coverage-guided, mutation-based | Coverage-guided, syscall fuzzing | Coverage-guided, cloud-based fuzzing | Mutation-based, dumb fuzzer |
پشتیبانی از binary | بله (QEMU mode) | بله (QEMU، Unicorn، Frida، LLVM LTO) | محدود، mostly source-based | بله از binary fuzzing پشتیبانی میکند | بله، kernel/syscall fuzzing | بله (اما معمولاً source-based) | بله (ساده) |
سرعت اجرای fuzzing | متوسط | سریعتر (افزودن تکنیکهای parallel و LLVM) | بسیار سریع ((in-process | سریع | متوسط تا کند (syscall-heavy) | متوسط، وابسته به زیرساخت Cloud | بسیار سریع ولی بدون feedback |
استراتژیهای mutation | پایهای (bitflip, arithmetic, havoc) | پیشرفته (MOpt، RedQueen، CmpLog، AFLfast) | پیشرفته با corpus evolution | خوب، مشابه AFL++ | محدود به syscall mutation | ترکیب AFL/libFuzzer | ساده (random mutations) |
Stateful fuzzing | ضعیف | کمی بهبود (persistent mode، custom mutator) | متوسط | بهتر از AFL/AFL++ | عالی (برای kernel/syscalls) | محدود | ضعیف |
مدیریت ورودی پیچیده | نیاز به seed معتبر | بهتر (custom mutators, grammar mode) | بهتر برای in-memory APIs | خوب | نیازمند syscall templates | نیاز به seed معتبر و integration | محدود، بدون understanding |
کشف باگها | crash و memory corruption | crash، memory corruption، leak detection | crash + UBSan/ASan | crash، memory corruption، leak detection | kernel crashes | crash، memory corruption | crash-based |
پایداری و توسعه | توسعه متوقف شده (فقط patchهای کوچک) | توسعه فعال (community-driven, features جدید) | توسعه فعال توسط LLVM/Google | توسعه فعال | توسعه فعال (Google) | توسعه فعال (Google) | توسعه کم اما ساده |
نقاط ضعف اصلی | سرعت پایینتر، نداشتن mutationهای مدرن | نیاز به seed معتبر، هنوز محدود در stateful | نیاز به source code و in-process integration | مصرف منابع، نیاز به seed معتبر | پیچیدگی زیاد در setup | وابستگی به محیط Cloud و CI | بدون feedback، مسیرهای عمیق سخت |
نقاط قوت اصلی | سادگی، پایدار، قابل اتکا | بسیار قابل توسعه، سریعتر، mutation هوشمندتر | بسیار سریع، یکپارچه با Sanitizerها | انعطافپذیر، پشتیبانی از input formats پیچیده | بهترین برای kernel/syscall fuzzing | مقیاسپذیر برای پروژههای بزرگ | سریع، ساده، مناسب برای data-driven fuzzing |
AFL++ جدید | AFL قدیمی | زمینه |
MOpt، RedQueen، CmpLog (mutation هوشمند مبتنی بر مقایسهها) | فقط الگوریتمهای کلاسیک | جهش (Mutation) |
LLVM LTO، Frida، QEMU بهینه | حالت QEMU کند | سرعت (Speed) |
Multi-core/cluster-friendly | محدود | فازینگ موازی (Parallel Fuzzing) |
Custom mutator، grammar-based fuzzing | پشتیبانی بسیار محدود | دستور زبان / ساختار (Grammar/Structure) |
پشتیبانی بهتر از ASAN/MSAN، persistent mode | سنتی | ادغام (Integration) |
2.7 ویژگیهای مهم ++AFL
فازینگ جعبه خاکستری (Greybox fuzzing): این فازینگ بین فازینگ جعبه سیاه (Blackbox) و جعبه سفید (Whitebox) قرار دارد، یعنی برنامه نیاز به سورس ندارد ولی اطلاعاتی از اجرای آن جمعآوری میکند.
- توانایی کشف آسیبپذیریهای حافظه مثل buffer overflow، use-after-free و مشکلات منطقی در برنامه.
- قابلیت ترکیب با فازینگ هوشمند دیگر و ابزارهای هدایت شده مبتنی بر پوشش.
فازر ++AFL با سرعت بالا و قابلیتهای پیشرفته، در بسیاری از تحقیقات امنیتی و تست نرمافزارها به استانداردی تبدیل شده است.
2.8 آیا امکان استفاده از ++AFL به عنوان فازر دسته بندی جعبه سیاه و سفید هم وجود دارد؟ (Can AFL++ be used as a black-box or white-box fuzzer)
در زیر به صورت خلاصه سه مدل فازینگ را برای فازر ++AFL بررسی میکنیم:
- فازینگ جعبه خاکستری (Greybox – حالت پیش فرض)
- حالت طبیعی ++AFL این Greybox است
- دارای جمعآوری code coverageها است.
- دارای جهش (mutation) هدایت شده (guided mutation) است. به این معنی که با توجه به بازخورد های دریافتی از اجرای برنامه تغییرات و داده های جدید را تصادفی صرف تولید نمی کند چون در حالت عادی جهش ها با تکنیک هایی مثل عوض کردن بایتها، حذف،یا اضافه کردن تکه از داده و تکنیکهای دیگر انجام میشود.
- دارای بازخورد (feedback) فعال تا بذرهای ورودی (seeds) را بهینه کند.
- قابل اجرا روی: LLVM, QEMU, Unicorn
- نمونه دستور اجرا پیش فرض با پوشش کد (Coverage feedback) و اجرا با مد QEMU:
afl-fuzz -Q -i in_dir -o out_dir -- ./target @@
- فازینگ جعبه سیاه (Blackbox):
- چنانچه پوشش کد (coverage) را نادیده بگیریم و فقط برنامه را با ورودیهای تصادفی اجرا کنیم، عملاً ++AFL مثل یک فازینگ جعبه سیاه یا dumb fuzzer میشود و برای این کار از ارسال پارامتر n- به afl-fuzz استفاده میشود، اما afl برای زمانی که کد در دسترس نیست هم قادر از ابزارگذاری دقیق در حین کار (on-the-fly instrumentation) با qemu و ابزارگذاری پویا (dynamic instrumentation) در زمان اجرا پوشش کد را اضافه و عملا مشابه فازینگ جعبه خاکستری اما با سرعت تقریبا نصف زمانی که سورس داریم فازینگ انجام می دهد، البته تکنیک ها و روش هایی دارد که میتوان تا حد بسیار خوبی این سرعت فازینگ را افزایثش داد.
- محدودیت: دیگر از هوش فازینگ جعبه خاکستری برای هدایت جهش (mutation) استفاده نمیشود، بنابراین بازده کم است.
- نمونه دستور اجرا بدون بازخورد پوشش کد (Coverage feedback) و اجرا با حالت QEMU:
AFL_NO_COVERAGE=1 afl-fuzz -Q -i in_dir -o out_dir -- ./target @@
- فازینگ جعبه سفید یا اجرای نمادین (Whitebox / symbolic execution):
- ++AFL خودش فازینگ جعبه سفید نیست.
- برای فازینگ جعبه سفید نیاز به ابزارهایی مانند angr، KLEE یا symbolic execution engine است.
- برخی تکنیکها (مثل RedQueen یا LAF-Intel) ممکن است مسیرها را بهتر پیدا کنند، اما هنوز فازینگ جعبه خاکستری هستند، نه جعبه سفید کامل.
- نمونه دستور اجرای دستور با angr در این حالت حل کننده قیود (constraint solver) و اجرای نمادین (symbolic execution) برای پیدا کردن مسیرها استفاده میشود، نه جهش (mutation) هدایت شده ساده ++AFL:
angr-fuzz ./target -i in_dir -o out_dir
2.9 چگونگی جهش در ++AFL
1. مراحل جهش در ++AFL:
- انتخاب بذر یا نمونه اولیه (Seed Selection): ++AFL ابتدا یک یا چند ورودی بذر (seed inputs) رو انتخاب میکنه. این ورودیها معمولاً ورودیهای معتبر برنامه هستند.
- کپی نمونه و شروع جهش (Copy and Start Mutation): نمونه انتخاب شده کپی میشود تا تغییرات روی کپی اعمال شود.
- اعمال جهشها (Applying Mutations): در این مرحله روشها و متدهای مختلف جهش به ورودی اعمال میشوند تا ورودی جدید تولید شود.
- اجرای برنامه با ورودی جدید (Execution): برنامه مورد تست با ورودی جدید اجرا میشود و نتایج ثبت میشود (مثل پوشش کد، کرشها و …).
- ارزیابی ورودی جدید (Evaluation): ورودیهای که باعث افزایش پوشش یا پیدا کردن خطا شدهاند، ذخیره و به مجموعه ورودیها اضافه میشوند.
- حلقه دوباره (Loop): این فرآیند بارها تکرار میشود تا بیشترین تعداد پوشش و خطاها پیدا شود.
2. متدهای جهش (Mutation Methods) در ++AFL:
++AFL مجموعه گستردهای از تکنیکهای جهش دارد که به دو دسته کلی تقسیم میشوند:
- تغییرات ساده (Bit/Byte-level mutations):
- Bit Flip: معکوس کردن یک یا چند بیت در ورودی.
- Byte Flip: معکوس کردن یک یا چند بایت.
- Arithmetic Mutations: اعمال عملیات جمع و تفریق روی بایتها یا کلمات ورودی، برای مثال افزایش یا کاهش مقدار عددی.
- Interesting Values: جایگزینی بایتها با مقادیر خاص (مثلاً 0, 255, -1, 127) که معمولاً در خطاهای برنامه موثرند.
- Block Deletion / Duplication: حذف یا تکثیر بلوکهایی از داده ورودی (مثلاً حذف ۱۶ بایت یا دو برابر کردن آن).
- Insertion: درج دادههای تصادفی یا خاص در موقعیتهای مختلف.
- تغییرات ترکیبی و پیشرفته:
- Splicing: ترکیب دو ورودی از نمونههای مختلف، به این صورت که بخشی از یک ورودی با بخشی از ورودی دیگر جایگزین شود.
- Dictionary-based Mutations: استفاده از دیکشنری ورودیها (کلمات کلیدی، الگوها) برای جایگزینی یا افزودن دادههای معنادار به ورودی.
- HAVOC Stage: اجرای جهشهای تصادفی و ترکیبی زیادی روی ورودی (شامل bit flips، byte flips، deletions و insertions) به طور تصادفی برای اکتشاف بهتر فضای ورودی.
- Trim: کاهش اندازه ورودیها برای افزایش سرعت تست، در حالی که پوشش کد حفظ میشود.
- تکنیکهای خاص و بهبود یافته در ++AFL:
- MOpt (Mutation Optimization): یادگیری خودکار الگوی جهشهای موثر در طول زمان برای بهبود جهشها.
- Power Schedules: تخصیص هوشمندانه منابع (مثلاً تعداد دفعات جهش) به ورودیهای مختلف بر اساس ارزش آنها (مثل پوشش ایجاد شده).
- Context-aware mutations: جهشهای هوشمندانه بر اساس بافت داده (مثلاً برای فایلهای متنی یا باینری).
- Custom mutators: امکان افزودن پلاگینهای جهش مخصوص پروژهها (مثل grammar-based mutators).
3. نحوه نصب و راه اندازی فازر ++AFL
3.1 روش نصب آسان (Easy Installation Method)
برای اینکه ++AFL به راحتی با هر چیزی که کامپایل شده است در دسترس باشد، image را مستقیماً از Docker Hub (که هم برای x86_64 و هم برای arm64 موجود است) دریافت کنید:
docker pull aflplusplus/aflplusplus
docker run -ti -v /location/of/your/target:/src aflplusplus/aflplusplus
این image به طور خودکار هنگام انتشار به شاخه (branch) پایدار (stable) منتشر میشود (به شاخهها مراجعه کنید). اگر از دستور بالا استفاده کنید، کد منبع هدف خود را در src/ در کانتینر خواهید یافت. توجه: همچنین میتوانید aflplusplus/aflplusplus:dev را که آخرین وضعیت توسعه ++AFL است، دریافت کنید. برای ساخت ++AFL خودتان – که توصیه شده است که به مسیر – به docs/INSTALL.md در مخزن رسمی مراجعه کنید.
3.2 روش نصب استاندارد (Standard Installation Method)
در اولین گام بایستی آخرین نسخه از این فازر را با دستورات زیر دانلود کرد.
$ git clone https://github.com/AFLplusplus/AFLplusplus
$ cd AFLplusplus
فازر ++AFL دارای گزینههای زیادی است. آسانترین روش ساخت و نصب همه قابلیتها را در زیر مشاهده میکنید.
$ make distrib
$ sudo make install
پس از اجرای موفقیتآمیز دستور نخست برای کامپایل سورس کد، خروجی مشابه تصویر زیر نمایش داده خواهد شد:
در این مرحله، با اجرای دستور زیر، ابزار AFL را روی سیستم نصب میکنیم. پس از اتمام موفقیتآمیز فرایند نصب، خروجی ترمینال مشابه تصویر زیر خواهد بود:
پس از نصب موفقیتآمیز AFL، با وارد کردن عبارت afl در ترمینال و فشردن کلید Tab، فهرست ابزارها و دستورات مربوط به این فازر که روی سیستم نصب شدهاند نمایش داده خواهد شد:
توجه داشته باشید که دستور نخست، علاوه بر هسته اصلی AFL، مؤلفههایی مانند llvm_mode، qemu_mode، unicorn_mode و سایر حالتهای اجرایی را نیز کامپایل (Build) میکند. اگر تنها به نسخه پایه AFL نیاز دارید، اجرای دستور make all کافی است. با این حال، برای دستیابی به عملکرد بهتر، سرعت بیشتر و پوشش مناسبتر در فرایند فازینگ، اکیداً توصیه میشود حداقل llvm_mode را نیز کامپایل و استفاده کنید. به همین دلیل، در این آموزش از روش کامل کامپایل استفاده شده است.
$ make source-only
چیزی است که باید انتخاب کنید.
گزینههای ساخت به شرح زیر میباشند:
all یا همه: فقط فایلهای باینری اصلی ++AFL.
binary-only یا فقط باینری: همه چیز برای فازینگ فقط باینری: qemu_mode، unicorn_mode، libdislocator، libtokencap، radamsa
source-only یا فقط منبع: همه چیز برای فازینگ کد منبع: llvm_mode، libdislocator، libtokencap، radamsa
distrib یا دستهبندی: همه چیز (برای فازینگ فقط باینری و کد منبع)
install یا نصب: همه چیزهایی را که با گزینههای ساخت بالا کامپایل کردهاید نصب میکند.
clean یا تمیز: همه چیز را پاک میکند. برای qemu_mode و unicorn_mode به این معنی است که همه دانلودها را نیز حذف میکند.
code-format یا قالب بندی-کد: کد را فرمت کنید، این کار را قبل از کامیت کردن انجام دهید و لطفاً یک PR ارسال کنید!
Tests یا آزمایشها: موارد آزمایشی (test cases) را اجرا میکند تا اطمینان حاصل شود که همه ویژگیها هنوز آنطور که باید کار میکنند.
Help یا کمک: این مورد گزینههای راهنمای ساخت را نمایش میدهد.
در صورتی که از سیستم عامل mac استفاده نمیکنید میتوانید نسخه ایستا (Static) باینریهای ++AFL را با دستور زیر بسازید.
make STATIC=1
توجه داشته باشید که هرچه نسخه کامپایلرهای مورد استفاده جدیدتر باشد، عملکرد ++AFL از نظر سرعت، کارایی و کیفیت فرایند فازینگ بهبود خواهد یافت. به همین دلیل، استفاده از GCC-9 و بهویژه LLVM/Clang 9 یا نسخههای جدیدتر بهشدت توصیه میشود. اگر این کامپایلرها در توزیع لینوکسی شما در دسترس نیستند، میتوانید از Dockerfile ارائهشده در پروژه برای ایجاد یک محیط آماده و سازگار استفاده کنید:
$ docker build -t aflplusplus
4. نحوه کاربری فازر ++AFL با سناریوهای مختلف (How to Use AFL++ Fuzzer in Different Scenarios)
در شکل زیر، فرایند کلی فازینگ یک برنامه هدف در شرایطی که کد منبع آن در دسترس باشد، نمایش داده شده است. در ادامه این سند، هر یک از این مراحل را با استفاده از سناریوهای مختلف بهصورت عملی بررسی خواهیم کرد. بهطور کلی، اجرای یک کمپین فازینگ شامل چهار مرحله اصلی است:
- ابزارگذاری (Instrumentation) برنامه هدف:
در این مرحله، برنامه بهگونهای کامپایل یا آمادهسازی میشود که اطلاعاتی مانند پوشش کد (Code Coverage)، مسیرهای اجرای برنامه و سایر دادههای موردنیاز برای هدایت و بهینهسازی فرایند فازینگ در اختیار فازر قرار گیرد. - آمادهسازی کمپین فازینگ:
این مرحله شامل جمعآوری نمونههای اولیه ورودی (Seed Corpus)، تولید ورودیهای مناسب و متنوع، حذف نمونههای تکراری و کمارزش، و کمینهسازی مجموعه ورودیها (Corpus Minimization) برای افزایش کارایی فرایند فازینگ است. - اجرای فازر (Fuzz Target):
در این مرحله، فازر با پیکربندی مناسب روی برنامه هدف اجرا میشود. انتخاب گزینههای مناسب، استفاده از تمام هستههای پردازنده برای اجرای موازی، و در صورت نیاز راهاندازی کمپین توزیعشده روی چندین سیستم، از مهمترین اقدامات این مرحله به شمار میروند. - مدیریت و تحلیل کمپین:
پس از آغاز فرایند فازینگ، وضعیت کمپین باید بهصورت مداوم پایش شود. بررسی میزان پوشش کد، تحلیل و دستهبندی کرشها (Crash Triage)، حذف موارد تکراری و ارزیابی نتایج بهدستآمده، از مهمترین فعالیتهای این مرحله هستند.
در تصویر زیر نحوه ابزارگذاری (Instrumenting) هدف در فازر ++AFL را مشاهده میکنید:
- انتخاب کامپایلر مناسب: انتخاب کامپایلری که از قابلیتهای موردنیاز فازر، مانند پوشش کد و سنیتایزرها، پشتیبانی کند.
- انتخاب گزینههای کامپایل: اعمال گزینههای (Compile Options) متناسب با کامپایلر انتخابشده و نوع کمپین فازینگ.
- انتخاب سنیتایزر (Sanitizer): در هر اجرای فازر تنها یک سنیتایزر باید فعال باشد. برای مثال، میتوان از AddressSanitizer (ASan)، UndefinedBehaviorSanitizer (UBSan) یا MemorySanitizer (MSan) استفاده کرد، اما استفاده همزمان از چند سنیتایزر در یک اجرا توصیه نمیشود.
به بیان ساده، هر اجرای فازر (Run) معادل استفاده از یک سنیتایزر است؛ در حالی که کمپین فازینگ (Fuzzing Campaign) مجموعهای از اجراهای مستقل با سنیتایزرهای مختلف است تا انواع گوناگون خطاها و آسیبپذیریها شناسایی شوند. در صورت نیاز، میتوان هارنس (Harness) اختصاصی نیز ایجاد کرد. هارنس قطعه کدی است که ورودیهای تولیدشده توسط فازر را به بخش موردنظر برنامه منتقل میکند. این روش بهویژه برای برنامههایی که مستقیماً از فایل ورودی استفاده نمیکنند و دادهها را از طریق سوکت، شبکه، رابطهای IPC یا سایر منابع دریافت میکنند، موجب افزایش چشمگیر کارایی و اثربخشی فازینگ خواهد شد. در پایان این مرحله، برنامه با تنظیمات انتخابشده کامپایل شده و برای آغاز فرایند فازینگ آماده میشود. مرحله بعد، اجرای فازر (Fuzz Target) است که در آن فازر با پیکربندی مناسب روی برنامه هدف اجرا شده و عملیات تولید و تزریق ورودیهای آزمایشی آغاز میشود.
- انتخاب کامپایلر مناسب: انتخاب کامپایلری که با فازر و قابلیتهای موردنیاز آن سازگار باشد.
- انتخاب گزینههای کامپایل: اعمال گزینههای مناسب بر اساس کامپایلر انتخابشده و نوع کمپین فازینگ.
- انتخاب سنیتایزر (Sanitizer): در هر اجرای فازر تنها یک سنیتایزر باید فعال باشد.
به بیان ساده، هر اجرای فازر (Run) معادل استفاده از یک سنیتایزر است؛ اما یک کمپین فازینگ (Campaign) معمولاً از چندین اجرای مستقل با سنیتایزرهای مختلف تشکیل میشود تا انواع متفاوتی از خطاها و آسیبپذیریها شناسایی شوند.
در برخی پروژهها، بهویژه زمانی که برنامه ورودی خود را از فایل دریافت نمیکند و از منابعی مانند سوکت، شبکه، حافظه اشتراکی یا سایر رابطهای ارتباطی استفاده میکند، لازم است یک هارنس (Harness) اختصاصی ایجاد شود. هارنس، دادههای تولیدشده توسط فازر را به فرمتی قابل استفاده برای برنامه تبدیل کرده و آنها را به بخش موردنظر نرمافزار تزریق میکند. طراحی یک هارنس مناسب میتواند سرعت، پوشش کد و اثربخشی فرایند فازینگ را بهطور قابلتوجهی افزایش دهد.
در پایان این مرحله، سورس برنامه با تنظیمات انتخابشده کامپایل شده و برای اجرای فازر آماده خواهد بود. پس از آمادهسازی برنامه، نوبت به اجرای فازر میرسد. در این مرحله، گزینههای اجرایی فازر متناسب با هدف انتخاب میشوند، کمپین فازینگ آغاز میشود و در صورت نیاز، از تمام هستههای پردازنده یا حتی چندین سیستم بهصورت موازی برای افزایش سرعت و کارایی فرایند فازینگ استفاده خواهد شد.
پس از آمادهسازی برنامه، مرحله اجرای فازر آغاز میشود. در این مرحله، کمپین فازینگ با پیکربندی مناسب اجرا شده و عملکرد آن بر اساس نیاز پروژه تنظیم میشود. مهمترین اقدامات این مرحله عبارتاند از:
- اجرای فازر: راهاندازی
afl-fuzzبا پارامترها و تنظیمات مناسب برای آغاز تولید و تزریق ورودیهای آزمایشی به برنامه هدف. - استفاده از چند هسته پردازنده (Multi-core Fuzzing): ابزار
afl-fuzzامکانات و گزینههایی را برای اجرای همزمان چندین نمونه از فازر روی هستههای مختلف پردازنده فراهم میکند. در صورت عدم استفاده از این قابلیت، فازر تنها روی یک هسته یا یک رشته (Thread) اجرا شده و بخش زیادی از توان پردازشی سیستم بلااستفاده خواهد ماند. - استفاده از چندین ماشین (Distributed Fuzzing): در صورتی که چندین سیستم در اختیار باشد،
afl-fuzzامکان اجرای کمپین فازینگ بهصورت توزیعشده را نیز فراهم میکند. با استفاده از این قابلیت، میتوان از مجموع توان پردازشی تمامی ماشینها برای افزایش سرعت و پوشش فازینگ بهره برد. استفاده از این ویژگی اختیاری است و بیشتر در کمپینهای بزرگ یا زمانهایی که حداکثر بهرهبرداری از منابع سختافزاری مدنظر باشد، کاربرد دارد.
پس از آمادهسازی محیط اجرا، نوبت به آمادهسازی کمپین فازینگ میرسد. در این مرحله، مجموعه ورودیهای اولیه (Seed Corpus) انتخاب و بهینهسازی شده، ورودیهای غیرضروری حذف میشوند و کمپین بهگونهای پیکربندی میشود که فازر بتواند با بیشترین کارایی، مسیرهای جدید برنامه را کشف و آزمایش کند.
مدیریت کمپین:
4.1 اجرا با دریافت ورودی از فایل
afl-fuzz -i in -o out -- ./vuln_afl_scov @@
#include
#include
#include
int main(int argc, char *argv[]) {
if (argc < 2) {
fprintf(stderr, "Usage: %s \n", argv[0]);
return 1;
}
FILE *fp = fopen(argv[1], "rb");
if (!fp) {
perror("fopen");
return 1;
}
char buf[100];
int n = fread(buf, 1, sizeof(buf), fp);
fclose(fp);
if (n < 4) return 0;
if (buf[0]=='F' && buf[1]=='U' && buf[2]=='Z' && buf[3]=='Z') {
// یک کرش مصنوعی برای مثال
*(volatile int*)0 = 0;
}
return 0;
}
afl-fuzz -i in -o out -- ./vuln_afl_scov
#include
#include
int main(void) {
char buf[100];
int n = fread(buf,1,sizeof(buf),stdin);
if (n < 4) return 0;
if (buf[0]=='F' && buf[1]=='U' && buf[2]=='Z' && buf[3]=='Z') {
// یک کرش مصنوعی برای مثال
*(volatile int*)0 = 0;
}
return 0;
}
4.3 بررسی ساختار، فایلها و پوشههای ایجاد شده توسط کاربر و فازر (Inspecting the Structure, Files, and Directories Created by the User and the Fuzzer)
برای آمادهسازی و اجرای یک کمپین فازینگ، لازم است چند مرحله اولیه انجام شود. در سادهترین حالت، ابتدا یک پوشه (Directory) برای پروژه ایجاد کرده و فایل اجرایی یا سورس برنامه هدف و سایر فایلهای موردنیاز را در آن قرار میدهیم.
پس از آمادهسازی محیط، AFL به مجموعهای از ورودیهای اولیه، موسوم به بذر (Seed) یا مجموعه بذرها (Seed Corpus)، نیاز دارد. این ورودیها نقطه شروع فرایند فازینگ هستند و فازر با ایجاد تغییرات (Mutation) روی آنها، ورودیهای جدیدی تولید کرده و مسیرهای مختلف اجرای برنامه را آزمایش میکند.
بهترین حالت آن است که فایلهای بذر (Seed)، ورودیهای معتبر و بدون کرش باشند؛ یعنی برنامه بتواند آنها را با موفقیت پردازش کند. استفاده از چنین ورودیهایی باعث میشود فازر از همان ابتدای اجرا بتواند بخشهای بیشتری از برنامه را پوشش داده و با سرعت بیشتری مسیرهای جدید را کشف کند.
اگرچه اجرای AFL بدون ارائه بذر نیز امکانپذیر است، اما در این حالت فازر باید ورودیهای اولیه را به صورت تصادفی تولید کند. این موضوع معمولاً باعث کاهش سرعت شروع کمپین، افت پوشش کد در مراحل اولیه و کاهش کارایی کلی فرایند فازینگ خواهد شد. به همین دلیل، استفاده از یک مجموعه بذر مناسب و متنوع همواره توصیه میشود.
4.4 بررسی رابط کاربری CLI فازر یا صفحه وضعیت (CLI Interface or Status Screen of the Fuzzer)
شکل زیر نمونه خروجی فازر afl است:
این بخش، نمایی کلی از صفحه وضعیت (Status Screen) ابزار AFL ارائه میدهد و نحوه تفسیر اطلاعات نمایشدادهشده در آن را توضیح میدهد. همچنین، راهنماییهایی برای عیبیابی (Troubleshooting) هشدارها و پیامهایی که به رنگ قرمز در رابط کاربری (UI) نمایش داده میشوند، ارائه خواهد شد.
برای آشنایی با مفاهیم پایه، تنظیمات عمومی و اطلاعات تکمیلی درباره AFL، به فایل README.md مراجعه کنید.
4.4.1 یادداشت درباره رنگها (Note on Colors)
صفحه وضعیت (Status Screen) و پیامهای خطا (Error Messages) در AFL از رنگهای مختلف برای نمایش اطلاعات استفاده میکنند تا خوانایی رابط کاربری افزایش یافته و توجه کاربر به مهمترین پیامها و هشدارها جلب شود. برای مثال، نمایش متن به رنگ قرمز معمولاً نشاندهنده وجود یک هشدار، خطا یا وضعیتی است که نیازمند بررسی کاربر است.
برای نمایش صحیح رنگها، رابط کاربری AFL باید در ترمینالی اجرا شود که از پالت رنگی استاندارد یونیکس (Traditional Unix Color Palette) استفاده میکند؛ بهطور معمول، متن سفید روی زمینه مشکی یا ترکیبی نزدیک به آن. در صورت استفاده از تمهای معکوس (Inverse Video) یا رنگبندیهای سفارشی، ممکن است برخی پیامها بهدرستی نمایش داده نشوند. در صورت نیاز، میتوانید تنظیمات رنگ ترمینال را بهصورت زیر تغییر دهید:
- GNOME Terminal: از مسیر Edit → Profile Preferences → Colors وارد تنظیمات شده و از میان الگوهای آماده، گزینه White on Black را انتخاب کنید.
- macOS Terminal: از مسیر Shell → New Window یک پنجره جدید با پروفایل Pro باز کنید یا این پروفایل را بهعنوان تنظیمات پیشفرض انتخاب نمایید.
اگر ترجیح میدهید از رنگبندی فعلی ترمینال خود استفاده کنید، میتوانید فایل config.h را ویرایش کرده، گزینه USE_COLORS را از حالت کامنت خارج کنید و سپس رنگهای موردنظر خود را تعریف یا رنگبندی پیشفرض را غیرفعال نمایید. در ادامه، بخشهای مختلف صفحه وضعیت AFL و اطلاعاتی که در طول اجرای فازر نمایش داده میشوند را بهصورت جزئی بررسی خواهیم کرد.
4.4.2 نوار وضعیت (status bar)
american fuzzy lop ++3.01a (default) [fast] {0}
خط بالا، علاوه بر نمایش نسخه ++AFL، اطلاعاتی درباره حالت اجرای afl-fuzz نیز ارائه میدهد. این ابزار میتواند در حالتهای مختلفی اجرا شود؛ از جمله حالت عادی با عنوان “American Fuzzy Lop” و حالتهای دیگر مانند حالت کاوش خرابی (“Peruvian Rabbit Mode”).
در کنار شماره نسخه، یک بنر نمایش داده میشود که در صورت عدم تنظیم دستی با گزینه -T، معمولاً شامل نام فایل دودویی در حال فاز شدن است. همچنین در سناریوهای اجرای موازی (Parallel Fuzzing)، از پیشوندهایی مانند M (Master) یا S (Secondary) برای نمایش نقش هر نمونه فازر استفاده میشود.
در ادامه این خط، وضعیت زمانبندی مصرف منابع (Scheduling/Power Schedule) نمایش داده میشود که بهطور پیشفرض روی حالت سریع (fast) تنظیم شده است. در نهایت، آخرین بخش این خط، شناسه پردازنده (CPU ID) را نشان میدهد که مشخص میکند این نمونه از فازر روی کدام هسته در حال اجرا است.
4.4.3 زمانبندی فرآیند (Process timing)
این بخش نشان میدهد که فازر چه مدت در حال اجرا بوده و آخرین فعالیتهای مهم آن چه زمانی رخ دادهاند. اطلاعات نمایشدادهشده معمولاً شامل تعداد مسیرهای جدید (Paths)، خرابیها (Crashes) و هنگها (Hangs) است. مسیرها به ورودیهایی گفته میشود که باعث فعال شدن رفتار یا مسیر اجرایی جدید در برنامه شدهاند.
در خصوص مدتزمان اجرای فازینگ، هیچ قاعده سخت و مشخصی وجود ندارد، اما در عمل بیشتر کمپینهای فازینگ برای دورههای طولانی، از چند روز تا چند هفته اجرا میشوند. برای پروژههای پیچیدهتر، حتی ممکن است اولین چرخه کامل فازینگ یک روز یا بیشتر به طول بیانجامد و در برخی موارد، اجرای مداوم کمپینها برای ماهها ادامه پیدا کند. با این حال، یک نکته مهم وجود دارد: اگر فازر در چند دقیقه ابتدایی اجرای خود هیچ مسیر جدیدی کشف نکند، معمولاً نشاندهنده وجود یک مشکل در تنظیمات است. این مشکل میتواند دلایل مختلفی داشته باشد، از جمله:
- فراخوانی نادرست یا ناقص فایل باینری هدف، که باعث میشود ورودیها بهدرستی پردازش نشوند.
- محدود بودن بیش از حد حافظه تخصیصیافته (Memory Limit)، که موجب کرش سریع برنامه پیش از پردازش ورودیها میشود.
- نامعتبر بودن فایلهای ورودی (Seed)، بهطوری که برنامه در همان مراحل ابتدایی پردازش، به دلیل خطا در فرمت یا هدر فایل، آنها را رد میکند.
در چنین شرایطی، اگر برای مدت طولانی هیچ مسیر جدیدی مشاهده نشود، سیستم در نهایت یک هشدار مهم (معمولاً با رنگ قرمز) در صفحه وضعیت نمایش خواهد داد تا کاربر را از وجود مشکل احتمالی در تنظیمات یا ورودیها مطلع کند.
4.4.4 نتایج کلی (Overall results)
اولین فیلد در این بخش، تعداد دفعاتی را نشان میدهد که فازر تمام موارد تست جالب (Interesting Test Cases) کشفشده را پردازش کرده، آنها را مجدداً فاز نموده و سپس به ابتدای حلقه اجرایی بازگشته است. هر کمپین فازینگ باید حداقل یک چرخه کامل را طی کند و در حالت ایدهآل، این چرخهها برای مدتزمان طولانیتری ادامه پیدا میکنند. همانطور که پیشتر اشاره شد، تکمیل اولین گذر ممکن است یک روز یا حتی بیشتر زمان ببرد.
برای کمک به تصمیمگیری در مورد زمان مناسب توقف فازر (مثلاً با Ctrl+C)، این شمارنده چرخه با رنگهای مختلف کدگذاری میشود. در طول گذر اول به رنگ ارغوانی نمایش داده میشود. اگر در دورهای بعدی همچنان یافتههای جدیدی در حال کشف باشد، رنگ آن به زرد تغییر میکند. پس از تکمیل آن چرخه، رنگ به آبی تغییر مییابد و در نهایت، زمانی که فازر برای مدت طولانی هیچ پیشرفت یا کشف جدیدی نداشته باشد، این شاخص به رنگ سبز نمایش داده میشود.
سایر فیلدهای این بخش نیز نسبتاً گویا هستند: تعداد کل موارد تست (مسیرها) و تعداد خطاها یا خرابیهای منحصربهفردی که تاکنون شناسایی شدهاند نمایش داده میشود. همچنین، این موارد (شامل تستکیسها، کرشها و هنگها) را میتوان بهصورت لحظهای از طریق دایرکتوری خروجی، مطابق توضیحات موجود در فایل README.md، بررسی و تحلیل کرد.
4.4.5 پیشرفت چرخه (Cycle progress)
این بخش نشان میدهد که فازر تا چه میزان از چرخه صف (Queue Cycle) فعلی فاصله دارد. در این قسمت، شناسه مورد آزمایشی (Test Case ID) که در حال حاضر توسط فازر در حال پردازش است نمایش داده میشود، بههمراه تعداد ورودیهایی که به دلیل اتمام زمان پردازش (Timeout) از چرخه حذف شدهاند. همچنین در برخی موارد، پسوند «*» که در خط اول صفحه وضعیت دیده میشود، نشاندهنده این است که مسیر (Path) فعلی جزو مسیرهای «مورد علاقه» (Favored) نیست؛ مفهومی که در بخشهای بعدی بهطور دقیقتر توضیح داده خواهد شد.
4.4.6 پوشش نقشه (Map coverage)
این بخش، اطلاعات جزئیتری درباره میزان پوشش (Coverage) مشاهده شده توسط ابزارگذاری (Instrumentation) تعبیه شده در فایل باینری هدف ارائه میدهد.
در خط اول این کادر، میزان برخورد با تاپلهای شاخهای (Branch Tuples) که در بیتمپ (Bitmap) ثبت شدهاند نمایش داده میشود. عدد سمت چپ نشاندهنده وضعیت فعلی یا ورودی در حال پردازش است و عدد سمت راست بیانگر کل فضای قابل استفاده یا مجموعه ورودیهای ثبتشده تاکنون است.
در تفسیر این مقادیر باید دقت کرد:
- مقادیر مطلق زیر ۲۰۰ یا در همین حدود معمولاً یکی از سه وضعیت را نشان میدهند:
- برنامه بسیار ساده است،
- ابزارگذاری (Instrumentation) بهدرستی انجام نشده است (مثلاً به دلیل استفاده از نسخهای از کتابخانه که ابزارگذاری نشده است)،
- یا ورودیهای تست خیلی سریع باعث کرش یا توقف برنامه میشوند.
در چنین شرایطی، فازر معمولاً این وضعیت را با رنگ صورتی مشخص میکند تا کاربر را از احتمال وجود مشکل آگاه کند.
در مقابل، درصدهای بالاتر از ۷۰٪ معمولاً در برنامههای بسیار پیچیده و بهخصوص نرمافزارهایی که از کد تولیدشده (Generated Code) یا الگوهای پیچیده استفاده میکنند مشاهده میشود. از آنجا که تراکم بالای بیتمپ میتواند تشخیص تغییرات رفتاری جدید را برای فازر دشوارتر کند، توصیه میشود در این حالت برنامه با پارامترهایی مانند AFL_INST_RATIO=10 یا مقادیر بالاتر مجدداً کامپایل شود (مطابق توضیحات موجود در README). این وضعیت معمولاً با رنگ قرمز نمایش داده میشود و در عمل بهندرت در پروژههای معمولی دیده میشود (جز در نرمافزارهای بسیار پیچیده مانند V8، Perl یا FFmpeg).
بخش دیگر این نمایشگر مربوط به تغییرپذیری (Variability) در تعداد برخوردهای تاپل است. اگر هر شاخه در تمام ورودیها تعداد ثابتی برخورد داشته باشد، مقدار این شاخص برابر با 1.00 خواهد بود. با افزایش تنوع در رفتار برنامه و تغییر تعداد برخوردها برای یک شاخه مشخص، این مقدار افزایش یافته و میتواند تا حدود 8.00 (حداکثر حالت تئوری برای بیتمپ ۸ بیتی) نزدیک شود، هرچند رسیدن به این مقدار در عمل بسیار نادر است. در مجموع، این مقادیر برای مقایسه کیفیت پوشش و رفتار اجرایی بین کمپینهای مختلف فازینگ که از یک باینری ابزارگذاریشده استفاده میکنند، بسیار مفید هستند.
4.4.7 پیشرفت مرحلهای (Stage progress)
این بخش نگاهی عمیق به آنچه فازر در حال حاضر انجام میدهد، انداخته است. این بخش در مورد مرحله فعلی به شما میگوید که میتواند هر یک از موارد زیر باشد:
- کالیبراسیون – مرحله پیش فازینگ که در آن مسیر اجرا برای تشخیص ناهنجاریها، تعیین سرعت اجرای پایه و غیره بررسی میشود. هر زمان که یک یافته جدید ایجاد میگردد، بسیار کوتاه اجرا میشود.
- تریم L/S – مرحله پیش فازینگ دیگری که در آن مورد آزمایشی به کوتاهترین شکلی که هنوز همان مسیر اجرا را تولید میکند، تریم میشود. طول (L) و گام به گام (S) به طور کلی با توجه به اندازه فایل انتخاب میشوند.
- بیت فلیپ L/S – بیتفلیپهای قطعی. در هر زمان L بیت وجود دارد که تغییر وضعیت میدهند و فایل ورودی را با افزایشهای S بیتی طی میکنند. انواع فعلی L/S عبارتند از: 1/1، 2/1، 4/1، 8/8، 16/8، 32/8.
- ریاضی L/8 – حسابهای قطعی. فازر سعی میکند اعداد صحیح کوچک را به مقادیر ۸، ۱۶ و ۳۲ بیتی تفریق یا اضافه کند. گام بعدی همیشه ۸ بیت است.
- interest L/8 – بازنویسی قطعی مقدار. فازر لیستی از مقادیر ۸، ۱۶ و ۳۲ بیتی “جالب” شناخته شده برای امتحان دارد. گام بعدی ۸ بیت است.
- extras – تزریق قطعی اصطلاحات دیکشنری. این میتواند به صورت “user” یا “auto” نشان داده شود، بسته به اینکه فازر از یک دیکشنری ارائه شده توسط کاربر (-x) یا یک دیکشنری ایجاد شده خودکار استفاده میکند. همچنین بسته به اینکه آیا کلمات دیکشنری دادههای موجود را بازنویسی میکنند یا با جبران دادههای باقی مانده برای تطبیق با طول آنها درج میشوند، “over” یا “insert” را خواهید دید.
- havoc – نوعی چرخه با طول ثابت با ترفندهای تصادفی انباشته. عملیاتی که در این مرحله انجام میشوند شامل تعویض بیت، بازنویسی با اعداد صحیح تصادفی و «جالب»، حذف بلوک، تکثیر بلوک، به علاوه عملیات مرتبط با دیکشنری متنوع (در صورت ارائه دیکشنری در وهله اول) هستند.
- splice – یک استراتژی به عنوان آخرین راه حل که پس از اولین چرخه کامل صف بدون هیچ مسیر جدیدی شروع میشود. این معادل «havoc» است، با این تفاوت که ابتدا دو ورودی تصادفی از صف را در یک نقطه میانی دلخواه به هم متصل میکند.
- sync – مرحلهای که فقط زمانی استفاده میشود که M- یا S- تنظیم شده باشد (به md مراجعه کنید). هیچ فازینگ واقعی در کار نیست، اما ابزار خروجی سایر فازرها را اسکن میکند و در صورت لزوم موارد آزمایشی را وارد میکند. اولین باری که این کار انجام میشود، ممکن است چند دقیقه یا بیشتر طول بکشد.
فیلدهای باقیمانده در این بخش نیز نسبتاً شفاف هستند. این قسمت شامل شاخصهایی مانند تعداد اجرای انجامشده در مرحله فعلی (execs for current stage)، شمارنده کلی اجراها (total execs)، و معیار سرعت اجرای فازر برای برنامه هدف است. این مقادیر ممکن است بسته به نوع ورودیها و رفتار برنامه تغییر کنند، اما در حالت ایدهآل، سرعت اجرا باید در بیشتر موارد بالاتر از ۵۰۰ اجرای در ثانیه (execs/sec) باشد. اگر این مقدار بهطور مداوم زیر ۱۰۰ execs/sec باقی بماند، معمولاً نشاندهنده کند بودن فرایند فازینگ و طولانی شدن غیرعادی زمان اجرا خواهد بود.
در چنین شرایطی، فازر بهصورت مستقیم درباره «اهداف کند» (Slow Targets) هشدار میدهد. در صورت مشاهده این هشدار، میتوان برای بهینهسازی عملکرد و افزایش سرعت اجرا به فایل
perf_tips.mdکه همراه فازر ارائه شده است مراجعه کرد؛ این فایل شامل پیشنهادهایی برای بهبود کارایی و کاهش زمان اجرای کمپین فازینگ است.
4.4.8 یافتهها در عمق (Findings in depth)
این بخش مجموعهای از معیارهای پیشرفتهتر را ارائه میدهد که بیشتر برای تحلیلگران و توسعهدهندگان حرفهای جذاب هستند. در این قسمت، تعداد مسیرهایی نمایش داده میشود که فازر بر اساس الگوریتم کمینهسازی (Minimization) داخلی خود، آنها را «مطلوبتر» یا مهمتر تشخیص داده است؛ این مسیرها معمولاً سهم بیشتری در فرآیند فازینگ دارند و در ادامه اجرای کمپین، وزن بالاتری در تولید ورودیهای جدید خواهند داشت.
همچنین تعداد موارد آزمایشی (Test Cases) که واقعاً منجر به افزایش پوشش لبهای (Edge Coverage) شدهاند نیز گزارش میشود؛ این معیار نسبت به افزایش صرف شمارندههای برخورد (Hit Counters) اهمیت بیشتری دارد، زیرا نشاندهنده کشف رفتار جدید در برنامه است، نه صرفاً تکرار مسیرهای قبلی.
در کنار این موارد، شمارندههای دقیقتری برای خرابیها (Crashes) و وقفهها (Time-outs) نیز وجود دارد. لازم به ذکر است که شمارنده مربوط به وقفهها با شمارنده «هنگ» (Hangs) تفاوت دارد. این مقدار شامل تمام موارد آزمونی (Test Case) است که از آستانه زمانی مجاز فراتر رفتهاند، حتی اگر به اندازه کافی طولانی نبوده باشند که به عنوان هنگ واقعی طبقهبندی شوند. به عبارت دیگر، این شاخص دامنه گستردهتری از رفتارهای کند یا غیرپاسخگو را نسبت به شمارنده هنگ پوشش میدهد.
4.4.9 بازده راهبرد فازینگ (fuzzing strategy yields)
این بخش، نمایی تکمیلی از عملکرد فازر ارائه میدهد و بهطور خاص تعداد مسیرهای کشفشده را در مقایسه با تعداد اجراها برای هر یک از استراتژیهای فازینگ که پیشتر معرفی شدهاند، نشان میدهد. این آمار بهخوبی نشان میدهد که رویکردهای مختلف مورد استفاده در afl-fuzz تا چه حد در بهبود پوشش و کشف مسیرهای جدید مؤثر بودهاند.
در این میان، آمار مربوط به استراتژی Trim اندکی متفاوت از سایر بخشها نمایش داده میشود. عدد اول در این خط، میزان بایتهای حذفشده از فایلهای بذر (Seedها) را نشان میدهد. عدد دوم بیانگر تعداد اجراهای لازم برای رسیدن به این میزان بهینهسازی است. در نهایت، عدد سوم نشاندهنده بخشی از بایتهایی است که امکان حذف آنها وجود ندارد، اما در عمل تأثیر معناداری بر رفتار برنامه ندارند و در مراحل مختلف فازینگ، بهصورت غیرقطعی و پرهزینه حذف شدهاند.
همچنین توجه داشته باشید که در صورت غیرفعال بودن حالت جهش قطعی (Deterministic Mutation) — که بهطور پیشفرض نیز غیرفعال است، زیرا کارایی کمتری دارد — پنج خط ابتدایی این بخش با عبارت «غیرفعال (پیشفرض، فعال با -D)» نمایش داده میشوند. در این حالت، تنها استراتژیهایی که فعال هستند در قالب شمارندههای مربوطه گزارش خواهند شد.
4.4.10 هندسه مسیر (path geometry)
در این بخش، نخستین فیلد میزان عمق مسیر (Path Depth) را در فرایند فازینگ هدایتشده نمایش میدهد. در این مدل، ورودیهای اولیهای که کاربر ارائه میکند بهعنوان سطح ۱ در نظر گرفته میشوند. ورودیهایی که از طریق فازینگ مستقیم از آنها تولید میشوند، در سطح ۲ قرار میگیرند و ورودیهایی که از این نتایج در دورهای بعدی فازینگ استخراج میشوند، بهعنوان سطح ۳ و به همین ترتیب ادامه مییابند. بنابراین، بیشترین عمق ثبتشده میتواند بهعنوان یک شاخص تقریبی از میزان بهرهبرداری موفق از رویکرد فازینگ هدایتشده در afl-fuzz تلقی شود.
فیلد بعدی تعداد ورودیهایی را نشان میدهد که هنوز در صف فازینگ قرار دارند اما پردازش نشدهاند. همین آمار برای ورودیهای «مورد علاقه» (Favored Inputs) نیز ارائه میشود؛ این ورودیها اولویت بالاتری دارند و فازر تمایل دارد در چرخه جاری صف، زودتر به آنها برسد، در حالی که ورودیهای غیرمورد علاقه ممکن است برای مدت بیشتری در صف باقی بمانند.
در ادامه، تعداد مسیرهای جدیدی گزارش میشود که در طول این بخش از کمپین فازینگ کشف شدهاند و از نمونههای تولیدشده توسط سایر نمونههای فازر در حالت فازینگ موازی (Parallel Fuzzing) وارد شدهاند. همچنین میزان تغییرپذیری رفتار برنامه برای ورودیهای یکسان نیز بررسی میشود؛ یعنی اینکه آیا یک ورودی ثابت همواره منجر به یک رفتار یکسان میشود یا خیر.
آخرین معیار این بخش مربوط به پایداری ردپاها (Trace Stability) است. اگر یک برنامه برای ورودیهای یکسان همواره رفتار یکسانی از خود نشان دهد، امتیاز آن ۱۰۰٪ خواهد بود. در صورتی که این مقدار کمتر باشد اما همچنان در محدوده قابل قبول (معمولاً با رنگ بنفش) نمایش داده شود، معمولاً تأثیر منفی جدی بر فرایند فازینگ ندارد. با این حال، اگر مقدار پایداری به محدوده قرمز برسد، ممکن است نشاندهنده وجود مشکلاتی در تشخیص صحیح تغییرات رفتاری باشد، زیرا AFL در این شرایط قادر به تفکیک تغییرات واقعی از نویزهای تصادفی نیست.
بهطور کلی، اکثر اهداف پایدار امتیاز ۱۰۰٪ دارند، اما در صورت مشاهده مقادیر پایینتر، باید به چند سناریوی احتمالی توجه کرد:
- استفاده از حافظه مقداردهینشده در کنار منابع ذاتی آنتروپی در برنامه هدف. این حالت معمولاً برای AFL بیضرر است، اما میتواند نشاندهنده وجود آسیبپذیری امنیتی باشد.
- دستکاری منابع پایدار مانند فایلهای موقت، اشیای حافظه مشترک (Shared Memory) یا وضعیتهای باقیمانده. همچنین کمبود فضای دیسک، محدودیت در هندلهای SHM یا سایر منابع سیستم نیز میتواند چنین رفتاری ایجاد کند.
- استفاده از قابلیتهایی که ذاتاً رفتار غیرقطعی دارند، مانند توابع تصادفی. برای مثال در پایگاه داده SQLite، اجرای عباراتی مانند
select random();میتواند مسیرهای اجرایی متغیری تولید کند که لزوماً نشانه خطا نیست. - اجرای همزمان چندین thread با ترتیبهای نیمهتصادفی. این حالت معمولاً زمانی بیضرر است که شاخص پایداری بالای ۹۰٪ باقی بماند، اما در غیر این صورت میتواند مشکلساز شود. در چنین شرایطی میتوان اقداماتی مانند استفاده از
afl-clang-fastبرای instrumentation، غیرفعالسازی threadها در زمان کامپایل (مانندwithout-threadsیاdisable-pthreads) یا جایگزینی pthreads با GNU Pth را بررسی کرد.
در حالت persistent mode نیز کاهشهای جزئی در پایداری طبیعی است، زیرا برخی بخشهای برنامه در هر تکرار رفتار یکسانی ندارند. اما افتهای شدید میتواند نشاندهنده این باشد که حلقه AFL_LOOP بهدرستی پیادهسازی نشده و وضعیت برنامه بین اجراها بهدرستی ریست نمیشود، که در نتیجه بخشی از تلاش فازینگ هدر میرود.
در نهایت، مسیرهایی که رفتار غیرپایدار یا متغیر در آنها شناسایی میشود، در مسیر زیر علامتگذاری و قابل پیگیری هستند:
out_dir/queue/.state/variable_behaviorاین امکان به کاربر کمک میکند تا بهراحتی ورودیهای دارای رفتار غیرقطعی را شناسایی و بررسی کند.
4.4.11 بار پردازنده (CPU load)
این ابزارک کوچک، میزان بهرهگیری ظاهری از پردازنده (Apparent CPU Utilization) را در سیستم نمایش میدهد. این مقدار با شمارش تعداد فرایندهای در وضعیت قابل اجرا (Runnable State) و مقایسه آن با تعداد هستههای منطقی (Logical Cores) موجود در سیستم محاسبه میشود. اگر این مقدار با رنگ سبز نمایش داده شود، به این معناست که فازر در حال حاضر از تمام ظرفیت پردازنده استفاده نمیکند و بخشی از منابع پردازشی بلااستفاده مانده است. در چنین شرایطی، معمولاً میتوان با افزایش موازیسازی (Parallelization) یا اجرای چند نمونه همزمان از فازر، کارایی کلی را بهبود داد و سرعت کشف مسیرهای جدید را افزایش داد.
اگر مقدار با رنگ قرمز نمایش داده شود، احتمالاً نشاندهنده این است که پردازنده در حالت بیشبارگذاری (Oversubscription) قرار دارد و اجرای نمونههای بیشتر از فازر لزوماً به بهبود عملکرد منجر نخواهد شد.
با این حال، این معیار بسیار سادهسازیشده است و صرفاً تعداد فرایندهای آماده اجرا را نشان میدهد؛ در حالی که مشخص نمیکند هر فرایند تا چه حد منابعبر (Resource-intensive) است یا چه میزان فشار واقعی بر سیستم وارد میکند. علاوه بر این، این سنجش تفاوتی بین هستههای فیزیکی (Physical Cores)، هستههای منطقی (Logical Cores) و پردازندههای مجازی (Virtual CPUs) قائل نمیشود؛ در حالی که عملکرد و کارایی هر یک میتواند تفاوت قابلتوجهی داشته باشد.
برای ارزیابی دقیقتر و واقعیتر وضعیت مصرف CPU، میتوان از ابزار خط فرمان afl-gotcpu استفاده کرد که تصویر دقیقتری از ظرفیت واقعی پردازنده در اختیار میگذارد.
4.4.12 افزوده پرونده های وضعیت و نمودار (Addendum: status and plot files)
برای اجرای بدون نظارت (Unattended Execution)، برخی از دادههای کلیدی صفحه وضعیت (Status Screen) بهصورت قابلخواندن برای ماشین (Machine-readable format) در فایل fuzzer_stats داخل پوشه خروجی (output directory) در دسترس قرار میگیرد. این فایل شامل مجموعهای از آمار و شاخصهای مهم مربوط به وضعیت فعلی کمپین فازینگ است، از جمله:
- زمان آغاز (start_time) – زمان یونیکس آغاز afl-fuzz.
- زمان آخرین به روزرسانی (last_update) – زمان یونیکس آخرین به روز شدن این پرونده.
- زمان اجرا (run_time) – زمان اجرا بر حسب ثانیه تا آخرین به روزرسانی.
- شناسه فرآیند فازر (fuzzer_pid) – PID فرآیند فازر.
- چرخه های انجام شده (cycles_done) – شمار چرخه های صف که تا کنون کامل شدهاند.
- چرخه های بدون کشف (cycles_wo_finds) – شمار چرخه هایی که بدون یافتن مسیر جدید بودهاند.
- اجرای انجام شده (execs_done) – شمار فراخوانی های execve که اجرا شدهاند.
- اجرای در ثانیه (execs_per_sec) – شمار کلی اجراها در هر ثانیه.
- همه مسیرها (paths_total) – شمار کل ورودیهای صف.
- مسیرهای برگزیده (paths_favored) – شمار ورودیهای برگزیده صف.
- مسیرهای یافته شده (paths_found) – شمار ورودی هایی که با فازینگ بومی پیدا شدهاند.
- مسیرهای وارد شده (paths_imported) – شمار ورودیهایی که از نمونه های دیگر وارد شدهاند.
- بیشینه ژرفا (max_depth) – شمار لایه ها در داده های ساخته شده.
- مسیر کنونی (cur_path) – شماره ورودی که اکنون پردازش میشود.
- برگزیده های در انتظار (pending_favs) – شمار ورودی های برگزیده که هنوز فاز نشدهاند.
- همه در انتظار (pending_total) – شمار همه ورودی هایی که در انتظار فاز شدن هستند.
- مسیرهای ناپایدار (variable_paths) – شمار موردهای آزمایشی با رفتار دگرگون شونده.
- پایداری (stability) – درصد بایت های نگاشت که رفتار پایدار دارند.
- پوشش نگاشت (bitmap_cvg) – درصد پوشش یال که تا کنون در نگاشت پیدا شدهاند.
- کرش یکتا (unique_crashes) – شمار کرشهای یکتای ثبت شده.
- هنگ یکتا (unique_hangs) – شمار هنگ های یکتای مشاهده شده.
- آخرین مسیر (last_path) – ثانیه های گذشته از یافتن آخرین مسیر.
- آخرین کرش (last_crash) – ثانیه های گذشته از یافتن آخرین کرش.
- آخرین هنگ (last_hang) – ثانیه های گذشته از یافتن آخرین هنگ.
- اجرای پس از کرش (execs_since_crash) – شمار اجراها از آخرین کرش.
- زمان پایان اجرا (exec_timeout) – مقدار گزینه t- در خط فرمان.
- کندترین اجرا (slowest_exec_ms) – زمان واقعی کندترین اجرا بر حسب میلی ثانیه.
- اوج حافظه (peak_rss_mb) – بیشینه مصرف rss در هنگام فازینگ بر حسب مگابایت.
- یال های یافته شده (edges_found) – شمار یالهایی که پیدا شدهاند.
- بایتهای ناپایا (var_byte_count) – شمار یالهایی که دقیق نیستند.
- نشان afl (afl_banner) – نوشته نشان مانند نام هدف.
- نسخه afl (afl_version) – نسخه AFL به کار رفته
- حالت هدف (target_mode) – پیش گزیده پایدار qemu unicorn بدون ابزارگذاری.
- خط فرمان (command_line) – خط فرمان کامل نشست فازینگ.
اگر مقدار این شاخص با رنگ قرمز نمایش داده شود، معمولاً نشاندهنده این است که پردازنده در وضعیت بیشازحد بارگذاریشده (Oversubscribed) قرار دارد و افزودن نمونههای فازر بیشتر احتمالاً باعث بهبود عملکرد نخواهد شد. با این حال، این معیار بسیار سادهسازیشده است و صرفاً تعداد فرایندهای آماده اجرا را نشان میدهد؛ در حالی که هیچ اطلاعاتی درباره میزان مصرف واقعی منابع توسط هر فرایند ارائه نمیکند. همچنین، این سنجش بین هستههای فیزیکی (Physical Cores)، هستههای منطقی (Logical Cores) و پردازندههای مجازی (Virtual CPUs) تفاوتی قائل نمیشود، در حالی که هر یک از این حالتها ویژگیهای عملکردی متفاوتی دارند. برای اندازهگیری دقیقتر وضعیت استفاده از CPU، میتوان از ابزار خط فرمان afl-gotcpu استفاده کرد که تصویر واقعیتری از ظرفیت پردازشی در دسترس ارائه میدهد.
4.4.12 افزوده: ارسال خودکار سنجه ها با StatsD(Addendum: Automatically send metrics with StatsD)
در محیطهای ادغام مداوم (CI) یا هنگام اجرای همزمان چند نمونه فازر، بررسی دستی آمار فازینگ (Fuzzer Statistics) یا اجرای اسکریپتهای جداگانه برای جمعآوری این دادهها میتواند زمانبر و خستهکننده باشد. برای حل این مشکل، میتوان از قابلیت ارسال خودکار سنجهها (Metrics) به سیستم مانیتورینگ استفاده کرد.
با استفاده از متغیر محیطی AFL_STATSD و متغیرهای مرتبط مانند AFL_STATSD_HOST، AFL_STATSD_PORT و AFL_STATSD_TAGS_FLAVOR، امکان ارسال خودکار آمار فازر به یک سرور StatsD فراهم میشود. این دادهها را میتوان برای مانیتورینگ، فعالسازی هشدارها (Alerts) یا حتی اجرای عملیات خودکار بر اساس وضعیت کمپین فازینگ مورد استفاده قرار داد.
سنجههای ارسالشده تنها بخشی از تمام دادههای موجود در صفحه وضعیت (Status Screen) و فایل نمودار (Plot Data) هستند. مهمترین این سنجهها عبارتاند از:
- چرخههای انجامشده (cycle_done)
- چرخههای بدون کشف (cycles_wo_finds)
- تعداد اجرای انجامشده (execs_done)
- نرخ اجرای در ثانیه (execs_per_sec)
- کل مسیرها (paths_total)
- مسیرهای مورد علاقه (paths_favored)
- مسیرهای کشفشده (paths_found)
- مسیرهای واردشده (paths_imported)
- بیشینه عمق (max_depth)
- مسیر فعلی (cur_path)
- ورودیهای در انتظار بررسی (pending_favs / pending_total)
- مسیرهای دارای رفتار متغیر (variable_paths)
- تعداد کرشهای یکتا (unique_crashes)
- تعداد هنگهای یکتا (unique_hangs)
- کل کرشها (total_crashes)
- کندترین اجرای ثبتشده (slowest_exec_ms)
- تعداد یالهای کشفشده (edges_found)
- تعداد بایتهای متغیر (var_byte_count)
- میزان گسترش تصادفی (havoc_expansion)
در سناریوهایی که چند نمونه فازر بهصورت همزمان به StatsD متصل هستند، استفاده صحیح از تنظیمات AFL_STATSD_TAGS_FLAVOR اهمیت ویژهای دارد. هماهنگ بودن این قالب برچسبگذاری با سرور StatsD امکان تفکیک دقیق دادهها را فراهم میکند؛ به این ترتیب میتوان عملکرد هر نمونه فازر را بهصورت جداگانه پایش کرد، فازرهای دارای عملکرد ضعیف را شناسایی نمود و پیشرفت هر استراتژی فازینگ را بهطور مستقل تحلیل کرد.
5. آشنایی با لیست دستورات پر کاربرد ++AFL
5.1 afl-addseeds
ابزار afl-addseeds یکی از ابزارهای جانبی ++AFL است که برای افزودن مجموعه بذرهای جدید (Seed Corpus) به یک کمپین فازینگ در حال اجرا به کار میرود. بذرها در واقع فایلهای ورودی اولیهای هستند که فازر بر اساس آنها شروع به تولید ورودیهای جدید، کشف مسیرهای اجرایی تازه و شناسایی خطاها و کرشها (Crash) در برنامه هدف میکند.
با استفاده از afl-addseeds میتوان مجموعه ورودیهای اولیه را در حین اجرای کمپین بهروزرسانی یا گسترش داد، بدون اینکه نیاز به توقف کامل فرایند فازینگ وجود داشته باشد. این قابلیت به بهبود پوشش کد (Code Coverage) و افزایش احتمال کشف مسیرها و آسیبپذیریهای جدید کمک میکند.
afl-addseeds -o [-i ]
توضیح | پارامتر |
مسیر پوشه خروجی کمپین fuzzing که AFL در آن اجرا میشود | -o afl-out-dir |
فایل یا دایرکتوری seed برای اضافه کردن (اختیاری) | -i seed_file_or_dir |
لیست فایلها یا دایرکتوریهای seed که باید اضافه شوند | seed_file_or_seed_dir … |
مثال عملی:
فرض کنید یک کمپین فازینگ (fuzzing) روی برنامه هدف دارید و خروجی AFL در پوشه findings ذخیره میشود:
1. اضافه کردن یک فایل بذر (seed) جدید:
afl-addseeds -o findings -i new_seed.txt new_seed.txt
2. اضافه کردن یک دایرکتوری کامل بذر (seed):
afl-addseeds -o findings -i additional_seeds/ additional_seeds/
- ++AFL بعد از اجرای این دستور، بذرهای جدید را به صورت امن وارد پوشه queue میکند.
- AFL در ادامه فازینگ، این بذرها را به عنوان ورودی اولیه استفاده میکند و ممکن است مسیرهای جدید یا کرشهای جدید کشف شود.
5.2 afl-persistent-config
همانطور که در لینک راهنمای زیر مشاهده میکنید نکاتی جهت بهینهسازی عملکرد[1] فازینگ ذکر شده است که اسکریپت فوق مربوط به هشتمین نکته ذکر شده که مربوط بهینه سازی تظیمات سیستم عامل می باشد را روی سیستم به صورت دائمی اعمال میکند. پس از اجرا نیازمند بوت مجدد (Reboot) است. جهت اطلاعات بیشتر حتما لینک زیر را مطالعه کنید. به عبارت دیگر، این ابزار تنظیمات کرنل و بوت لینوکس را تغییر میدهد تا سرعت فازینگ بالاتر برود. کاری که انجام میدهد:
- ایجاد فایل پیکربندی در sysctl
/etc/sysctl.d/99-fuzzing.conf
برای اعمال برخی تنظیمات کرنل با هدف افزایش سرعت اجرای تستها، لازم است تغییراتی در تنظیمات بوت سیستم انجام شود. این تغییرات معمولاً از طریق تنظیمات GRUB اعمال میشوند.
- تنظیم گزینههای بوت در GRUB
- غیرفعال کردن mitigations مربوط به CPU (IBPB, IBRS, KPTI, Spectre, Meltdown و …).
- فعال یا غیرفعال کردن TSX، NoExec و سایر گزینههای بهینهسازی.
- پیغام هشدار امنیتی
- بعد از اعمال تنظیمات، سیستم کمتر امن میشود.
- توصیه میشود فقط SSH فعال باشد و فایروال قوی تنظیم شود.
- نیاز به root
- بدون دسترسی روت، تغییرات سیستمی قابل انجام نیست.
- ریبوت پس از اعمال تغییرات
- تا تغییرات کرنل و بوت اعمال شوند.
5.3 afl-plot
این ابزار یک ابزار کمکی برای فریمورک AFL (American Fuzzy Lop) است که برای تحلیل، پردازش و نمایش دادههای حاصل از فرایند فازینگ طراحی شده است. این ابزار، دادههای تولیدشده توسط AFL را دریافت کرده و آنها را به شکل نمودارهای بصری و گزارشهای HTML ارائه میدهد تا روند فازینگ بهصورت شفاف و قابلتحلیل در دسترس قرار گیرد.
کاربردها:
- پایش و رصد فازینگ: نمایش تعداد تستهای اجراشده، مسیرهای جدید کشفشده و کرشهای شناساییشده در طول کمپین.
- تحلیل پوشش کد (Code Coverage): کمک به شناسایی بخشهایی از برنامه که مورد تست قرار گرفتهاند و بخشهایی که هنوز پوشش داده نشدهاند.
- ارزیابی سرعت و کارایی فازینگ: نمایش نرخ اجرای تستها (execs/sec) و شناسایی گلوگاههای احتمالی در عملکرد فازر یا برنامه هدف.
- تولید گزارشهای گرافیکی: امکان تولید خروجیهای تصویری (مانند PNG) و صفحات HTML برای مستندسازی، تحلیل روند و ارائه نتایج فازینگ.
5.4 afl-plot-ui
این دستور برای تجسم (Visualizing) خروجی ابزار afl-plot استفاده میشود. در واقع، این ابزار صرفاً دادههای تولیدشده توسط فازر را دریافت کرده و آنها را در قالب چهار پنجره گرافیکی نمایش میدهد تا امکان بررسی بصری روند فازینگ فراهم شود. اجرای این ابزار معمولاً نیازمند محیط گرافیکی سازگار با Wayland یا سیستمهای مبتنی بر X11/GTK است. از آنجا که این ابزار بهصورت پیشفرض—even با اجرای make distrib—روی سیستم نصب نمیشود، لازم است بهصورت دستی نصب یا کامپایل شود. روش نصب معمول به شکل زیر انجام میگیرد:
5.4.1 نصب و راه اندازی afl-plot-ui
sudo apt install libgtk-3-0 libgtk-3-dev pkg-config
cd utils/plot_ui
make cd ../
sudo make install
5.4.2 نحوه استفاده (How to Use)
نکته مهم این است که ابزار afl-plot-ui نباید بهصورت مستقیم اجرا شود. این برنامه یک زیرابزار (Sub-tool) است که فقط در زمان نیاز و توسط ابزار اصلی afl-plot برای تولید نمودارهای مبتنی بر GNUplot فراخوانی میشود. در صورت اجرای مستقیم afl-plot-ui با مشکلات زیر مواجه خواهید شد:
- اولاً مسیر ورودی به درستی مدیریت نمیشود.
- دوماً روی سیستمهای جدید (Wayland/Ubuntu 24) ممکن است با خطای GTK/X11 و Segmentation fault مواجه شوید.
برای اجرای صحیح این ابزار، کافی است از دستور afl-plot به همراه سوئیچ گرافیکی -g یا --graphical استفاده کنید:
afl-plot -g /home/test/aflplusplus_testcases/helloworld/out/default ./report
پارامتر اول مشخص میکند که خروجی باید بهصورت گرافیکی و در قالب چهار پنجره مجزا توسط برنامه نمایش داده شود. همانطور که در تصویر زیر نیز مشاهده میشود، این حالت باعث میشود دادههای فازینگ به شکل تفکیک شده و قابلبررسی در چند نمای مختلف ارائه شوند.
پارامتر دوم مربوط به مسیر خروجی فازر (Fuzzer Output Directory) است؛ جایی که دادههای مورد نیاز برای تولید نمودار (plot data) ساخته و ذخیره میشوند. پارامتر سوم نیز به پوشهای اشاره دارد که خروجی نهایی ابزار در آن ذخیره خواهد شد. این خروجی معمولاً شامل تصاویر، جداول و فایل HTML گزارش است. توجه داشته باشید که ساختار خروجی وب (Web Output) در این حالت با خروجی تولیدشده توسط دستور afl-plot یکسان است و تفاوتی در محتوای نهایی گزارش وجود ندارد.
۵.۵ تفاوت مهم در ارتباط با ورودی فازر ++AFL از طریق stdin و یا file
در فازر ++AFL میتوان به دو روش اصلی به برنامه هدف ورودی داد: یا از طریق stdin و یا بهصورت آرگومان فایل. در حالت stdin، نیازی به مشخصکردن فایل ورودی نیست و دادهها از طریق pipe مستقیماً در حافظه به برنامه ارسال میشوند. در روش دوم، ورودی بهصورت یک فایل در قالب آرگومان خط فرمان به برنامه داده میشود. برای فعالسازی این حالت در AFL، کافی است از نماد @@در انتهای دستور afl-fuzz استفاده شود؛ این نماد در زمان اجرا با مسیر فایل ورودی جایگزین میشود.
در سیستمعامل لینوکس، محدودیت مشخصی برای ورودی stdin تعریف نشده است و محدودیت عملی بیشتر به منابع سیستم مانند RAM، فضای swap یا تنظیمات خود فازر وابسته است. بهطور مشابه، برای ورودی فایل نیز در اغلب توزیعها محدودیت سختگیرانهای وجود ندارد. با این حال، در AFL بهصورت پیشفرض یک سقف حدود ۱ مگابایت برای اندازه ورودی در نظر گرفته شده است (که در کد منبع فازر نیز قابل مشاهده است). این محدودیت برای هر دو نوع ورودی (stdin و فایل) اعمال میشود تا از پردازش ورودیهای بیشازحد بزرگ جلوگیری شود؛ زیرا چنین ورودیهایی میتوانند باعث کاهش شدید کارایی، کندی فازینگ یا حتی ایجاد شرایط شبیه به حملات DoS شوند.
بنابراین باید در نظر داشت که حداکثر اندازه بافر ورودی بهصورت پیشفرض حدود ۱ مگابایت است. این مقدار قابل تغییر بوده و توسعهدهنده میتواند آن را از طریق فلگهای مربوطه تنظیم کند. معمولاً بازه پیشنهادی برای این مقدار بین ۱ مگابایت تا حداکثر ۱۰۰ مگابایت در نظر گرفته میشود.
export AFL_MAX_FILE=5000000 # مثلاً 5 مگابایت
afl-fuzz -i inputs -o outputs -- ./target
یا در یک خط به صورت زیر:
env AFL_MAX_FILE=5000000 afl-fuzz -i inputs -o outputs -- ./target
در صورتی که برای اندازه فایل محدودیتی اعمال شده باشد، میتوان آن را با تنظیمات مناسب تغییر داد. با استفاده از دستور زیر میتوانید محدودیت فعلی مربوط به اندازه فایلهای خروجی را در توزیع لینوکس خود مشاهده کنید. بهطور کلی، در اکثر توزیعهای لینوکسی مانند Ubuntu، Debian و Fedora و سایر سیستمهای مشابه، محدودیت پیشفرض برای اندازه فایلهای خروجی وجود ندارد و این مقدار معمولاً بهصورت نامحدود (unlimited) تنظیم شده است.
در تصویر فوق خروجی دستور مذکور در توزیع اوبونتو 24 را مشاهده می کنید.
در نظر داشته باشید که مقدار PIPE برابر با 512 در نظر گرفته شده است. این مقدار به این معناست که اندازه pipe buffer در کرنل، در حالت پایه، برابر با 512 bytes تعریف میشود. با توجه به اینکه در بسیاری از سیستمها این مقدار در عمل به صورت ضریبی از 8 محاسبه میشود، مقدار نهایی آن برابر با 4096 بایت (4KB) خواهد بود.
نکته مهم این است که این مقدار به معنای محدودیت کلی در حجم داده نیست، بلکه تنها مربوط به اندازه بافر انتقال داده در کرنل است. در واقع دادهها در قالب chunkهایی با این اندازه به برنامه هدف ارسال میشوند و بهصورت مرحلهای (streaming) پردازش میگردند. بنابراین برخلاف تصور رایج، PIPE محدودیت کلی روی حجم ورودی اعمال نمیکند و صرفاً نحوه انتقال داده بین فازر و برنامه هدف را در سطح سیستمعامل کنترل میکند. در ادامه، دو مثال ساده برای نحوه اجرای فازر ارائه میشود:
1. دستور اجرا با ورودی STDIN
afl-fuzz -i in -o out -- ./vuln_afl_scov
2. دستور اجرا با ورودی File
afl-fuzz -i in -o out -- ./vuln_afl_scov @@
6. آشنایی با اصطلاحات فازینگ (Introduction to Fuzzing Terminology)
6.1 اصطلاحات عمومی (General Terminology)
- Seed: بذر یا ورودی اولیه یا ورودی ابتدایی برای شروع فازر؛ نقطه شروع برای تولید ورودیهای جدید.
- Inputs/Outputs: دادههایی که به برنامه فرستاده میشوند و پاسخهایی که دریافت میکنیم.
- Code Coverage: میزان پوشش کد توسط فازینگ، نشاندهنده بخشهایی از کد که تست شدهاند.
- Corpus: مجموعهای از ورودیها (inputs) یا دادهها که فازر از آنها برای تولید تستهای جدید استفاده میکند. این مجموعه ورودیها شامل بذر و ورودیهای تولید شده است؛ خوراک فازر برای تست برنامه. این ورودیها میتوانند بذر یا ورودیهای اولیه (seed inputs) هم باشند، یعنی نقاط شروع برای mutation یا generation.
- Minimize Corpus: کاهش تعداد ورودیها به حداقل لازم بدون از دست دادن پوشش کد یا باگها.
- Unique Corpus: مجموعهای از ورودیها که رفتار منحصربهفرد یا کرشهای متفاوت ایجاد میکنند.
- Fuzz Target: بخش مشخصی از برنامه که قرار است با فازینگ تست شود.
- Fuzz Test: اجرای فازینگ روی برنامه با ورودیهای تولید شده توسط فازر.
- Triage: فرایند تحلیل و دستهبندی کرشها و باگهای پیدا شده توسط فازر.
- Job Type/Fuzzer Build: نوع وظیفه فازر و تنظیمات ساخت (build) آن برای معماریها و سنیتایزرهای مختلف.
- Harness: کد یا تابعی که برنامه را برای فازینگ آماده میکند و ورودیها را به هدف فازینگ (fuzz target) میدهد.
- Architectures: معماریهای سختافزاری یا نرمافزاری که فازینگ روی آنها اجرا میشود (x86, ARM, MIPS و غیره).
- Sanitizer: ابزارهای تشخیص خطا.
- ASan (AddressSanitizer): تشخیص خطاهای حافظه مانند سرریز (overflow) و استفاده مجدد پس از آزادسازی (use-after-free).
- CFI (Control Flow Integrity Sanitizer): بررسی انحراف در جریان کنترل برنامه و حملات تغییر جریان کد.
- LSan (LeakSanitizer): تشخیص نشت حافظه.
- MSan (MemorySanitizer): تشخیص دسترسی به حافظه استفاده نشده (uninitialized memory).
- TSan (ThreadSanitizer): تشخیص شرایط رقابتی (race condition) و مشکلات همزمانی.
- UBSan (UndefinedBehaviorSanitizer): تشخیص رفتارهای نامشخص و خطرناک در برنامه.
- Mutation: تغییر خودکار ورودیها برای ایجاد ورودیهای جدید.
- Feedback-driven Fuzzing: فازینگ مبتنی بر بازخورد، که از اطلاعات پوشش کد برای تولید ورودیهای مؤثر استفاده میکند.
- Crash Bucketing: گروهبندی کرشها بر اساس نوع و محل وقوع.
- Sanitizer Coverage: پوشش کد که در حضور ابزارهای سنیتایزر (sanitizer) جمعآوری میشود.
- Persistent Mode: حالت اجرای مداوم فازینگ بدون بارگذاری مجدد برنامه برای افزایش سرعت.
- Dictionary: مجموعهای از رشتهها یا الگوهای مفید برای تولید ورودیهای معنادار.
- Edge Coverage: پوشش مسیرهای بین بلوکهای کد (edges) در برنامه.
- Fuzzing Campaign: اجرای طولانی مدت فازینگ روی برنامه برای کشف باگها و تست پایدار.
6.2 اصطلاحات مربوط به تکنیکهای فازینگ (Terminology Related to Fuzzing Techniques)
- Generation-based Fuzzing: تولید ورودیها بر اساس مشخصات فرمت دادهها به جای تغییر ورودیهای موجود.
- Mutation-based Fuzzing: همانطور که گفتیم، تغییر خودکار ورودیهای موجود برای تولید ورودیهای جدید.
- Greybox Fuzzing: فازری که از اطلاعات محدود (مثل code coverage) برای هدایت mutation استفاده میکند.
- Blackbox Fuzzing: فازر بدون دسترسی به کد یا اطلاعات داخلی برنامه عمل میکند.
- Whitebox Fuzzing: فازینگ با دسترسی کامل به کد و مسیرهای برنامه، معمولاً با symbolic execution.
- Symbolic Execution: اجرای نمادین برنامه برای یافتن مسیرهای خاص و تولید ورودیهای هدفمند.
- Concolic Testing: ترکیب concrete و symbolic execution برای تولید ورودیها.
6.3 اصطلاحات مربوط به مدیریت ورودیها و کرشها (Terminology Related to Input and Crash Management)
- Seed Scheduling: تصمیمگیری درباره اینکه کدام بذرها (seed) باید اولویتبندی شوند.
- Coverage-guided Mutation: تغییر ورودیها با هدف افزایش پوشش کد.
- Corpus Reduction: مشابه minimize corpus، اما شامل حذف ورودیهای مشابه یا غیرضروری است.
- Crash Exploitability: تعیین اینکه آیا یک کرش قابلیت exploitation دارد یا خیر.
- Regression Test: استفاده از کرشهای قدیمی برای اطمینان از اصلاح شدن باگها.
6.4 اصطلاحات مربوط به ابزارها و ساختار فازینگ (Terminology Related to Fuzzing Tools and Architecture)
- Fuzzer Engine: موتور اصلی فازینگ که ورودیها را تولید و اجرا میکند.
- Fuzzing Harness Generator: ابزارهایی که خودکار harness میسازند.
- Instrumentation: اضافه کردن کد به برنامه برای جمعآوری اطلاعات پوشش و خطاها.
- Persistent Fuzzing Loop: حلقه اجرای مداوم برای افزایش کارایی فازینگ.
6.5 اصطلاحات مربوط به عملکرد و متریکها (Terminology Related to Performance and Metrics)
- Edge Hit: هر مسیر یا edge جدیدی که فازر اجرا کرده است.
- New Path: مسیر جدیدی که تاکنون توسط هیچ ورودی اجرا نشده.
- Mutation Score: معیاری برای سنجش اثرگذاری تغییرات ورودیها.
- Throughput: تعداد ورودیهای تست شده در واحد زمان.
- Stability / Flakiness: بررسی اینکه آیا فازر نتایج پایدار میدهد یا خیر.
6.6 اصطلاحات پیشرفته/متفرقه (Advanced / Miscellaneous Terminology)
- Heuristic-driven Fuzzing: استفاده از heuristics برای انتخاب بهترین جهشها (mutation).
- Hybrid Fuzzing: ترکیب فازینگ جعبه خاکستری (greybox) و اجرای نمادین (symbolic execution) یا تکنیکهای دیگر.
- Feedback Sanitizer: استفاده از اطلاعات سنیتایزر (sanitizer) برای هدایت فازینگ.
- Persistent Mode / In-memory Execution: اجرای سریع بدون بارگذاری مجدد برنامه.
- Sanitizer-assisted Fuzzing: هدایت فازر با اطلاعات خطای ارائه شده توسط سنیتایزرها (sanitizers).
7. منابع
https://aflplus.plus/libafl-book
https://people.csail.mit.edu/asolar/SynthesisCourse/Lecture1.htm
https://www.syllab.ir/courses/s46
https://lcamtuf.coredump.cx/afl
https://aflplus.plus
https://github.com/AFLplusplus/AFLplusplus
https://github.com/google/sanitizers
https://google.github.io/oss-fuzz/reference
https://appsec.guide/docs/fuzzing/#introduction-to-fuzzers
https://appsec.guide/docs/fuzzing/c-cpp/libfuzzer/#compile-a-fuzz-test
https://appsec.guide/docs/fuzzing/c-cpp/techniques/coverage-analysis
https://groups.google.com/g/afl-users/c/KgE5V8p1Yi8
https://afl-1.readthedocs.io/en/latest/fuzzing.html
https://medium.com/@lachunasberme/fuzzing-cheat-sheet-afl-libfuzzer-boofuzz-windbg-and-ghidra-710faa2d8414
https://intel.github.io/HBFA-FL/src/fuzzing/generatingCoverageReports.html
https://weinholt.se/articles/fuzzing-scheme-with-aflplusplus
https://barro.github.io/2018/06/afl-fuzz-on-different-file-systems
https://aflplus.plus/docs/quickstartguide
https://medium.com/@cy1337/a-basic-guide-to-afl-qemu-495df504b5fb
https://robertheaton.com/2019/02/16/introducing-afl-ruby
https://appsec.guide/docs/fuzzing/c-cpp/aflpp
https://aflplus.plus
https://aflplus.plus/docs/afl-fuzz_approach
https://aflplus.plus/docs/tutorials
https://en.wikipedia.org/wiki/Satisfiability_modulo_theories
https://github.com/ksluckow/awesome-symbolic-execution
https://github.com/cpuu/awesome-fuzzing
https://queue.acm.org/detail.cfm?id=2094081
https://blog.adacore.com/advanced-fuzz-testing-with-aflplusplus-3-00
https://www.sidechannel.blog/en/afl-and-an-introduction-to-feedback-based-fuzzing
https://aflplus.plus/docs/afl-fuzz_approach
https://flabbergasted.in/blog/afl-internals-qemu-instrumentation
Andrea Fioraldi, Dominik Maier, Heiko Eißfeldt, and Marc Heuse. “AFL++: Combining incremental steps of fuzzing research”. In 14th USENIX Workshop on Offensive Technologies (WOOT 20). USENIX Association, Aug. 2020.
Andrea Fioraldi, Daniele Cono D’Elia, and Leonardo Querzoni. “Fuzzing binaries for memory safety errors with QASan”. In 2020 IEEE Secure Development Conference (SecDev), 2020.
Dominik Maier, Lukas Seidel, and Shinjo Park. “BaseSAFE: BasebandSAnitized Fuzzing through Emulation”. In 13th ACM Conference on Security and Privacy in Wireless and Mobile Networks (WiSec 20), Linz (Virtual Event), Austria, July 2020.
Jinghan Wang, Chengyu Song, and Heng Yin. “Reinforcement Learning-based Hierarchical Seed Scheduling for Greybox Fuzzing”. In Proceedings of the 2021 Network and Distributed System Security Symposium (NDSS’21), February 2021.
Luca Borzacchiello, Emilio Coppa and Camil Demetrescu. “Fuzzing Symbolic Expressions”. In 2021 IEEE/ACM 43rd International Conference on Software Engineering (ICSE), 2021.
Sihang Liu, Suyash Mahar, Baishakhi Ray, and Samira Khan. “PMFuzz: Test Case Generation for Persistent Memory Programs”. The International Conference on Architectural Support for Programming Languages and Operating Systems (ASPLOS), 2021
Andrea Fioraldi, Daniele Cono D’Elia, Davide Balzarotti. “The Use of Likely Invariants as Feedback for Fuzzers”. In 30th USENIX Security Symposium (USENIX Security 21), USENIX Association, August 2021.
Prashast Srivastava and Mathias Payer. “Gramatron: Effective Grammar-Aware Fuzzing”. InProceedings of the 30th ACM SIGSOFT International Sympo-sium on Software Testing and Analysis (ISSTA ’21), July 11–17, 2021, Virtual, Denmark.
Luca Borzacchiello, Emilio Coppa and Camil Demetrescu. “FUZZOLIC: Mixing fuzzing and concolic execution”. Computers & Security, Vol. 108, 2021.
Dominik Maier and Fabian Toepfer. “BSOD: Binary-only Scalable fuzzing Of device Drivers”. In 24th International Symposium on Research in Attacks, Intrusions and Defenses, San Sebastian, Spain, October 2021
Keno Haßler and Dominik Maier. “WAFL: Binary-Only WebAssembly Fuzzing with Fast Snapshots “. In Reversing and Offensive-oriented Trends Symposium, Vienna, Austria, November 2021
Alessandro Mantovani, Andrea Fioraldi, Davide Balzarotti. “Fuzzing with Data Dependency Information”. In EuroS&P 2022, 7th IEEE European Symposium on Security and Privacy, 6-10 June 2022, Genoa, Italy, IEEE (Ed.). Genoa.
Alexey Vishnyakov, Daniil Kuts, Vlada Logunova, Darya Parygina, Eli Kobrin, Georgy Savidov, Andrey Fedotov. “Sydr-Fuzz: Continuous Hybrid Fuzzing and Dynamic Analysis for Security Development Lifecycle”. 2022 Ivannikov ISPRAS Open