Disassembly - basics

תהליך הקומפילצייה

Pasted image 20230120004816.png|350

נרצה להבין כיצד ניתן להשיג מידע מהתהליך ההפוך כלומר לקחת את ה executable ולהתחיל לנתח את הקוד שלו. מעין די-קומפילצייה או במונחים מקצועיים יותר Disassembly ו reverse engineering.

How to Disassembly

נשתמש ב objdump שזה בעצם CLI כדי להציג מידע על קבצי object במערכות הפעלה מבוססות UNIX. הוא משוייך לספריות העזר של GNU ולכן הוא מובנה בלינוקס, זאת הסיבה שגם נשתמש בו.

על ידי שימוש בפקודה

objdump-d a.out

נקבל קוד שדומה לאסמבלי שמייצג את הקוד שמופיע בקבצי .c שלנו.

פקודה נוספת שיכולה להיות שימושית היא

objdump -t a.out

שתדפיס לנו את כל הsymbol table של הקובץ. הטבלה תכיל את כל הmetadata שקשורות לשמות המשתנים , הכתובות והשמות של המשתנים הגלובליים. הפקודה הזאת תביא לנו את כל ה labels בתוכנית.

הפקודה strings a.out ידפיס את כל הסטרינגים בתוכנית.

כמה נקודות חשובות
a) בתהליך הדיסאסמבלי תמיד נקבל רק את הקוד של הפונקציות שנעשה בהם שימוש באותו קובץ.
b) לא נראה מימושים לפונקציות מערכת כמו printf ..
c) לא נראה ערכים של משתנים גלובלים או של סטרינגים.
d) הרבה פעמים יש אופטימיזציות או nops שהקומפיילר מוסיף שלעתים מקשות על ההבנה. שתי דוגמאות למשל

a) nop
b) xchg %cx, %cx

nop- פקודה שלא עושה כלום no operation . יש לזה שימוש בהקשרים של תזמון וניהול זכרון או סנכרון של ה cpu .

ניתוח של קוד באמצעות Disassembly

נתחיל לנתח מהפונקצייה main (לעתים נראה פונקציות גלובליות נוספות כמו init ו start אבל נוכל להתעלם מהם).
Pasted image 20230120010339.png
ניתן לראות שתי דברים חשובים.
א) משמאל לפקודה יש את ערך הבייט שמייצג את הפקודה עצמה למשל push %rbp מיוצג על ידי בייט אחד שערכו 55 .
ישנם תבניות שחוזרות על עצמן שקל לשים לב אליהן למשל הבייט שערכו b8 מייצג את הפקודה mov to eax . השורה החמישית הפקודה אומרת להזיז את הint (כי ארבעה בתים) שמיוצג על ידי

04  00  00  00  00

ל edi שמיוצג על ידי בייט אחד שערכו bf .
כמו כן נשים לב שניתן לראות מהייצוג שמדובר במערכת Little Endian
(Little and Big Endian) .

ב) בעמודה הכי שמאלית ניתן לראות שמדובר בכתובות של השורות קוד עצמן ב text segment . והקפיצות הן לפי כמות הבייטים שהפקודה עצמה דורשת.

ג) נשים לב לקריאות callq היא מראה את ה label של הפונקצייה שלה קוראים ואת הכתובת שלה בtext segment. נוכל ממש לחפש את הכתובת ולהגיע לקוד של hello .

Pasted image 20230120011216.png|300
כאן נשים לב שיש שתי דברים מעניינים. ראשית הפקודה

mov $0x4006bd, %edi

על פניו נראה טריוויאלי אבל אם נחפש את הכתובת הזאת אנחנו לא נמצא בניגוד לכתובת של hello שכן מצאנו.
מיד לאחר מכן אנחנו קוראים לכתובת 400490 שזה קריאה לפונקצייה puts .
אם נראה מה היא עושה אנחנו רואים שהיא פשוט מדפיסה סטרינג. כמו כן אנחנו יודעים מ Functions in assembly ש edi הוא קונבצייה לפרמטר הראשון שמעבירים לפונקצייה. אם נסתכל על הדוקומנטצייה של puts נראה ש


//The `puts()` function writes the given string to the standard output stream `stdout`; it also appends a new-line character to the output. The ending null character is not written.

int puts(const char *string)

אם כן אנחנו מבינים שהשורה מעל ה call מייצגת את הכתובת לסטרינג הזה. כפי שאמרנו אין לנו גישה לערכים של סטרינגים. כאן נכנס לתמונה gdb שיאפשר לנו להדפיס בזמן ריצה ערכים של כתובות.

חשוב לציין שגם ל puts אין לנו גישה באמת בתהליך הדיסאסמבלי בגלל שזאת פונקציית ספרייה

כעת נחזור בחזרה ל main של אחרי הקריאה ל hello.
ישנה קריאה נוספת:

callq 4005cd <even>

Pasted image 20230120012048.png|400

לפני שצוללים לקוד כל כך מורכב כדי ברמה האבסטרקטית להבין מה קורה במחסנית ולנסות להבין מה הקוד עושה מהשם ומהקוד שקורא לו.

הבלוק המפחיד של הקוד הזה הוא הבלוק הבא
Pasted image 20230120012444.png|350
נשים לב שלפני כן יש את התהליך ההתחלתי הסטנדרטי להזזת המצביעים לראש המחסנית , מיד לאחר מכן אנחנו רואים שיש הקצאה של 0x20 בייטים למחסנית ומ base convertors אנחנו יודעים שזה מספר בבסיס 16 שערכו 32 בבסיס עשרוני כלומר שמרו כאן על התכונת הaligned של 8 בייטים.
נשים לב שבסוף התוכנית לפני ה״בלוק המוזר״ יש הכנסה של 0xa ל edi כלומר מתכוננים לקריאת פונקצייה ואז

 callq 400480 <putchar@plt>

נשים לב שה @plt מייצג איזשהי קריאה לפונקצייה שמגיע מתהליך הלינקוז׳ procedure linkage table . זה רמז לכך שזאת קריאה לפונקציית ספרייה.

כעת נתחיל להסתכל על ה״בלוק הבעייתי״ מלמעלה.
בשורה השלישית יש קפיצה לשורה מסויימת בתוך even מה שמרמז לנו על if else או לולאה בקוד המקורי. אם נלך לכתובת שאליה קופצים אנחנו נגיע לקוד הבא

cmpl $0x9, -0x4(%rbp)
jle 4005e1 <even+0x14>

כלומר הקפיצה הזאת היא לפקודה cmpl שאנחנו יודעים מ control flow in assembly שמדובר בהשוואה בין שתי ערכים, ניתן לראות שמדובר בהשוואה למספר 9 עם הערך שנמצא 4 בייטים מתחת לכתובת שעליה נמצא כעת rbp. למה מתחת? כי לפי Program structure in assembly אנחנו יודעים שהמחסנית מתחילה מכתובות גבוהות ויורדת בערכיה לאט לאט. כלומר מדובר באיזה ערך ששמרנו במחסנית בסקופ של הפונקצייה עצמה . סך הכל מה שקורה פה בקוד קריא:

arg1 = Mem[-0x14(%rbp)]; //this is an input value that is saved in stack
value = 0;
while (value <= 9) {
	print("the number is %d" , arg1+value);
	value += 2;
}

הסיבה שזה לולאה כלשהו היא בגלל השורה שמתחת להשוואה שמקפיצה אותנו בחזרה למעלה לשורה הראשונה מתחת ל jmp שמוביל לבדיקה.
מכאן אפשר להתחיל לנתח את המחסנית עבור הקוד הזה ולהבין איך מגיעים לקוד c הנ״ל.

לבסוף חוזרים ל main ומסיימים את התוכנית.

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

void hello() {
	printf("Hello there!\n");
}

void even(int a) {
	int i;
	for(i=0; i<10; i+=2) {
		printf("%d", (a+i));
	}
	printf("\n")
}

נשים לב שאומנם קראנו ל printf בקוד המקורי אבל באופטימיזציות קראנו ל puts שהקומפיילר החליט שזה יותר יעיל.