
הטקסט המלא של הדקות הראשונות בהרצאה «הסבר על התקפה - buffer overflow» בקורס הווידאו של PracticU – כתוביות בעברית עם זמן התחלה לכל פסקה. כך תוכלו לקרוא ולחפש את הנושאים לפני שמתחילים ללמוד.
אני רוצה להזכיר לכם שכל מה שאנחנו עושים זה דרך שדה של יוזרניים. אנחנו באים לחלון לוגין, אנחנו לא יודעים כלום, לא יוזרניים, לא פספורט, סתם בחלון לוגין אנחנו מכניסים יוזרניים מזויף, משהו ענקי, קוד זדוני במקום היוזרניים, מתכנת של סינגבריז לא בדק את הגודל של היוזרניים
שהכנסנו, ואנחנו דורסים עם היוזרניים הזה את כל הנתונים שיש לנו בסטייק, כולל כתובת חזרה בסיום הפונקציה, כולל כתובת EIP שמגובה בתוך הסטייק הזה.
אז אנחנו דורסים את כתובת החזרה, ואז כשהפונקציה מסתיימת, המעבד לא חוזר למי שקרא לפונקציה הזאת, אלא קופץ לכתובת שאני הכנסתי לו, שאני דרסתי.
וזה העיקרון של הפריצה. אנחנו בחלון של יוזרניים מכניסים אינפוט במקום יוזרניים, משהו גדול וענקי, מה שאנחנו רוצים אנחנו יכולים להכניס לתוך היוזרניים, וזה נכנס לתוך זיכרון בתא מסוים, בתוך הסטייק, ואנחנו צריכים על ידי האינפוט הזה שאנחנו מכניסים להגיע למצב שאנחנו יודעים בדיוק איך לדרוס את אוגר EIP ואיך להשתלט על כל התוכנה
הזאת, איך לפתוח בזיכרון של התוכנה CMD ולהתחבר ל-CMD הזה ולשלוח פקודות למחשב הזה מרחוק. למעשה אנחנו בתוכנה הזאת שותלים CMD משלנו, שותלים של קוד ופותחים לעצמנו דלת כניסה פנימה.
המגבלה שלנו שאנחנו יכולים להיכנס רק בנקודה מסוימת, אנחנו יודעים להיכנס רק בנקודה אחת בזיכרון שזה היוזרניים הזה, ויחסית לנקודה הזאת אנחנו צריכים לדרוס דברים ביחס אליה.
אנחנו לא יכולים להגיד תלך לפה, תלך לשם. אנחנו נכנסים רק בנקודה אחת, איפה שיש חולשה הזאת שלא בודקים את הגודל של האינפוט. אנחנו נכנסים בתא מסוים בזיכרון ומתחילים לדרוס ואנחנו צריכים להיות חכמים כדי לדרוס בצורה מבוקרת ולדעת בדיוק מה אנחנו דורסים.
אנחנו צריכים לדעת בדיוק באיזה שלב אנחנו בדריסה שלנו מגיעים ל-AIP. באיזה שלב אנחנו מגיעים לראש הסטייק, לראש המחסנית שיהיה בסיום הפונקציה? הרי מתי אנחנו נקבל שליטה?
רק בסיום הפונקציה נקבל שליטה, כשהוא יבוא לשלוף את ה-EIP, ואחרי שהוא ישלוף את ה-EIP הוא למעשה ישלוף לא את הגיבוי המקורי, אלא ישלוף את מה שאני דרסתי, את הכתובת שאני הכנסתי לו.
אז רק כשהוא יסיים את הביצוע של הפונקציה הנוכחית, הוא יסיים את כל הפקודות, הוא ישלוף את הכתובת שאני הכניס לו ל-AIP על ידי דריסה חכמה מה שנקרא אני אדע בדיוק מה אני דורס באיזה שלב ואז המעבד יעבור לבצע את הפקודות שלי שגם את הפקודות שלי אני צריך להכניס בתוך סטאק כי סטאק זאת הנקודה היחידה שיש לי כניסה אליה יש לי רק תא אחד בזיכרון שאני יכול להזין לשם את המידע המקום שאליו
המתכנת בחר לשמור יוזרניים שהזין לו המשתמש יעני אני אז הוא לא בודק את הגודל של היוזרניים אז הוא שומר את מה שאני מזין לו לתוך הזיכרון ואני מנצל את זה גולש מהאזור המותר עובר לאזורים אסורים דורס את הכל בדרך אני צריך לעשות את זה חכם כדי להשתלט על eip לדעת בדיוק איפה נמצא eip ואיפה שנמצא eip אני מכניס
לו כתובת שאני רוצה שהמעבד יתחיל לבצע וגם אני מכניס לו לא סתם שטויות של דריסה אלא גם פקודות שפותחות לי CMD זה הרעיון שימו לב עוד פעם אני יכול להיכנס רק בנקודה אחת בזיכרון רק לתוך סטייק רק לתוך אזור שבו נשמר היוזרניים אין לי גישה לקוד אני נמצא בחוץ אני מזין יוזרניים הוא נשמר בזיכרון פשוט לא שמו עליו מגבלות ואני עושה הכל בדריסה חכמה אני צריך לדעת
בדיוק איפה וכמה אני צריך לדרוס כדי להגיע לגיבוי של EIP גם אותו לדרוס בצורה חכמה ולתת לו את הכתובת בזיכרון שאני רוצה שהמעבד יתחיל לבצע ולמעשה כך אני משתלט על המעבד אני דורס את הגיבוי שלו ובסיום הפונקציה הוא מתחיל לבצע את הפקודות שלי ואנחנו צריכים להיות מאוד מאוד חכמים ואנחנו מתרגלים את המפת זיכרון הזאת בתנאי מעבדה כדי
להגיע בסוף לשרת האמיתי ויהיה לנו סקריפט שפורץ הכל בפקודה אחת בוא נראה מה עשינו בפעם הקודמת אנחנו היינו קרובים ממש להשתלטות ורק עכשיו בהרצאה זו אנחנו נשתלט תכלס.
שימו לב מה עשינו, הגענו למצב שיש לנו עוד D, 1500 פעם פחות מה שהשתמשנו בו לפני כן, ולתוך הבאפר הזה עם עוד D אנחנו נשתול את הקוד הזדוני שלנו.
למעשה המקום הזה של עוד D הוא שמור לקוד הזדוני, שזה ה-של קוד. אלקוד זה טרמינל CMD שייפתח בתוך הזיכרון של התוכנה הזו אנחנו נשתול אותו שם ונפתח אותו ולתוך ה-CMD הזה, לתוך הטרמינל הזה נשלח את הפקודות אז תחכו לסוף ההרצאה לראות שזה באמת מתבצע אבל בואו נראה מה שתלנו לפני ה-D הזה, לפני
ה-Buffer הזה, מה יש לנו מעליו קודם כל השתמשנו ב-Filler, מה זה Filler? פילר זה ממלא מילוי ואנחנו צריכים למעשה למלא מקום ולדרוס זיכרון שבו נמצאים משתנים מקומיים של הפונקציה זאת אומרת בקוד של הפונקציה יש כל מיני משתנים מקומיים שהפונקציה שומרת שם כל מיני דברים
שלה אז המשתנים המקומיים האלו בתוך הקוד של הפונקציה אנחנו צריכים קודם כל לדרוס אותם אז אנחנו דורסים אותם ברגע שאנחנו מסיימים לדרוס אותם אנחנו מגיעים לחלק הכי חשוב שזה הכתובת לגיבוי של EIP. הפונקציה בהתחלה במעמד ההפעלה שלה שומרת את כתובת של EIP
שאליו היא צריכה לחזור זאת אומרת פונקציה מגבה את כתובת של פקודה הבאה לביצוע בתום ההפעלה שלה לאן היא צריכה לחזור אחרי שהיא תסיים את כל הקוד שלה למעשה מי התוכנית הראשית שקרה לה.
מישהו הרי קרא לפונקציה, אז פה מגובה הכתובת שאליה צריך לחזור בתום ביצוע הקוד של הפונקציה. ואנחנו דורסים את הכתובת לחזרה ומכניסים את הכתובת שלנו.
זאת הכתובת שאנחנו רוצים שהפונקציה תקפוץ במקום הבעל הבית הקודם, במקום הקוד הראשי שקרה לה. אנחנו רוצים שהיא תעבור לכתובת 83.0C.0.9.10 מה יש בכתובת הזאת?
בכתובת הזאת יושב dll ובתוך ה-dll הזה כתובה פקודה שאני צריך. הפקודה היא jump ל-ESP. זאת אומרת, תקפוץ ותבצע פקודות שיש בתוך ESP.
מה זה ESP? מה זה הכתובת שיש ב-ESP? זה הכתובת של הסטאק. למה צריך לקפוץ לשם בשביל לקפוץ חזרה? מה ההיגיון?
הקטע שיש בווינדוס הגנה שנקראת ASLR וההגנה הזאת ASLR גורמת לתוכנית כל פעם להתען לכתובות אחרות בזיכרון אז אנחנו לא יודעים בקוד הזדוני איזה כתובת לתת בהקפצה הזאת כדי להגיע לביצוע של הסטאק הרי הסטאק הזה שאנחנו מסתכלים עליו עכשיו זה נתונים אנחנו רוצים לכתוב ב-EIP כתובת
שמשם המעבד יעשה את הפקודות אנחנו רוצים את הכתובת של הסטאק ואנחנו לא יודעים מה הכתובת של הסטאק כי כל פעם הגנת וינדוס ASLR תטעין את הסטאק לכתובת אחרת. זו ההגנה שהם עשו לפני קצת יותר מ-10 שנים.
מה אנחנו נעשה? אנחנו מחפשים כתובת בזיכרון ששייכת ל-DLL חיצוני שאין לו את ההגנה ASLR. ה-Dלל החיצוני הזה, במעמד התכנות שלו, נתנו לו פלאג לא להפעיל את ההגנת ASLR.
ובגלל שאין לו את ההגנה הזאת, הוא תמיד יטען לאותן כתובות בזיכרון. ואז אנחנו בתוך ה-dll הזה, שהוא תמיד יושב באותן כתובות בזיכרון, הקוד שלו, אנחנו מחפשים את הפקודה jump ל-esp.
למעשה, אנחנו מחפשים פקודה בתוך ה-dll שיהיה קבוע בזיכרון, פקודה שמקפיצה אותנו חזרה לכתובת של הסטאק. מצאנו את זה בהרצאות קודמות, שזאת הכתובת 83.0.c.0.9.10.
דרך אגב, הכתובת רשומה בסדר הפוך, כן? זאת אומרת, יש לו סדר כתיבה הפוך ממה שאנחנו רגילים. זה נקרא Little Indian. זאת הכתובת בתוך DLL שה-Dלל הזה לא מוגן עם SLR ובכתובת הזאת תובא פקודה Jump ל-ESP, Jump ל-Stack ובתוך ה-Stack אנחנו נשתול את הקוד הזדוני שלנו, את ה-Shell קוד איפה שהאותיות D נמצאות.
זה ההיגיון. מצאנו את הכתובת הזאת. הדבר הבא זה Offset. מה זה Offset? למעשה ארבע בתים שהולכים להימחק מראש המחסנית ESP ימחק אותם, ידלג מעליהם בסיום ביצוע של הפונקציה כי אלו הם הכתובות שבהם יושבים הפרמטרים שאיתם קראו לפונקציה
עוד פעם, אנחנו נפעיל את הקוד הזדוני שלנו רק בסיום ביצוע של הפונקציה אז אנחנו בעצם משתלטים רק בסיום הפונקציה כשנשלף את הכתובת לחזרה והוא למעשה חוזר לא לקוד המקורי אלא לקוד הזדוני שלנו אז בגלל שאנחנו משתלטים רק בפקודה האחרונה רק במעמד חזרה מהפונקציה יש לנו פה עוד ארבע בתים שהם c4 כתבנו את זה עוד c שזה ארבע בתים
שיימחקו מראש המחסנית למה כי אלו ארבע בתים שיושבים בהם הפרמטרים שהפעילו את הפונקציה כשאנחנו קוראים לפונקציה נניח my function ומעבירים לה בסוגריים כל מיני פרמטרים כל מיני נתונים אקסטרה שהפונקציה צריכה אז כשהפונקציה מסיימת לבצע היא מוחקת אותה מהזיכרון.
אז אנחנו מבינים שכשנקפוץ חזרה ל-ESP, כשנחזור חזרה לראש המחסנית אחרי הקפיצה ל-DLL וחזרה, אנחנו נחזור למעשה לא לכתובת של ה-Offset כי היא תשעלף, כבר ישלפו אותה, אנחנו נחזור לכתובת של ה-Buffer בדיוק.
זאת אומרת, בסיום הפונקציה אנחנו מנקים את כל הפילר. מה זה פילר? המשתנים המקומיים בזיכרון אנחנו מנקים את הכתובת של הגיבוי של ה-EIP פקודה הבאה לביצוע ומנקים מהזיכרון בסיום הפונקציה את ה-Offset שזה הפרמטרים שאיתם הפעלנו את הפונקציה מלכתחילה ואז כאילו פונקציה לא הייתה קיימת ואז הראש
של המחסנית ה-ESP מצביע לכתובת של ה-Buffer של ה-D אז אנחנו הבנו איך המבנה של הזיכרון עובד הבנו שאנחנו צריכים לדלג על ארבע בתים שאיתם הפעילו את הפונקציה ואנחנו יודעים שהכתובת הבאה לביצוע אחרי ההקפצה ל-DLL וחזרה ל-ESP ה-Jump ESP ייקח אותנו לכתובת של ה-D, של ה-Buffer
D למעשה איפה שעוד D, שם תהיה הפקודה הראשונה שתתבצע עכשיו היינו צריכים בעצם לעלות על כל זה בשביל לדעת איפה בדיוק לשתול את הקוד הזדוני שלנו עכשיו השאלה כמה מקום יש לנו לעוד D אז יש לנו 1500 פחות כל מה שהיה למעלה וזה מספיק לנו, אנחנו לא צריכים כל כך הרבה מקום
בואו נפעיל את זה עכשיו באימיוניטי ונראה את כל זה קורה תכלס מפעילים אימיוניטי עם הרשאות של אדמיניסטרטור כדי שנוכל לעשות את טאץ' להתחבר לתהליך שרץ בזיכרון לסרוויס של סינגבריס אנחנו מתחברים לתוכנה קיימת אנחנו למעשה נהיים הדבאגר של התוכנה ומה זה אומר שאנחנו הדבאגר שלה?
זה אומר אחרי כל פקודה היא חוזרת אליי. היא עושה פקודה, חוזרת אליי. אליי זה לדבאגר. כרגע הדבאגר, איך שהוא התחבר אליי, הוא העביר אותה לפאוז.
עצר אותה, אז היא במצב של פאוז עכשיו. ואני יכול ללחוץ פליי, סימן של פליי למעלה, ושהיא תמשיך לרוץ. בואו נדלג לכתובת הזאת של DLL שקבוע בזיכרון בלי הגנת ASLR, ובכתובת הזאת, בתוך ה-DLL הזה, יושבת הפקודת Jump ESP שתחזיר אותי חזרה ל-Stack, לכתובת של ה-Stack.
הוא אומר Jump לתוך הכתובת בזיכרון שיושבת בתוך מצביע ESP. יש רגיסטר, יש אוגר ESP שמצביע לראש המחסנית, ואני אומר בוא תקפוץ חזרה למעשה, אני מחזיר את השליטה אליי.
למה לא יכולתי לעשות את זה ישר? מסיבה מאוד פשוטה, שאני לא יודע באיזה כתובת יהיה ESP הזה. הפקודה הזאת, Jump ESP, בשפת אסמבלר, שפת מכונה זה FFE4 זה למעשה תקן של x86 זה התקן של שפת מכונה x86 שאומר FFE4 המשמעות של זה ג'אמפ ל-ESP עכשיו תשאלו למה ב-EIP לא שלטלת ישירות את הג'אמפ ESP בשביל מה לך לקפוץ ל-DLL ואז לחזור חזרה כי בתוך ה-EIP אני שותל כתובת אני לא יכול לשתול פקודה אם
EIP זה לא היה כתובת אלא היה ממש פקודה לביצוע, אז הייתי שותל לו Jump ASP ונגמר הסיפור. הקטע ש-EIP הוא מצביע למקום שיש בו את הפקודה.
אני לא יכול לשתול לו את הפקודה, אז אני צריך לשתול ל-EIP כתובת ששם כתובה הפקודה שאני רוצה שיבצע. ואת זה עשינו בסרטים הקודמים,