Unpacking 101 - איך, מה, ולמה

מבוא ל-Packing

כמעט כל נוזקה שתשימו את היד עליה משתמשת ב-packing. זוהי טכניקה שמטרתה היא לגרום לנוזקות להיות יותר מורכבות לניתוח ולזיהוי אוטומטי. בסופו של דבר, בתור מפתח הנוזקות, השאיפה שלך היא לעקוף כל מוצר הגנה שהוא ולמנוע מרברסרים להבין מה אתה עושה לעזאזל. Packing זו דרך אחת (ויחסית פרימיטיבית, כמובן שתלוי ב-packer) שמאפשרת לעשות את זה.

איך זה עובד?

במרבית המקרים, הנוזקה שתרוץ היא למעשה לא נוזקה אלא Loader. ה-Loader יכיל בתוכו את הקוד האמיתי בפורמט שלא ניתן לניתוח מיידי, ז"א, בצורה מוצפנת (RC4 הוא לחלוטין הפייבוריטי) או מקומפרסת (aPLib זה אלגוריתם דחיסה די נפוץ בקרב מפתחי נוזקות). הקוד יישב בדר"כ בתוך section נוסף ב-PE או בכל איזור שהוא, כאשר הקוד שרץ ב-entry עצמו הוא בעצם loader, שלוקח את הקוד שב-section, מפענח או מחלץ אותו, טוען אותו, ולאחר מכן מריץ אותו. לעתים הטעינה נעשית באותו הפרוסס (מה שנקרא self-injection, הנפוץ ביותר) ולפעמים בפרוסס מרוחק (מה שנקרא process injection או code injection). יש לשים לב שבמקרים רבים מאוד הטעינה תתבצע בשלבים - כלומר לפעמים ה-entry code יטען קודם כל שלקוד כלשהו ששמור איפשהו ב-PE, יפענח או יחלץ אותו, ואז השלקוד הזה יפענח את השכבות הבאות. מקרים כאלה נפוצים מאוד, וצריך לדעת להתמודד גם איתם.

הערת שוליים קטנה - בדר"כ ה-loaders האלה נקראים second-stage loaders, מכיוון שלרוב, ירוץ לפניהם loader אחר (first-stage loader כפי שאפשר לנחש) שיבצע את ההדבקה הראשונית, יחלץ את ה-second-stage loader ויריץ אותו, ולאחר מכן ה-second-stage loader יחלץ את הנוזקה עצמה, שהיא תבצע את ההדבקה האמיתית. First-stage loaders הם במרבית המקרים malicious documents, ולעתים אפשר להיתקל גם ב-first-stage loaders שהם אקספלויטים לחולשות כלשהן (החולשה ב-eqeditor עדיין בטופ אצל מפתחי הנוזקות).

אז איך מתמודדים עם Packing?

טוב ששאלתם, לרוב, השאיפה שלנו כחוקרים זה לנתח את הנוזקה עצמה ולהיפטר מכל שכבות ה-packing וההצפנה, לכן נרצה לעשות unpack לנוזקה. יש 3 דרכים נפוצות לעשות זאת:

  • unpacking אוטומטי - בעזרת כלים כמו unpac.me ודומיו, אפשר להגיע לתוצאות יפות מאוד ללא שום מאמץ. מספיק לחלוטין ב-90% מהמקרים.
  • unpacking דינמי - פותחים דיבאגר, שמים breakpoints על פונקציות winapi \ ntapi שיכולות להעיד על unpacking ולעזור לנו למצוא את הקוד המפוענח, ועושים unpacking כמו גדולים. זו השיטה שאתמקד בה בפוסט זה.
  • unpacking סטטי - פותחים את הדיסאסמבלר המועדף, מנתחים ל-loader את הצורה, מבינים איך הוא עובד לפרטי פרטים, וכותבים סקריפט שעושה בצורה אוטומטית unpack לנוזקה. לא נפוץ בכלל, אך במקרים שיש התעסקות עם packing מורכב שכולל שלבים רבים ושיטות anti-debugging מורכבות, זו השיטה היותר מוצלחת.

יאללה - בואו נעשה Unpack למשהו

בשביל הפוסט הזה, בחרתי סאמפל כלשהו של IcedID שיצא לי לחקור לאחרונה, מכיוון שבעיני הוא מייצג בצורה יפה מאוד את מרבית הנוזקות שהן packed, יש שם compression קלאסי ושימוש בפונקציות WinAPI הנפוצות.

לרוב, כשלב מקדים ל-unpacking, אנסה להבין אם הסאמפל שאני מסתכל עליו הוא בכלל packed. סקשנים מוזרים, אי תאימויות יוצאות דופן בין גדלים וירטואלים לגדלים פיזיים של sections, ואנטרופיה גבוהה (6-6.5+) הם אינדיקטורים טובים מבחינתי. אני זורק בדר"כ את הסאמפל ב-pe studio ואוסף את התוצאות משם. במקרה של הסאמפל הזה, לא ניתן להבחין ממש בפרטים יוצאי דופן, אין sections מוזרים, אין פערי גדלים משמעותיים, אבל יש אנטרופיה גבוהה (6.103), והאנטרופיה של ה-code section עצמו עוד יותר גבוהה (6.744). מבחינתי זה בדר"כ מספיק כדי להבין שהסאמפל הוא packed, אבל כשיש לי יותר ספק אני אזרוק ב-ida ואבדוק מה הולך, ושם כבר יהיה לי די ברור אם זה packed או לא.

אחרי שאני מבין שהנוזקה היא packed, השלב הבא הוא כבר לעשות unpack. אני זורק את הסאמפל ב-x32dbg, עוצר ב-entry, ושם breakpoints על הפונקציות הנפוצות ביותר ל-unpacking. לרוב, בשלב הזה אני עדיין לא יודע אם ה-unpacking משלב בתוכו הזרקת קוד לפרוסס מרוחק, לכן אשים breakpoints גם על פונקציות שרלוונטיות ל-code injection. בדר"כ אתן גם breakpoint נוסף על IsDebuggerPresent, משום שהרבה נוזקות משתמשות בפונקציה הזו כדרך מהירה לזהות הרצה בדיבאגר. זו הרשימה הקבועה שלי:

  • VirtualAlloc (אני שם תמיד את ה-breakpoint ב-ret על מנת לזרוק בקלות את הזכרון שאולקץ ב-dump, אדגים עוד רגע)
  • VirtualProtect
  • CreateProcessInternalW (יש לשים לב שהפונקציה הזו תיקרא רק ב-windows 7, בגרסאות העדכניות יותר רוב יצירת הפרוסס נעשית בקרנל והפונקציה הזו כבר לא נקראת, ולכן נעדיף לרברס נוזקות על VM של ווינדוס 7, מקל על החיים בעוד אספקטים)
  • NtResumeThread
  • WriteProcessMemory
  • IsDebuggerPresent

השיטה שלי לשים breakpoint על החזרה מ-VirtualAlloc היא כזו:

  1. ראשית, אני קופץ ל-VirtualAlloc (על ידי Ctrl+G):
    1
  2. בתוך VirtualAlloc, עושה follow לפונקציה עצמה (kernel32.dll במקרה הזה עושה קריאה ל-kernelbase.dll שמכיל את המימוש של VirtualAlloc):
  3. מרפרף בפונקציה עד שאני מגיע ל-ret (לא אמור להיות ארוך מדי) ואז שם breakpoint (לוחץ F2):
    3

על כל שאר הפונקציות אני שם breakpoints על ידי bp function-name.

ברגע שסיימתי עם כל ה-breakpoints, אתן F9 ואחכה שאחד מה-breakpoints יתפוס את ההרצה. במקרה הזה, מספר שניות אחרי שאני לוחץ F9, ה-breakpoint על VirtualAlloc נתפס!

כעת, אני לוחץ מקש ימני על EAX (שמכיל את ערך ההחזרה, הכתובת של הזכרון שאולקץ) ועושה follow in dump כלשהו, על מנת שאעקוב אחרי מה שנכתב לזכרון הזה. בדר"כ הזכרון הזה מעניין אותנו מאוד, כי הוא עשוי להכיל את ה-unpacked binary.

אחרי שה-breakpoint הבא נתפס, אני יכול לראות מה נכתב לזכרון הזה (שאני עוקב אחריו ב-dump). כרגע, לא נכתב לזכרון הזה שום דבר שמעניין אותי (כנראה שזה סתם שלקוד):

image

מאחר וה-breakpoint על VirtualAlloc נתפס שוב, אעשה את אותו הדבר שוב, אעקוב אחר הזכרון ב-dump חדש, ואמשיך ככה עד שאראה משהו שתופס לי את העין. במקרה הזה, לאחר הקריאה השלישית ל-VirtualAlloc, ה-breakpoint על VirtualProtect נתפס, ומשהו בזכרון החדש שאולקץ תופס לי את העין:

image

החדים מביניכם ישימו לב שבתחילת הזכרון שאולקץ כתוב MZ (עם 8 באמצע), ה-magic number של פורמט MZ, שמעליו נבנה פורמט PE. כלומר, יש סיכוי סביר שהקטע הזה הוא למעשה קובץ PE דחוס (ולמעשה מהכרה מוקדמת אני מזהה כבר את הרצף M8Z, שהוא אינדיקטור מעולה לדחיסה של קובץ PE עם aPLib), שזה מעולה עבורינו! אני משוטט טיפה ב-dump עד שאני נתקל בקטע הבא:

image

נראה שמיד לאחר הבינארי הדחוס, מופיעה הגרסה ה-decompressed שלו! אז אני מסמן את כל ה-PE בזכרון, ועושה לו dump לתוך קובץ, ובשאיפה, זו הנוזקה ה-unpacked. כעת אזרוק את הקובץ לתוך PE Studio, ואבדוק מה האנטרופיה, והאם נראה שהקובץ תקין:

כפי שניתן לראות - הכל מצוין! האנטרופיה נמוכה יחסית (5.347), כל ה-imports תקינים ונראה שבסה"כ הקובץ תקין לחלוטין. אם כך, הצלחנו לעשות unpack לנוזקה!

סיכום

במאמר זה עסקנו ב-packing - למדנו מה מטרת טכניקה זו, כיצד היא מבוצעת לרוב, כיצד ניתן להתמודד איתה, ואפילו עשינו unpacking דינמי לסאמפל של IcedID. שימו לב שבמאמר זה לא עסקנו בכלל באיך ה-packer הספציפי הזה עובד - כל מטרתנו הייתה לעשות unpack לנוזקה, ובגלל שה-packer הזה (כמו רוב ה-packers) פשוט יחסית, הצלחנו לעשות unpacking בלי לחקור אותו בכלל. במאמרים עתידיים אולי אעסוק ב-unpacking סטטי ובמחקר של packers. אשמח לשמוע שאלות, והצעות למאמרים עתידיים (נוזקות, רברסינג ו-winternals הם התחומים העיקריים שלי, ועל כן אשמח לכתוב עליהם). תודה על הקריאה :upside_down_face:

26 לייקים

וואלה מאמר מעולה, הייתי שמח לשמוע יותר על הקטע עם האנטרופיה כי זה לא היה לי מאוד ברור

לייק 1

בטח. אז בעקרון אנטרופיה זה מדד כלשהו לכמה משהו מסוים הוא “רנדומלי” (ההגדרה הזו צולעת מאוד כי אני לא סטטיסטיקאי, בפועל זה מדד לגודלו האפקטיבי של מרחב הסתברות, אבל זה סתם מילים באוויר בלי שאף אחד מבין). אנטרופיה גבוהה יותר => משהו רנדומלי יותר. ז"א, שככל שהאנטרופיה של קובץ (או של כל מקטע בינארי, כמו section של pe) גבוהה יותר, כך עולה הסיכוי שהוא packed. נתונים דחוסים נוטים להיות עם אנטרופיה גבוהה יותר כי אחת המטרות של דחיסה היא להעלות את האנטרופיה של הנתונים (לצמצם קטעים שחוזרים על עצמם). לכן, אני בוחר להשתמש באנטרופיה כמדד די איכותי עבורי לכמה הסיכוי שסאמפל הוא packed. לרוב מתייחסים לאנטרופיה של 6-6.5+ כאינדיקטור של סאמפל שהוא packed.

8 לייקים

הערה קטנה - @Lit3r4lly שאל אותי בפרטי אם בכוונה לא נגעתי בbroken headers (וסחתיין לו על תשומת הלב), ובמקרה הספציפי הזה הצלחתי לשלוף את הimage בunmapped format אז לא היה צריך לתקן כלום (אחרת pe studio היה צועק עלי), ובאמת במקרים רבים אנחנו מצליחים לשלוף את הimage בmapped format שלו, ואז עלינו לתקן את ההאדרים (בדרכ מדובר בהשוואה של הraw size לvirtual size בכל section, ולפעמים זה מסתבך יותר). לצערי איני יכול לערוך את המאמר ישירות, אחרת כבר הייתי מתקן את זה

4 לייקים

@m4gnum תודה על פוסט מעניין.
מה שציינת על האנטרופיה, איך אתה צופה בנתון? כי מהידע שלי בida זה לא מוצג (או שאני לא מכיר?)

אני בדר"כ משתמש ב-PE Studio עבור זה. הוא אחלה - מציג אנטרופיה גם עבור כל הבינארי, וגם עבור sections ספציפיים

מאוד מעניין,

השילוש הקדוש שלי לחקירת “נוזקות”:

  1. densityscout
  2. pescan
  3. sigcheck

:sweat_smile: :rofl: :face_with_hand_over_mouth:

אולי המילה “חקירה” גדולה מדי… :grimacing:

יש כל מיני כלים שנותנים לך מידע על הבינארי לדוגמא:

  1. PEstudio
  2. detect it easy
  3. peid
  4. exeinfo pe

וכנראה שיש עוד…

ההבדל המרכזי של pe studio מכל שאר הכלים שציינתי הוא שהוא נותן יותר מידע ומפרסר מידע על הPE מעבר לאנטורפיה ונסיון לזיהוי packer.

סתם מוסיף את הכלים שיהיה לך מגוון :slight_smile:

3 לייקים

ממש יפה, אשמח להבין איך ידעת עד איפה לחלץ מה DUMP את הקובץ PE.
אני מבין למה התחלת מאיפה שהתחלת בגלל ה magic byte, לא הבנתי איפה סיימת ולפי מה ידעת את זה.

בעקרון אין בעיה שתעשה דאמפ מעבר לגבולות הPE האמיתיים, אם תעתיק את הnull bytes שאחרי הקובץ בזכרון זה לא אמור לשנות כלום מבחינת רב הכלים שתריץ על הdump.

כדי לעשות את החיים קצת יותר קלים יש כל מיני pluginים לx64 וכלים שעוזרים לעשות דאמפ ו"לתקן" את הImport table.

plugin לדוגמא:

scylla

לייק 1

היי

אני אשמח לעוד כאלו כתבות ממש יפה ומעניין, וכ"כ אשמח להמלצות על איך להתקדם בתחום הזה

תודה

כתבה ממש מעניינת אשמח אם תמשיך