فازینگ کد برنامه‌های متن باز با استفاده از LibFuzzer
مقاله
  • ۳۰ بهمن ۱۴۰۲
  • Learning Road Map
  • ۷ دقیقه خواندن

فازینگ کد برنامه‌های متن باز با استفاده از LibFuzzer

پیش‌گفتار

فازینگ (Fuzzing) روشی برای شناسایی باگ و آسیب‌پذیری‌های موجود در نرم‌افزارها و سیستم‌عامل‌ها است. به عبارت دیگر کارشناس امنیت در فازینگ تلاش می‌کند تا با ارسال تعداد زیادی از ورودی‌های غیر معتبر و غیر پیش‌بینی شده به یک برنامه، پاسخ آن برنامه را ارزیابی کرده و رفتارهای نا متعارف یا Crash نرم‌افزار در برابر ورودی‌های خاص را شناسایی کند. با این روش می‌توان آسیب‌پذیری‌ها و مشکلات موجود در هر نرم‌افزار را شناسایی کرد. این آسیب‌پذیری‌ها در امن‌سازی نرم‌افزار و سیستم‌عامل، نوشتن اکسپلویت برای آن یا گزارش در پلتفرم‌های باگ‌بانتی جهت دریافت جایزه کاربرد دارد (بسته به هدفی که متخصص از اجرای فازینگ داشته است). ابزارهای زیادی برای شناسایی آسیب‌پذیری‌های شناخته شده در کدهای برنامه‌نویسی یا سامانه‌ها وجود دارد اما فازینگ این قابلیت را به ما می‌دهد تا آسیب‌پذیری‌هایی را شکار کنیم که تا پیش از این ناشناخته بودند. هکرهای کلاه سیاه از این روش برای شناسایی آسیب‌پذیری‌های جدید و توسعه‌ی اکسپلویت‌های Zero-Day برای آن‌ها بهره می‌برند. در طرف مقابل شرکت‌ها و سازمان‌های تلاش می‌کنند تا با اجرای انواع فازینگ در سامانه‌های خود، آسیب‌پذیری‌های موجود را پیش از مهاجمین شناسایی و برطرف کنند. فازینگ را می‌توان برای فایل‌ها و سامانه‌های مختلفی مانند موارد زیر اجرا کرد:
  • کامپایلرها
  • مفسرها
  • برنامه‌های وب
  • انواع فایل‌ها مثل PNG ، JSON، YML و غیره
در گذشته طی یک برنامه‌ی پخش زنده درباره‌ی تحلیل کد به صورت دستی را شرح دادم. در این مقاله قصد دارم فازینگ کد به صورت داینامیک و با استفاده از LibFuzzer  را شرح دهم. ویدیوی این برنامه را می‌توانید از اینجا تماشا کنید. ‌

libFuzzer چیست؟

libFuzzer در حقیقت کتاب‌خانه‌ای (Library) است که برای فاز کردن کتابخانه‌های متن‌باز (Open Source) دیگر کاربرد دارد. این کتاب‌خانه توسط Clang  برای کامپایلر زبان C  توسعه داده شده است. آسیب‌پذیری‌های مهم بسیاری در دنیا با استفاده از همینlibFuzzer  کشف و برطرف شده‌اند. به عنوان مثال می‌توان به مورد زیر اشاره کرد:

https://llvm.org/docs/LibFuzzer.html#trophies

libFuzzer از سیستم‌عامل‌های ویندوز، مک، لینوکس و اندروید پشتیبانی می‌کند. در این مقاله ابتدا می‌خواهم شیوه‌ی استفاده از libFuzzer  را به صورت خلاصه و کاربردی به شما آموزش دهم به‌گونه‌ای که بتوانید برنامه‌های خود یا سازمانتان را با فازینگ تست کنید. در انتهای مقاله نیز یک مثال کاربردی استفاده از libFuzzer را شرح خواهم داد.

آشنایی با ClusterFuzz

شرکت گوگل پروژه‌ای برای فازینگ  کتاب‌خانه‌های متن‌باز شروع کرده که ClusterFuzz نام دارد. این پروژه بر روی بسترGoogleCloud  اجرا می‌شود و از  libFuzzer  تحت عنوان OSS-Fuzz  برای   فازینگ  کتاب‌خانه‌ها بهره می‌برد. معماری OSS-Fuzz  در شکل زیر نمایش داده شده است. روال کار به این صورت است که ابتدا Write Fuzzers توسط کتاب‌خانه‌یlibFuzzer  نوشته شده و در CI  قرار می‌گیرد. از این لحظه به بعد، هر بار یک commit  شامل تغییرات جدید در کد اعمال شود، این فازر  شروع به تست کد کرده و با توجه به Corpus  ( ورودی که تعربف شده) برای یافتن مواردی که موجب رخ‌دادن crash در برنامه می‌شود، تلاش می‌کند. لازم به ذکر است که در تمام پروژه‌های متن‌باز معروف، برنامه‌نویس‌ها از libFuzzer برای تست API های کتاب‌خانه یک پوشه به نام fuzz  در مسیر پروژه‌ی خود ایجاد می کنند.  برای هر API  در بدنه‌ی کد یک‌سری متدهای داخلی وجود دارد و اگر کدی در این متد‌های داخلی اضافه یا تغییری در آن‌ها داده شود، فازر مجدد در پوشه‌ی fuzz  شروع به تست API  ها می‌کند. فهرست پروژه‌هایی که توسط OSS-Fuzz (Open Source Software - Fuzz ) تست می‌شود را در لینک زیر می‌توانید مشاهده کنید:

https://github.com/google/oss-fuzz/tree/master/projects

 ‌

شیوه‌ی نصب و استفاده از libFuzzer

در اولین گام از نصب libFuzzer در سیستم‌عامل لینوکس نیاز است تا کامپایلر Clang/Clang++  را با استفاده از دستور زیر نصب کنیم:

$ apt-get install clang/clang++

در گام دوم باید کتاب‌خانه‌ی Sanitizer  را با دستور زیر نصب کنیم (در دوره‌ی مبانی اکسپلویت‌نویسی لینوکس، مثال‌های کاربری متعددی در خصوص این کتاب‌خانه را با هم کار خواهیم کرد):

$ apt-get install libasan

Sanitizer در واقع راهکاری برای کشف خطاهای حافظه (Memory Error Detector) است که خود به چند نوع به شرح زیر تقسیم می‌شود:
  1. AddressSanitizer (ASAN)
  2. MemorySanitizer (MSAN)
  3. ThreadSanitizer (TSAN)
  4. LeakSanitizer
  5. UndefinedBehaviorSanitizer (UBSAN)
هر یک از Sanitizerهای بالا خطاهای خاصی از حافظه را بررسی می‌کنند. سپس باید فایل build.sh در پوشه‌ی libFuzzer/Fuzzer را در کنسول Bash‌ اجرا کنیم:

$ ./build.sh

ar: `u' modifier ignored since `D' is the default (see `U')

ar: creating libFuzzer.a

اگر خروجی مشابه بالا دریافت کردید به این معنا است که libFuzzer به درستی و به صورت Static Library  کامپایل شده است.

 ‌

چگونه با استفاده از  libFuzzer یک فازر بنویسیم؟

در این بخش یک فازر ساده با استفاده از libFuzzer  نوشته و با استفاده از آن، تلاش می‌کنیم آسیب‌پذیر بودن یک برنامه‌‌ی نمونه با ورودی‌های مختلف را بررسی کنیم. اگر فایل first_fuzzer.cc را بخوانید، یک متد فازینگ در خطی مشابه زیر مشاهده خواهید کرد:

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size){

return 0;

}

LLVMFuzzerTestOneInput در واقع یک متد فازینگ است که دو ورودی دریافت می‌کند. البته در اینجا که قصد فازینگ یک  API  را داریم، باید توجه کنیم که نوع ورودی ما مشابه با نوع ورودی همان API  یا متد باشد. برای مثال کد زیر را در نظر بگیرید.

char func_api(const char *in, int size) {

             ...

}

int LLVMFuzzerTestOneInput(const char *Data, long long Size) {

func_api(Data, Size); 

return 0;

}

با استفاده از دستورات زیر می‌توان این فازر را کامپایل کرد:

$  clang++ -g -std=c++11 -fsanitize=address,fuzzer -fsanitize-coverage=trace-pc-guard first_fuzzer.cc Fuzzer/libFuzzer.a -o first_fuzzer

دستور -fsanitize=address,fuzzer به صورت اتوماتیک برنامه‌ی ما را با کتاب‌خانه‌ی libFuzzer لینک (Link) می‌کند. همچنین دقت داشته باشید هنگامی که متد VulnerableFunction1(data, size) را فراخوانی می کنیم، هیچ Argument  ورودی دریافت نمی‌کند. درنتیجه خود کتاب‌خانه‌ی libFuzzer  ورودی تستی برای متد VulnerableFunction1  را تولید خواهد کرد. سپس با استفاده از دستور زیر برنامه‌ی کامپایل شده را اجرا می‌کنیم:

$ git clone https://github.com/Ravin-Academy/OSS-LibFuzzer.git

$ ./first_fuzzer

نمونه‌ی خروجی اجرای این فاز در شکل زیر نمایش داده شده است. همان‌طور که در این شکل قابل مشاهده است، یک آسیب‌پذیری Heap Overflow  توسط libFuzzer   پیدا شده است. در ادامه، به بررسی عمیق‌تر libFuzzer  به همراه یک مثال کاربردی از فازینگ با استفاده از این برنامه خواهیم پرداخت. یک مثال عملی ساده: چگونه در کد PParam  یک آسیب‌پذیری Memory Leak  پیدا کردم؟ PParam  یک کتاب‌خانه برای تبدیل داده‌ها به XML و JSON است و در ادامه روشی که یک آسیب‌پذیری با استفاده از libFuzzer در این کتاب‌خانه کشف کردم را شرح می‌دهم. در اولین گام چون آشنایی زیادی با ساختار کد این کتاب‌خانه نداشتم پس به ناچار باید در ابتدا کد را کامپایل کرده و شیوه‌ی عملکرد این کتاب‌خانه را بررسی می‌کردم. روش کامپایل در بخش README گیت هاب این پروژه شرح داده شده است:

https://github.com/CloudAvid/PParam

در گام بعد مثال‌هایی از این برنامه را در آدرس زیر مطالعه و بررسی کردم:

https://github.com/CloudAvid/PParam/tree/master/examples

سپس دو مثال از این برنامه را انتخاب و در پروژه‌ی خودم در پوشه‌ی به نام fuzz  در آدرس زیر قرار دادم:

https://github.com/raminfp/fuzz-libpparam/tree/master/fuzz

در شکل زیر یک برنامه که در پوشه‌ی مثال وجود دارد نمایش داده شده است. همان‌گونه که مشاهده می‌کنید یک متد به نام main در آن وجود دارد. در ادامه متد  Main را به متد «extern "C" int LLVMFuzzerTestOneInput» تغییر دادم که در شکل زیر نمایش داده شده است. حال نوبت آن رسیده تا برنامه‌ی تغییر داده شده را با استفاده از  کامپایلر clang  مطابق دستور زیر کامپایل نماییم تا libFuzzer  بتواند کار خود را شروع کند:

$  clang++ -g -std=c++11 -fsanitize=address,fuzzer -fsanitize-coverage=trace-pc-guard nic.cpp Fuzzer/libFuzzer.a -o nic

سپس برنامه کامپایل شده را اجرا می‌کنیم.

$ ./nic

در خروجی اجرایی این برنامه که در شکل زیر نمایش داده شده است می‌بینیم که یک crash  رخ داده است. همان‌گونه که در شکل مشاهده می‌شود، LeakSanitizer یک خطای نشت حافظه یا Memory Leak را شناسایی کرده است. سپس این مشکل را گزارش دادم، که در آدرس زیر می‌توانید آن را مطالعه کنید:

https://github.com/CloudAvid/PParam/issues/9

CVE  ثبت شده برای این آسیب‌پذیری نیز از لینک زیر قابل مشاهده است:

https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-28723

ارتباط با نویسنده‌ی مقاله «رامین فرج‌پور»:

https://twitter.com/MF4rr3ll