8.2 ארכיטקטורות ARM ו MIPS תרגול
תרגול - ארכיטקטורות ARM ו-MIPS¶
בתרגול הזה נצא מהעולם הנוח של x86 ונקרא אסמבלי ARM אמיתי. המטרה: להתמודד עם אתגר leg מ-pwnable.kr, שכולו בנוי סביב טריק ה-pc+8 של ARM ואחיו הקטן ה-pc+4 של Thumb. בדרך תקימו סביבת עבודה לבינאריים זרים (qemu ו-gdb-multiarch), ותסגרו עם תרגיל MIPS קצר על חריץ ההשהיה.
רקע על האתגר - leg¶
האתגר יושב בקטגוריית Toddler's Bottle. מתחברים אליו כך:
בתיקיית הבית תמצאו שלושה קבצים:
הקובץ leg.c הוא קוד המקור, ו-leg.asm הוא הפריקה של הפונקציות ל-ARM אסמבלי (בדיוק כמו פלט של objdump -d). התוכנית מבקשת מספר עם scanf("%d", &key), ובודקת אם הוא שווה ל-key1() + key2() + key3(). אם כן - היא פותחת את הקובץ flag ומדפיסה אותו. אין כאן buffer overflow ואין exploit זיכרון: כל האתגר הוא לקרוא נכון שלוש פונקציות ARM זעירות ולחשב את הסכום.
הנה שלושת גופי הפונקציות מתוך leg.asm (הכתובות מגיעות מהבינארי של האתגר - כשאתם עובדים מול העותק שלכם, קראו את הכתובות מ-leg.asm שלו):
key1:
0x00008cd4 <+0>: push {r11}
0x00008cd8 <+4>: add r11, sp, #0
0x00008cdc <+8>: mov r3, pc
0x00008ce0 <+12>: mov r0, r3
0x00008ce4 <+16>: ...
0x00008cec <+24>: bx lr
key2:
0x00008cf0 <+0>: push {r11}
0x00008cf4 <+4>: add r11, sp, #0
0x00008cf8 <+8>: push {r6}
0x00008cfc <+12>: add r6, pc, #1
0x00008d00 <+16>: bx r6
0x00008d04 <+20>: mov r3, pc ; .code 16 (Thumb state starts here)
0x00008d06 <+22>: adds r3, #4
0x00008d08 <+24>: push {r3}
0x00008d0a <+26>: pop {pc}
0x00008d0c <+28>: pop {r6} ; .code 32 (back to ARM state)
0x00008d10 <+32>: mov r3, r6
0x00008d14 <+36>: mov r0, r3
0x00008d18 <+40>: pop {r11}
0x00008d1c <+44>: bx lr
key3:
0x00008d20 <+0>: push {r11}
0x00008d24 <+4>: add r11, sp, #0
0x00008d28 <+8>: mov r3, lr
0x00008d2c <+12>: mov r0, r3
0x00008d30 <+16>: ...
0x00008d3c <+24>: bx lr
וקטע מ-main, כדי שנדע מאיפה קוראים ל-key3:
main:
...
0x00008d78 <+...>: add r4, r4, r3
0x00008d7c <+...>: bl 0x8d20 <key3>
0x00008d80 <+...>: mov r3, r0
...
תרגיל 1 - הקמת סביבה לבינאריים זרים¶
לפני שנוגעים ב-leg, נוודא שאתם יכולים להריץ ולנפות באגים בקוד ARM על מכונת ה-x86 שלכם.
התקינו את הכלים:
sudo apt install qemu-user qemu-user-static gdb-multiarch
sudo apt install gcc-arm-linux-gnueabi gcc-mips-linux-gnu
כתבו תוכנית "שלום עולם" קטנה בשם hello.c, הדרו אותה ל-ARM סטטי, והריצו אותה תחת האמולטור:
arm-linux-gnueabi-gcc -static -o hello_arm hello.c
file hello_arm # should say: ELF 32-bit ARM
qemu-arm ./hello_arm
המטרה: לראות את הפלט של תוכנית ARM רצה על מעבד אינטל.
רמז: אם qemu-arm צועק שאין לו את המקשר הדינמי, הדרתם דינמית - הוסיפו -static, או הצביעו על הספריות עם qemu-arm -L /usr/arm-linux-gnueabi ./hello_arm.
תרגיל 2 - חישוב key1 עם טריק ה-pc+8¶
זו הפונקציה הקלה, וכבר ראיתם אותה בהרצאה. ההוראה המעניינת היחידה היא mov r3, pc בכתובת 0x8cdc, ואנחנו במצב ARM רגיל.
המשימה: חשבו איזה ערך key1 מחזירה.
רמז: במצב ARM, ה-pc שנקרא הוא כתובת ההוראה ועוד שמונה. אל תתפתו לכתוב את כתובת ה-mov עצמה.
תרגיל 3 - חישוב key2 והמלכוד של Thumb¶
כאן מסתתר המלכוד. key2 מבצעת קודם add r6, pc, #1 ואז bx r6. שימו לב לחישוב: ב-0x8cfc המצב עדיין ARM, אז ה-pc שנקרא שם הוא 0x8cfc + 8. הוספת ה-1 מדליקה את הביט הנמוך - וזה בדיוק מה שמעביר את המעבד למצב Thumb כשמבצעים bx r6.
מרגע ש-bx r6 קפצה, אנחנו במצב Thumb (שימו לב ל-.code 16), וההוראה mov r3, pc ב-0x8d04 פועלת לפי כללי Thumb.
המשימה: חשבו איזה ערך key2 מחזירה, שלב אחר שלב.
- רמז 1: במצב Thumb, קריאת
pcמחזירה כתובת ההוראה ועוד ארבע, לא ועוד שמונה. אם תשתמשו ב-8 כאן, תקבלו תשובה שגויה. - רמז 2: אחרי שחישבתם את הערך ב-
mov r3, pc, אל תשכחו את ההוראה שאחריה:adds r3, #4מוסיפה עוד ארבע. - רמז 3: הערך שיוצא מ-
r3הוא מה שנדחף ל-pc(דרךpush {r3}/pop {pc}), ובסוף מועתק ל-r6ומשם ל-r0- כלומר הוא ערך החזרה.
תרגיל 4 - חישוב key3 מתוך אוגר הקישור¶
הפונקציה key3 עושה דבר אחד מעניין: mov r3, lr. כלומר היא מחזירה את הreturn address שלה עצמה.
המשימה: חשבו איזה ערך key3 מחזירה.
רמז: lr נקבע על ידי הוראת ה-bl שקראה ל-key3. חפשו ב-main את השורה bl 0x8d20 <key3>, ותנו את הכתובת של ההוראה שמיד אחריה. זו הreturn address, וזה מה ש-key3 מחזירה.
תרגיל 5 - הרכבת המפתח וקבלת הדגל¶
עכשיו יש לכם את שלושת הערכים. סכמו אותם (הם ערכים הקסדצימליים - שימו לב, המרו לעשרוני כי scanf("%d") מצפה למספר עשרוני), הכניסו את התוצאה לתוכנית, וקבלו את הדגל.
המשימה: הזינו את המספר וקבלו את הדגל.
רמז לאימות מקומי: אתם לא חייבים לסמוך על הראש. הדרו את leg.c שקיבלתם ל-ARM והריצו תחת qemu כדי לבדוק את המספר שלכם:
arm-linux-gnueabi-gcc -static -o leg leg.c
echo "108400" | qemu-arm ./leg # replace with the number you calculated
שימו לב: אם תדרו מחדש עם מהדר אחר, הכתובות ב-objdump עשויות להשתנות, ואיתן גם המפתח. לכן כשבודקים מקומית, חשבו את המפתח מחדש מתוך ה-leg.asm שהמהדר שלכם ייצר. הערך "הקנוני" של האתגר תקף לבינארי המקורי של pwnable.kr.
תרגיל 6 - חריץ ההשהיה של MIPS¶
לסיום, תרגיל קריאה קצר ב-MIPS כדי לתרגל את חריץ ההשהיה. נניח שקימפלתם את הפונקציה הבאה ל-MIPS וקיבלתם את האסמבלי:
המשימה: איזה ערך הפונקציה מחזירה? הסבירו למה, ואמתו על ידי הדרה והרצה:
רמז: זכרו את הכלל - ההוראה שמיד אחרי jr יושבת בחריץ ההשהיה ורצה תמיד, לפני שהחזרה נכנסת לתוקף.