גלישת חוצץ, גלישות זכרון ותכנות נכון

מדריך זה ינסה להסביר קצת על פרצות האבטחה בשם "גלישת חוצץ" (Buffer Overflow) וגלישה נומרית (Integer Overflow), על הנזקים שגלישות אלו יכולות לעשות ומעט על תכנות נכון. עם זאת, טכניקה זו דומה מאוד להזרקות SQL, לכן אולי כדאי גם לקרוא את הנושא על SQL Injections ולהיות בקיא בנושא.

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

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

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

סוג נוסף של גלישה היא הגלישה הנומרית. שבמקרים רבים משעשעת הרבה יותר. כשאנו מתעסקים עם מספרים, הרבה פעמים נצטרך לבחור את סוג המשתנה בו נשתמש – במקרים רבים "הבחירה הטבעית" Integer (שמספק לנו טווח של בין 32,000- ל- 32,000+)יספיק לנו, ולא נרצה להשקיע מאמץ גדול בבחירת המשתנה. אבל קורה שאנחנו לא מערכים בצורה נכונה את גודל המשתנה את צורת ההצגה שלו וכדומה. במקרים כאלה, נוצרים באגים שנובעים מהכנסה של ערך גדול מדי. במקרים מסוימים, ייכנס הערך המקסימלי אליו יכולנו להגיע. במקרים אחרים, ייכנסו רק מספר הספרות האחרונות, ובכך נאבד חלק חשוב מהמספר אליו התכוונו (ראו ערך באג 2000. מכיוון שהשנים היו מיוצגות בשתי ספרות, לאחר השנה 99 הגיעה השנה 00) או בשיגור של משגר הלוויינים אריאן 5 (שנחל כישלון, אתם יכולים להניח) תוצאות מוזרות אפילו יותר יכולות להתקבל.

ידע בשפת C נדרש.

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

  • gets()
  • memcpy()
  • strcpy()

בצורה הזו, אנו יכולים להכניס אל הזיכרון קטע קוד זדוני שמשפיע על גרעין מערכת ההפעלה (בשפה המקצועית – Shell Code) ולגרום לכך שהמעבד יריץ את הקוד הזדוני. ניתן לעשות זאת על ידי הכנסת מצביע אל הקוד הזדוני שלנו לאוגר בשם EIP (שעובד על עקרון זהה לזה של PSW – מכיל בתוכו הצבעה לפקודה הבאה שצריכה להתבצע) ולשם להכניס קטע קוד שייתן לנו הרשאות בצורה מסודרת.

הוסף תגובה