לדלג לתוכן

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. מתחברים אליו כך:

ssh leg@pwnable.kr -p2222
# password: guest

בתיקיית הבית תמצאו שלושה קבצים:

ls
# leg.c  leg.asm  flag
cat leg.c
cat leg.asm

הקובץ 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 וקיבלתם את האסמבלי:

get_val:
    li      $v0, 5
    jr      $ra
    li      $v0, 7

המשימה: איזה ערך הפונקציה מחזירה? הסבירו למה, ואמתו על ידי הדרה והרצה:

mips-linux-gnu-gcc -static -o mips_demo mips_demo.c
qemu-mips ./mips_demo

רמז: זכרו את הכלל - ההוראה שמיד אחרי jr יושבת בחריץ ההשהיה ורצה תמיד, לפני שהחזרה נכנסת לתוקף.