گزارش فنی و مقاله پژوهشی

خودکارسازی تولید رابط آزمون فازی با استفاده از مدل‌های زبانی بزرگ و بهینه‌سازی تکاملی

طراحی یک چارچوب پویا و تکراری برای تکامل معنایی کدهای Fuzz Harness بر اساس بازخوردهای چندبعدی محیط اجرا

ضرورت و انگیزه پژوهش

نرم‌افزارها ستون اصلی بسیاری از خدمات حیاتی جوامع مدرن محسوب می‌شوند. امروز سامانه‌های بانکی، شبکه‌های توزیع انرژی، زیرساخت‌های ارتباطی و حتی خدمات سلامت، همگی به درستی و امنیت کد وابسته‌اند. همین وابستگی باعث شده است که یک آسیب‌پذیری نرم‌افزاری دیگر صرفاً یک خطای فنی ساده نباشد، بلکه بتواند پیامدهای اقتصادی، امنیتی و اجتماعی جدی به همراه داشته باشد. این مسئله به‌ویژه در کتابخانه‌های نوشته‌شده با زبان‌های C و C++ اهمیت بیشتری دارد؛ کتابخانه‌هایی که بسیاری از آن‌ها سال‌ها یا حتی دهه‌هاست در زیرساخت نرم‌افزارهای امروزی استفاده می‌شوند. تنها در سال ۲۰۲۴، بیش از چهل هزار آسیب‌پذیری جدید در پایگاه داده CVE ثبت شده است و بخش قابل‌توجهی از این آسیب‌پذیری‌ها به همین اکوسیستم‌های قدیمی اما همچنان پرکاربرد مربوط می‌شود.

در چنین فضایی، آزمون فازی “Fuzzing test” به یکی از مؤثرترین روش‌های کشف خودکار آسیب‌پذیری تبدیل شده است. ابزارهایی مانند AFL و libFuzzer با اجرای مکرر برنامه روی ورودی‌های جهش‌یافته، مسیرهای اجرایی جدید را شناسایی می‌کنند و ورودی‌هایی را که به کشف رفتارهای تازه منجر شوند، نگه می‌دارند و توسعه می‌دهند. موفقیت این رویکرد در پروژه‌هایی مانند OSS-Fuzz گوگل و syzkaller نشان داده است که آزمون فازی می‌تواند در مقیاس بزرگ، هزاران خطا و آسیب‌پذیری واقعی را آشکار کند.

چالش اصلی: کیفیت Fuzz Harness

با این حال، موفقیت آزمون فازی تنها به قدرت موتور جهش وابسته نیست. یکی از عوامل تعیین‌کننده، کیفیت «رابط آزمون فازی» یا Fuzz Harness است؛ بخشی از کد که ورودی خام تولیدشده توسط فازر را به فراخوانی‌های معنادار در API کتابخانه هدف تبدیل می‌کند. اگر این رابط ضعیف، محدود یا بیش از حد ساده باشد، فازر هرچقدر هم پیشرفته باشد نمی‌تواند به بخش‌های عمیق‌تر کد دسترسی پیدا کند.

به بیان دیگر، رابط آزمون فازی پنجره‌ای است که فازر از طریق آن کتابخانه را می‌بیند؛ هرچه این پنجره محدودتر باشد، بخش بزرگ‌تری از کد در تاریکی باقی می‌ماند. نوشتن این رابط‌ها به‌صورت دستی، به‌خصوص برای کتابخانه‌های پیچیده‌ای مانند OpenSSL، کاری دشوار و زمان‌بر است. توسعه‌دهنده باید توالی درست فراخوانی‌ها، مقداردهی اولیه ساختارهای داده، شرایط پنهان API و الگوهای درست مدیریت حافظه را بشناسد؛ دانشی که همیشه در مستندات رسمی وجود ندارد. افزون بر این، یک رابط دست‌نویس معمولاً تنها یک دیدگاه ثابت از API را پوشش می‌دهد و پس از مدتی، روند رشد پوشش کد متوقف می‌شود. این پدیده در آزمون فازی با عنوان سکون یا Plateau شناخته می‌شود.

مسئله پژوهش و شکاف موجود

شواهد تجربی نشان می‌دهد که در بسیاری از کتابخانه‌های پیچیده، سکون آزمون فازی نه به دلیل ضعف موتور فازر، بلکه به دلیل محدودیت خود رابط آزمون رخ می‌دهد. اگر رابط فقط یک مسیر خاص از API را فعال کند، فازر نمی‌تواند از محدوده‌ای که آن رابط برایش ساخته فراتر برود. بنابراین مسئله اصلی این پژوهش آن است که چگونه می‌توان رابط آزمون فازی را از یک قطعه کد ثابت به موجودیتی قابل بهبود و تکامل تبدیل کرد.

در سال‌های اخیر، ابزارهای مختلفی برای تولید خودکار رابط آزمون فازی پیشنهاد شده‌اند. برای مثال، FuzzGen با تحلیل ایستای وابستگی‌های API رابط تولید می‌کند، اما پس از تولید، آن را تغییر نمی‌دهد و از بازخورد اجرای فازر برای بهبود آن استفاده نمی‌کند. ابزارهایی مانند OSS-Fuzz-Gen و HarnessAgent از مدل‌های زبانی بزرگ برای تولید رابط بهره می‌برند و می‌توانند خطاهای کامپایل را تا حدی اصلاح کنند، اما این فرایند بیشتر در سطح نحوی باقی می‌ماند. پس از آنکه کد کامپایل شد، معمولاً سازوکاری برای بهبود منطقی و تدریجی رابط وجود ندارد.

شکاف تحقیقاتی (Research Gap)

شکاف اصلی در این حوزه آن است که رابط آزمون فازی معمولاً یک خروجی ایستا در نظر گرفته می‌شود، نه موجودیتی که بتواند بر اساس نتایج اجرا تغییر کند. این پژوهش دقیقاً بر همین نقطه تمرکز دارد: طراحی چارچوبی که بتواند رابط آزمون فازی را با استفاده از بازخورد اجرایی، به‌صورت تکراری و تکاملی بهبود دهد.

رویکرد پیشنهادی

ایده اصلی این پژوهش ساده اما مهم است: اگر در آزمون فازی، ورودی‌ها جهش پیدا می‌کنند تا مسیرهای جدیدی در برنامه کشف شود، چرا خود رابط آزمون فازی جهش پیدا نکند؟ به جای آنکه همیشه ورودی‌های جدید را به یک رابط ثابت بدهیم، می‌توان خود رابط را تغییر داد تا بخش‌های بیشتری از API فعال شود و مسیرهای اجرایی تازه‌ای در دسترس فازر قرار گیرد. بر همین اساس، در این پژوهش طراحی رابط آزمون فازی به‌عنوان یک مسئله بهینه‌سازی تکاملی چندهدفه روی کد منبع تعریف می‌شود.

مراحل فرآیند چرخه‌ی تکاملی سیستم:

گام اول: تولید اولیه مجموعه کاندیداها
ابتدا مدل زبانی بزرگ، بر اساس امضاهای API کتابخانه هدف، مجموعه‌ای اولیه از رابط‌های کاندیدا تولید می‌کند.
گام دوم: کامپایل و اجرای پویا
سپس هر رابط با ابزارهایی مانند Clang، libFuzzer و پاک‌سازهای خطای حافظه و رفتار تعریف‌نشده کامپایل و اجرا می‌شود.
گام سوم: تحلیل چندمعیاره تابع شایستگی
پس از اجرا، کیفیت هر رابط با یک تابع شایستگی ترکیبی ارزیابی می‌شود. این تابع شایستگی سه معیار را هم‌زمان در نظر می‌گیرد: میزان پوشش لبه‌ها، پایداری اجرا، و تنوع مسیرهای کشف‌شده.
گام چهارم: اعمال تغییرات و جهش معناشناختی
در پایان هر نسل، بهترین رابط‌ها همراه با بازخورد عملکردی به مدل زبانی داده می‌شوند. مدل سپس تغییراتی ساختاریافته پیشنهاد می‌کند؛ مانند تغییر روش پارس ورودی، افزودن فراخوانی‌های جدید API، وارد کردن منطق حالت‌مند یا اصلاح مدیریت حافظه.

در این چارچوب، مدل زبانی بزرگ صرفاً یک مولد کد یک‌باره نیست، بلکه نقش «عملگر جهش معناشناختی» را بازی می‌کند. این نقش تفاوت اصلی رویکرد پیشنهادی با روش‌های پیشین است.

چرا مدل زبانی بزرگ عملکرد موفقی به عنوان عملگر جهش دارد؟

جهش تصادفی در سطح کد C/C++ معمولاً کارآمد نیست، زیرا بیشتر تغییرات تصادفی به کدی نامعتبر یا کامپایل‌ناپذیر منجر می‌شوند. اما مدل‌های زبانی بزرگ، به دلیل آموزش روی حجم عظیمی از کد، تا حدی با الگوهای رایج API، توالی‌های فراخوانی و قواعد ضمنی مدیریت حافظه آشنا هستند. بنابراین می‌توانند تغییراتی پیشنهاد دهند که احتمال کامپایل‌پذیری و درستی معنایی آن‌ها بسیار بیشتر از جهش‌های تصادفی است.

نتایج تجربی

چارچوب پیشنهادی روی یازده کتابخانه متنوع C و C++ ارزیابی شد. در هفت مورد، یعنی بیش از ۶۳ درصد آزمایش‌ها، سیستم توانست پوشش کد قابل‌اندازه‌گیری ایجاد کند و رابط آزمون فازی را طی نسل‌های متوالی بهبود دهد. موارد ناموفق عمدتاً به وابستگی‌های پیچیده کامپایل یا APIهای بسیار خاص مربوط بودند؛ وضعیتی که در آن مدل زبانی گاهی دچار فرض‌های نادرست یا توهم‌زایی می‌شد.

برجسته‌ترین نتیجه در کتابخانه چندرسانه‌ای GPAC به دست آمد. در این مورد، پوشش لبه از ۷۵ لبه در نسل صفر به ۲۳۱ لبه در نسل هشتم رسید؛ یعنی رشدی معادل ۲۰۸ درصد. این نتیجه نشان می‌دهد که حلقه تکاملی توانسته است در طول زمان، رابط را به‌گونه‌ای تغییر دهد که مسیرهای اجرایی بیشتری فعال شوند. در مقایسه با HarnessAgent، که یکی از ابزارهای پیشرفته موجود در این حوزه است، تفاوت چشمگیر بود.

روش / ابزار ارزیابیپوشش لبه (کتابخانه GPAC)بهبود نسبت به نسل صفربرتری نسبت به HarnessAgent
تولید یک‌باره (نسل صفر)75 لبه
ابزار HarnessAgent44 لبهمبنای رقیب
سیستم پیشنهادی (نسل هشتم)231 لبه208% رشد425% برتری

همچنین در مقایسه مستقیم با تولید یک‌باره رابط توسط مدل زبانی، اضافه کردن حلقه تکاملی به‌طور میانگین ۶۸.۷ درصد پوشش لبه بیشتری ایجاد کرد. این نتیجه نشان می‌دهد که ارزش اصلی سیستم فقط در توانایی مدل زبانی نیست، بلکه در ترکیب آن با بازخورد اجرایی و بهینه‌سازی تکراری نهفته است.

در آزمایش روی OpenSSL نیز سیستم توانست دوازده آسیب‌پذیری را به‌صورت خودکار کشف کند. این یافته نشان می‌دهد که بهبود تکاملی رابط آزمون فازی می‌تواند مسیرهای عمیق‌تری را در کد فعال کند؛ مسیرهایی که احتمال رسیدن به حالت‌های آسیب‌پذیر در آن‌ها بیشتر است. از نظر هزینه نیز میانگین هزینه محاسباتی برای هر هدف حدود ۴۴ سنت بود که در مقایسه با زمان و تخصص لازم برای نوشتن دستی رابط‌های آزمون فازی، بسیار مقرون‌به‌صرفه است.

اهمیت پژوهش

اهمیت این پژوهش در چند سطح قابل بررسی است و می‌توان دستاوردهای آن را در سه قلمرو کلی دسته‌بندی نمود:

01

امنیت نرم‌افزار

این چارچوب راهی عملی برای مقیاس‌پذیر کردن آزمون فازی در کتابخانه‌های پیچیده ارائه می‌دهد؛ کتابخانه‌هایی که APIهای آن‌ها گسترده، حالت‌مند و گاهی فاقد مستندات کامل است.

02

هوش مصنوعی کاربردی

این پژوهش نشان می‌دهد که مدل‌های زبانی بزرگ می‌توانند فراتر از تولید یا تکمیل کد، در نقش یک مؤلفه فعال در فرایند بهینه‌سازی تکاملی ظاهر شوند.

03

مهندسی نرم‌افزار

الگوی «بهبود تکراری کد با استفاده از بازخورد اجرایی» می‌تواند در حوزه‌هایی مانند تست رگرسیون خودکار، تولید برنامه و تحلیل پویای نرم‌افزار مورد استفاده قرار گیرد.

جمع‌بندی

این پژوهش چارچوبی برای تبدیل طراحی رابط آزمون فازی از یک وظیفه ایستا به یک مسئله بهینه‌سازی تکاملی ارائه می‌کند. در این چارچوب، مدل زبانی بزرگ نقش عملگر جهش معناشناختی را بر عهده دارد و رابط‌های آزمون فازی را بر اساس بازخورد واقعی اجرا به‌صورت تدریجی بهبود می‌دهد. کیفیت هر رابط با ترکیبی از پوشش لبه، پایداری اجرا و تنوع مسیر سنجیده می‌شود و بهترین رابط‌ها مبنای نسل بعدی قرار می‌گیرند.

نتایج تجربی نشان داد که این رویکرد می‌تواند در بخش قابل‌توجهی از کتابخانه‌های C و C++ پوشش قابل‌اندازه‌گیری ایجاد کند، در مواردی مانند GPAC رشد چشمگیری در پوشش به دست آورد، نسبت به ابزارهای موجود عملکرد بهتری داشته باشد و حتی در کتابخانه‌ای مانند OpenSSL آسیب‌پذیری‌های واقعی را کشف کند. در مجموع، یافته‌های این پژوهش نشان می‌دهد که ترکیب هدفمند مدل‌های زبانی بزرگ با بازخورد اجرایی چندبعدی می‌تواند رابط آزمون فازی را از یک قطعه کد ثابت به موجودیتی پویا، قابل تکامل و مؤثرتر در کشف آسیب‌پذیری‌های عمیق تبدیل کند.

Avatar photo
عاطفه محقق راد

نظرات بسته شده است.