כפי שכתבתי ב פרק אחרון, במאמר זה אסביר רק כיצד לטשטש כאשר יש גישה לקוד המקור באמצעות AFL. כדי להדגים, לקחתי תוכנת קוד פתוח ישנה שמצאתי ב-GitHub בשם cccalc. כפי שהשם מרמז, זהו מחשבון פשוט שנכתב ב-C. בחרתי בו כי סביר להניח ש-fuzzing ימצא בו בקלות קריסות רבות, וכפי שתראו, השערה זו התבררה כנכונה.
דבר ראשון, עלינו לקבוע יעד. החלטתי לטשטש את הפונקציה main() במקום למצוא פונקציה ספציפית ל-fuzz. הנימוק הוא שהתוכנה מנתחת את הקלט הגולמי בסדרה של פונקציות לפני שהיא מעבירה קלט מעובד לפונקציות שמבצעות בפועל את החישובים בעצמן. מכיוון שהתוכנית מסננת קלט גרוע במהלך הניתוח, זה לא הוגן לטשטש פונקציה ספציפית כלשהי.
ה-main() המקורי נראה פחות או יותר כך (קיצרתי אותו מעט):
בדרך כלל, הוא מכיל שני ענפים: או שהוא מקבל קלט דרך argc, argv[], או דרך קלט רגיל (stdin). אני לא בטוח למה, אבל היוצר של AFL ממליץ במפורש לֹא לטשטש דרך argc, argv[], וכך גם כל שאר המקורות המקוונים. אז שיניתי את זה קצת: קודם כל הסרתי את הקטע שעוסק בקלט שמגיע ממנו argc, argv, ולאחר מכן ניסיתי לייעל אותו ככל האפשר. אחרי הכל, בכל פעם שה-fuzzer מנסה קלט אחר, פונקציה זו מבוצעת. לכן חוסר יעילות יכול להוביל להאטות חמורות. הנה התוצאה הסופית:
שימו לב ללולאה שהוספתי. הוא מורה ל-AFL להשתמש במצב מתמשך. במצב ברירת המחדל של AFL, בכל פעם ש-AFL מריץ את התוכניות, הוא משתמש ב-fork() syscall, כדי ליצור תת-תהליך חדש. ל-syscall הזה יש תקורה רצינית, שמאטה ברצינות את כל תהליך הטשטוש. כאשר נעשה שימוש במצב מתמשך, ה-fuzzer משתמש באותו תהליך פעם אחר פעם. הדרישה היחידה היא למחוק את כל המשתנים והמאגרים בכל פעם שהלולאה פועלת.
קראתי לקובץ עם הפונקציה המשתנה harness.c, וכדי להרכיב את התוכנה עם הקובץ הזה ולא עם main.c, שיניתי את טופס הקובץ automake:
ל
הגיע הזמן לטשטש את התוכנית. הרצתי את automake כדי ליצור את קבצי ה-make, ולאחר מכן יוצר את עצמו עם הפקודה:
כך שהתוכנית תתחבר עם מכשור AFL.
הדבר היחיד שנותר לעשות כעת הוא ליצור את תיקיית הקלט הראשונית עבור AFL, לה קראתי 'in', ואת תיקיית הפלט 'out'. בתיקיית הקלט, שמים קבצי טקסט פשוטים (ללא פורמט) המכילים קלט לגיטימי, כמו "20/2" או "5*4". מכיוון שאנו רוצים להפעיל כמה תהליכי AFL (כל תהליך פועל על ליבת המעבד שלו), אנו יוצרים תיקיית קלט נפרדת עבור כל תהליך.
על מנת לטשטש, פשוט מפעילים את הפקודה:
לתהליך הראשון, ההוויה השנייה
וכן הלאה וכן הלאה.
כפי שניתן לראות בצילום המסך הבא, ל-AFL יש ממשק משתמש (GUI) שנותן מידע חשוב על תהליך ההתערבות. לדוגמה, מדידות של הכיסוי ניתנות תחת כיסוי המפה, ומתחת ממצאים לעומק ה-GUI מספק גם את מספר קריסות ומידע קשור.
לאחר שנמצאה כמות מספקת (המספר המדויק תלוי בך) של קריסות, בדרך כלל לאחר פרק זמן ארוך ללא התרסקות ייחודית חדשה, תוכל להפסיק את התהליך. עבור כל תהליך של AFL שאתה מפעיל, תיקיה נפרדת תיווצר בתיקיית הפלט:
בתוך התיקיות הללו, הנתונים מסודרים כך:
חשוב להסביר איזה מידע מכיל כל אחד מאלה:
- plot_data - מעמיד את המידע בצורה מוליך ליצירת עלילה.
- fuzzer_stats - סטטיסטיקות על תהליך ה-fuzzing.
- fuzz_bitmap - מפת סיביות שבה כל בייט מתאים לענף של התוכנית.
"AFL שומרת על "מפת סיביות של fuzz", כאשר כל בייט בתוך מפת הסיביות מייצג ספירה של מספר הפעמים שענף מסוים בתוך התוכנית המטושטשת נלקח. AFL לא מבצע מיפוי אחד לאחד בין ענף מסוים לבין בתים בתוך מפת הסיביות. במקום זאת, המכשור המוטבע של AFL מציב מזהה קבוע של שני בתים אקראי בכל ענף. בכל פעם שהביצוע מגיע לסניף מכשיר, AFL מבצע XOR של מזהה הסניף החדש ומזהה הסניף האחרון שנראה לפני ההגעה לסניף החדש. זה לוכד גם את הענף הנוכחי וגם את הנתיב הייחודי שנלקח כדי להגיע אליו (כגון כאשר אותה פונקציה נקראת ממספר מיקומים בקוד). לאחר מכן AFL מחיל פונקציית גיבוב על הערך XOR'd כדי לקבוע איזו ערך במפת הסיביות מייצגת את שילוב הענף הזה. בכל פעם ששילוב ענף מסוים מופעל, הבתים המתאים מוגדלים בתוך מפת הסיביות." - cmdline - הפקודה שסופקה ל-AFL. בפועל שם התוכנית.
- .cur_input - הקלט הנוכחי.
- queue - כל הקלט שנוסה עד עכשיו.
- קורס ונתקע - התוצאות. כל קובץ בתיקיות אלה מכיל קלט שגרם לקריסות או להיתקע. מצוין בשם של כל קובץ הוא אות הקרנל של הקריסה (לא רלוונטי ב-hangs, כמובן), המזהה של הקלט המשמש את AFL ליצירת הקלט שגרם לקריסה, הזמן שחלף מאז החל AFL לרוץ, והמוטציה המשמשת ליצירת הקלט מהזרע שלה.
רוצה לשמוע עוד?
כולל פגישת ייעוץ חינם.