ABA


"חידה בC ובאבטחה. (עודכן+ אתגרון)"
גירסת הדפסה        
קבוצות דיון פיתוח, תיכנות ובניית אתרים נושא #5132 מנהל    סגן המנהל    מפקח   Winner    צל"ש   מומחה  
אשכול מספר 5132
dryice

   16:21   18.02.03   
אל הפורום  
  חידה בC ובאבטחה. (עודכן+ אתגרון)  
 
   עבר עריכה לאחרונה בתאריך 23.02.03 בשעה 21:03
 
החידה הראשונה הכתובה פה נפתרה ע"י AVI885, כל הכבוד, יש כעת
בתגובה מספר 1 חידות חדשות, שיזכו בניקוד כחלק מהאתגרונים של פורום
תיכנות.
(מי שקורא רק עכשיו, לצורך הבנה קראו גם החידה הראשונה)

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


int main()
{
char buff[200];
gets(buff);
return 0;
}

מעבר לעובדה שהתוכנית לא עושה הרבה, איפה יש באג מהותי בקוד?

והשאלה היותר מעניינת, כיצד תוכלו לנצל באג זה בשביל להשתלט לי על
המחשב, arbitrary code execution


זרקו נא תשובות חופשי.

DRYICE
(באם תפתרו את זה, יש לי חידות המשך)


                                שתף        
מכתב זה והנלווה אליו, על אחריות ועל דעת הכותב בלבד

  האשכול     מחבר     תאריך כתיבה     מספר  
  גודל ה BUFFER avi885 18.02.03 17:16 1
     נכון מאוד, וכעת השאלה הקשה:(עם ניקוד) dryice 18.02.03 17:54 2
         טוב, אחרי שהסברת את עצמך, חשבתי על הדבר הבא: Dudenland 19.02.03 00:15 6
             לא ולא, ממש לא. dryice 19.02.03 10:42 7
  BUFFER OVERRUN בסיסי, הסבר: dryice 18.02.03 18:02 3
     תהיתי אם תואיל בטובך, ותוכל לתת קלט דוגמה... Dudenland 18.02.03 19:56 4
         מבנה המחסנית: dryice 18.02.03 20:15 5
  מחר בלילה/יום שישי אפרסם פתרונות מלאים. dryice 19.02.03 19:42 8
  רמז: מה עושה %n בפקודת printf dryice 20.02.03 02:58 9
     Injection avi885 20.02.03 10:33 10
         נכון, אבל איך נריץ קוד משלנו? dryice 20.02.03 16:43 11
             האם זה נכון רק ללינוקס avi885 20.02.03 19:55 12
                 זה נכון לכל מערכת הפעלה. dryice 21.02.03 15:56 13
  פתרונות: dryice 21.02.03 22:24 14
     יש לי כמה שאלות שנוגעות לעניין: Dudenland 22.02.03 14:15 15
         מישהו ? Dudenland 25.02.03 13:18 16

       
avi885

   17:16   18.02.03   
אל הפורום  
  1. גודל ה BUFFER  
בתגובה להודעה מספר 0
 
   כנראה שגודל ה BUFFER לא נבדק וניתן לעשות מה שנקרא BUFFER OVERRUN כלומר להכניס בתור פרמטר STRING גדול מ 200 תווים כאשר החלק אחרי ה 200 יהיה קוד בשפת מכונה וכאשר התוכנית תבצע RETURN היא למעשה תחזור לקוד זה ושם ניתן להכניס תוכנית כרצוננו.


                                                         (ניהול: מחק תגובה)
מכתב זה והנלווה אליו, על אחריות ועל דעת הכותב בלבד
dryice

   17:54   18.02.03   
אל הפורום  
  2. נכון מאוד, וכעת השאלה הקשה:(עם ניקוד)  
בתגובה להודעה מספר 1
 
   עבר עריכה לאחרונה בתאריך 18.02.03 בשעה 17:57
 
נתון הקוד:

int main()
{
char buff[200];
fgets(buff,200,stdin);
printf(buff);
return 0;
}

כיצד ננצל קוד זה בשביל להשתלט על המחשב?

במסגרת האתגרונים שלנו, לפותר הראשון ינתנו 13 נקודות, וכן
5 נוספות, ינתנו למי שיידע להציג תוכן מלא לbuff שיעשה משהוא.

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

לאחר חידות אלו נעשה סיכון לנקודות, ונתן WINNER בהתאם.

DRYICE


                                                         (ניהול: מחק תגובה)
מכתב זה והנלווה אליו, על אחריות ועל דעת הכותב בלבד
Dudenland

   00:15   19.02.03   
אל הפורום  
  6. טוב, אחרי שהסברת את עצמך, חשבתי על הדבר הבא:  
בתגובה להודעה מספר 2
 
   בקטע הקוד שנתת, הפונקציה main, מקצה על המחסנית זיכרון בגודל 200 בתים, עבור המשתנה buff, ומבקשת מהמשתמש להכניס קלט.
מכיוון שזוהי הפונקציה fgets, שבניגוד ל-gets, אינה מאפשרת לעבור את גבול הזיכרון שהוקצה לה, צריך קצת להתחכם:

ע"י Debugger, ניתן לבדוק, כנראה, היכן מתחילה המחסנית, והיכן היא מסתיימת...כמו כן, ניתן לראות את המבנה שלה (מקום שמור ל-buff, לערך המוחזר, לכתובת החזרה וכו'...).

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

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

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

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

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


                                                         (ניהול: מחק תגובה)
מכתב זה והנלווה אליו, על אחריות ועל דעת הכותב בלבד
dryice

   10:42   19.02.03   
אל הפורום  
  7. לא ולא, ממש לא.  
בתגובה להודעה מספר 6
 
   אם יש לך יכולת לשנות את האוגרים של המעבד, הרי אתה כבר יכול
להריץ קוד כרצונך על המחשב, למה להסתבך. המטרה היא להריץ קוד כרצונך על
המחשב, כאשר כל מה שיש לך שליטה עליו זה הקלט הסטנדרטי לתוכנית, stdin
של התוכנית מגיע מהמחשב שלך, וזהוא!

DRYICE


                                                         (ניהול: מחק תגובה)
מכתב זה והנלווה אליו, על אחריות ועל דעת הכותב בלבד
dryice

   18:02   18.02.03   
אל הפורום  
  3. BUFFER OVERRUN בסיסי, הסבר:  
בתגובה להודעה מספר 0
 
   בהתאם למה שכתב avi885 לעיל, נתן הסבר מלא יותר:

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

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

DRYICE


                                                         (ניהול: מחק תגובה)
מכתב זה והנלווה אליו, על אחריות ועל דעת הכותב בלבד
Dudenland

   19:56   18.02.03   
אל הפורום  
  4. תהיתי אם תואיל בטובך, ותוכל לתת קלט דוגמה...  
בתגובה להודעה מספר 3
 
   ולא הבנתי משהו:
נניח שעל מחסנית הוקצו 20 בתים עבור הפונקצייה.
המשתנה יתפוס X בתים, והכתובת חזרה תתפוס, סביר להניח, בית אחד.
עכשיו, נשאלת השאלה, איפה ???
איך בדיוק בנוי הזיכרון שהוקצה על המחסנית עבור הפונקצייה ???
איך המחשב יודע לקרוא דווקא את אותו בית בודד שעליו מוקצת הכתובת לחזרה ??? האם זה דבר הקבוע מראש לאחר הקומפליקציה ??? כלומר, בקטע הקוד, המחשב מורה לקרוא, נניח את הבית ה-20, שעליו אנחנו נכניס כתובת שונה, שהיא בעצם כתובת של המשתנה Buff, ואז הוא יבצע את הקוד ???
רציתי לדעת איך אתה מסוגל לדעת באיזה בית (כלומר באיזה אינדקס במחרוזת) להכניס את הכתובת החדשה, ואיך אתה יודע לאן להחזיר ???
כמו כן, רציתי לדעת איך אני, ככותב תוכנית, מונע ממשתמש לבצע דב כזה ???

בתודה Dudenland


                                                         (ניהול: מחק תגובה)
מכתב זה והנלווה אליו, על אחריות ועל דעת הכותב בלבד
dryice

   20:15   18.02.03   
אל הפורום  
  5. מבנה המחסנית:  
בתגובה להודעה מספר 4
 
   כאשר קוראים לפונקציה, ראשית נדחפים על המחסנית הפרמטרים שלה
לאחר מכאן נדחפת כתובת החזרה(שזאת מילת מחשב, 16 או 32 ביט)
לאחר מכאן יש איזה ערך BP לא חשוב לענינינו ואז נמצאים
כל המשתנים המקומיים של הפונקציה:

למשל הקוד לעיל על המחסנית יראה כך:
Return address: 1000
Base Pointer, BP: 998
Buffer: 798-997

המספר 1000 נבחר שרירותית. (יש לשים לב שהמחסנית צומחת למטה
במעבדי אינטל.)
אם המשתמש ניגש לתא הראשון בBUFFER הוא ניגש לכתובת 798 אם הוא ניגש
לערך האחרון, הוא יגש לכתובת 997, אבל עם אני נותן לו אינדקס גדול יותר
201 או 202 אני אחרוג מהמקום שהוקצה לBUFFER בזכרון ואכתוב על המקום
של הBP, שזה וודאי לא יעזור לתוכנית לרוץ נכון, אבל לא זה העניין שלנו,
כאשר נמשיך לגשת לאינדקסים גבוהים מידי, 203,204 אנו נכתוב מידע
מעל כתובת החזרה. במקומות אלו בBUFFER שלנו אנו נכתוב את הכתובת של
הBUFFER שלנו בזכרון על המחסנית 997, ואז כאשר נחזור מהפונקציה
המעבד יקפוץ לכתובת 997 שזה איפה אנחנו שמנו את הקוד הזדוני שלנו.

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

DRYICE


                                                         (ניהול: מחק תגובה)
מכתב זה והנלווה אליו, על אחריות ועל דעת הכותב בלבד
dryice

   19:42   19.02.03   
אל הפורום  
  8. מחר בלילה/יום שישי אפרסם פתרונות מלאים.  
בתגובה להודעה מספר 0
 
  


                                                         (ניהול: מחק תגובה)
מכתב זה והנלווה אליו, על אחריות ועל דעת הכותב בלבד
dryice

   02:58   20.02.03   
אל הפורום  
  9. רמז: מה עושה %n בפקודת printf  
בתגובה להודעה מספר 0
 
  


                                                         (ניהול: מחק תגובה)
מכתב זה והנלווה אליו, על אחריות ועל דעת הכותב בלבד
avi885

   10:33   20.02.03   
אל הפורום  
  10. Injection  
בתגובה להודעה מספר 9
 
   OK אז כמובן שהרמז עוזר מאוד.
מכיוון שהפונקציה fgets בודקת את גודל ה Buffer לא ניתן כאן להשתמש ב Buffer Overrun.

השיטה לפריצה אליה אתה מכוון נקראת Injection כלומר "הזרקה"

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

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

הפרמטר n% אומר ל printf לא להדפיס כלום וארגומנט הוא מצביע לכתובת בה מאוחסנים מספר התווים שהודפסו עד כה.

אם נכניס כקלט משהו כמו 666,"n%" נוכל לגרום לפונקציה להגיע לכתובת אשר לא שייכת לתוכנית.

יש type נוסף עימו אולי ניתן להשתמש: p%.


                                                         (ניהול: מחק תגובה)
מכתב זה והנלווה אליו, על אחריות ועל דעת הכותב בלבד
dryice

   16:43   20.02.03   
אל הפורום  
  11. נכון, אבל איך נריץ קוד משלנו?  
בתגובה להודעה מספר 10
 
   אנחנו לא שלחנו לprintf שום ארגומנט מלבד המחרוזת, כיצד נשתמש
ב%n בשביל מידע בעל משמעות, למקום בעל משמעות בזכרון?

שוב המטרה היא לתת לprintf איזה שהוא format string שיגרום לתוכנית
להריץ קוד כרצוני!

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

(המרצה, שחר שמש, נתן לי כתרגיל למחשבה את הבעיה עם הprintf,
ואני דיי משוכנע שיש לי פתרון מלא ונכון)

DRYICE
נ.ב
%p מדפיס מצביע בייצוג הקסה-דסימלי, ממש נשגב מבינתי
איך זה יכול לעזור.


                                                         (ניהול: מחק תגובה)
מכתב זה והנלווה אליו, על אחריות ועל דעת הכותב בלבד
avi885

   19:55   20.02.03   
אל הפורום  
  12. האם זה נכון רק ללינוקס  
בתגובה להודעה מספר 11
 
   או לכל מערכת הפעלה?

האם כל הקומפיילרים של C עובדים באותה שיטה בדיוק?

האם זה נכון גם לשפות אחרות או בכל שפה מבנה הזכרון אחר?
האם יש לך אתרים בנושא מבנה הזכרון של תוכנית בשפת C?

אני מנסה בינתיים לפתור את שאלתך.

תודה


                                                         (ניהול: מחק תגובה)
מכתב זה והנלווה אליו, על אחריות ועל דעת הכותב בלבד
dryice

   15:56   21.02.03   
אל הפורום  
  13. זה נכון לכל מערכת הפעלה.  
בתגובה להודעה מספר 12
 
   DRYICE


                                                         (ניהול: מחק תגובה)
מכתב זה והנלווה אליו, על אחריות ועל דעת הכותב בלבד
dryice

   22:24   21.02.03   
אל הפורום  
  14. פתרונות:  
בתגובה להודעה מספר 0
 
   אנו משתמשים ב%n ,משום שלא שלחנו פרמטרים לprintf הוא תופס
בתור פרמטר את הדברים שהיו לפני כתובת החזרה מהפונקציה,
על כן נשים %d ואחריו %n הd יתפוס את הBP והn תופס את כתובת החזרה
מהפונקציה שקראה לprintf, (אם היו משתנים מקומיים, צריך לדלג עליהם
ע"י הוספת עוד %c כרצוננות, שתופסים תו בודד מהמחסנית)

אנו מתייחסים לכתובת החזרה כאילו הוא int * ואנו כותבים מספר התווים
שהדפסנו אל הכתובת שמוצבעת ע"י כתובת החזרה, לכשנחזור, המעבד יבצע
את הפקודה, וזה יהיה המספר שרשמנו.

אבל אנו עדיין צריכים לכתוב שם, מספר כרצוננו. ואילו עם הBUFFER שלנו
קטן, ו%n כותב את מספר התווים שהדפסנו עד כה, ועל כן אנו נרצה שיודפסו
הרבה תווים, כמות כרצוננו. אנו יכולים לעשות זאת על ידי כתיבת מספר
בתוך המחרוזת כמציין עורך ל%d וכך עם אנו רוצים למשל להקיץ פקודה
שיוצגה בעשרוני: 5017 אז נכתוב מחרוזת:
"%5017d%n"
נוכל כמובן לרשום פקודה שתקפוץ לתוך הBUFFER שלנו.


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

DRYICE


                                                         (ניהול: מחק תגובה)
מכתב זה והנלווה אליו, על אחריות ועל דעת הכותב בלבד
Dudenland

   14:15   22.02.03   
אל הפורום  
  15. יש לי כמה שאלות שנוגעות לעניין:  
בתגובה להודעה מספר 14
 
   עבר עריכה לאחרונה בתאריך 22.02.03 בשעה 14:28
 
1. מה מציין הביטוי (ארגומנט במחרוזת) %10n (כמו שיש למשל בשביל ליצור שדה של 8 מספרים שלמים - %8d).

2. מה מציין הביטוי %$10n.

3. איזה מחרוזת תשמש (בעזרת %n) אותנו כדי לכתוב ביטוי מסויים, למשל 0xBF, לכתובת מסויימת בזיכרון, למשל 0xB1D873FF.

4. איפה (באיזה כתובת בזיכרון) בדיוק ה%n שם את הערך שנתת לו ?
כתבת שזה לכתובת חזרה של הפונקצייה שקראה ל-printf. למה שזה יכתוב את זה שם ? איך אני מוצא (ב-Debbug) את הכתובת חזרה של פונקצייה מסויימת ?

Dudenland


                                                         (ניהול: מחק תגובה)
מכתב זה והנלווה אליו, על אחריות ועל דעת הכותב בלבד
Dudenland

   13:18   25.02.03   
אל הפורום  
  16. מישהו ?  
בתגובה להודעה מספר 15
 
  


                                                         (ניהול: מחק תגובה)
מכתב זה והנלווה אליו, על אחריות ועל דעת הכותב בלבד

תגובה מהירה  למכתב מספר: 
 
___________________________________________________________________

___________________________________________________________________
למנהלים:  נעל | תייק בארכיון | מחק | העבר לפורום אחר | מחק תגובות | עגן אשכול
       



© כל הזכויות שמורות ל-רוטר.נט בע"מ rotter.net