پیشگفتار
فازینگ (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) است که خود به چند نوع به شرح زیر تقسیم میشود:
- AddressSanitizer (ASAN)
- MemorySanitizer (MSAN)
- ThreadSanitizer (TSAN)
- LeakSanitizer
- 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
ارتباط با نویسندهی مقاله «رامین فرجپور»: