7.3 GDB מתקדם פתרון
פתרון - GDB מתקדם¶
פתרון תרגיל 1 - מציאת סיסמה בבינארי עם GDB¶
א+ב. מציאת הסיסמה:
gdb ./passcheck
(gdb) set disassembly-flavor intel
(gdb) break strcmp@plt
(gdb) run
Enter password: test123
התוכנית נעצרת ב-strcmp. נבדוק את הארגומנטים:
rdi מכיל את הקלט שלנו ("test123"), ו-rsi מכיל את הסיסמה ("OpenSesame").
ג. אימות:
ד. עקיפת הבדיקה:
ראשית, נמצא את הכתובת שאחרי הcall ל-strcmp:
נמצא שורה כמו:
נגדיר breakpoint אחרי ה-call:
נקדם עד שנגיע אחרי הcall ל-strcmp:
עכשיו eax מכיל ערך שונה מ-0 (כי המחרוזות לא שוות). נשנה:
הצלחנו לעקוף את הבדיקה. strcmp החזיר ערך שונה מ-0 (מחרוזות שונות), אבל שינינו את eax ל-0 כך שtest eax, eax יגדיר ZF, ו-jne לא יקפוץ.
פתרון תרגיל 2 - מעקב אחרי ארגומנטים של פונקציות¶
א. מעקב אחרי fopen:
כשנעצרים:
rdi = שם הקובץ (הארגומנט הראשון של fopen), rsi = מצב הפתיחה ("r" = קריאה).
ב. מעקב אחרי fgets:
(gdb) delete ; מחיקת כל הbreakpoints
(gdb) break fgets@plt
(gdb) commands
> silent
> printf "fgets(buf=%p, size=%d, file=%p)\n", $rdi, $rsi, $rdx
> continue
> end
(gdb) run /tmp/testfile.txt
פלט:
fgets נקראת פעם אחת (כי הקובץ מכיל שורה אחת) ועוד פעם שמחזירה NULL (סוף הקובץ). הגודל שמועבר הוא 256 (sizeof(buf)).
ג. מעקב אחרי printf:
(gdb) delete
(gdb) break printf@plt
(gdb) commands
> silent
> printf "printf format: %s\n", (char*)$rdi
> continue
> end
(gdb) run /tmp/testfile.txt
פלט:
printf נקראת עם format string "%s" והארגומנט השני (rsi) מכיל את תוכן השורה שנקראה מהקובץ.
פתרון תרגיל 3 - איתור נקודת ההחלטה ועקיפתה¶
א. ניתוח main:
פלט (הכתובות עשויות להשתנות):
; ... printf("Enter a number: ") ...
; ... scanf("%d", &num) ...
0x4011c0: mov edi, [rbp-4] ; טעינת num לedi
0x4011c3: call 0x401149 <check_magic>
0x4011c8: test eax, eax ; בדיקת ערך החזרה
0x4011ca: je 0x4011d8 ; אם 0 -> "Not a magic number"
0x4011cc: lea rdi, [rip+0x...] ; "Magic number found!"
0x4011d3: call puts@plt
0x4011d8: lea rdi, [rip+0x...] ; "Not a magic number."
; ...
זיהוי:
- call check_magic בכתובת 0x4011c3
- test eax, eax בכתובת 0x4011c8 - בדיקת ערך ההחזרה
- je 0x4011d8 בכתובת 0x4011ca - אם eax==0 (check_magic החזיר false), קפוץ ל-"Not a magic number"
ב. שינוי ערך ההחזרה:
נעצרים אחרי check_magic:
check_magic החזיר 0 (999 הוא לא מספר הקסם). נשנה:
הצלחנו לעקוף את הבדיקה.
ג. מציאת מספר הקסם:
שיטה מתמטית: (num * 7 + 13) % 100 == 42 -> num * 7 + 13 = 42 (mod 100) -> num * 7 = 29 (mod 100).
צריך למצוא num כך ש-num * 7 נותן שארית 29 בחלוקה ב-100.
נבדוק: num = 7 -> 77 = 49, 49+13 = 62, 62%100 = 62. לא.
num = 9 -> 97 = 63, 63+13 = 76, 76%100 = 76. לא.
num = 47 -> 47*7 = 329, 329+13 = 342, 342%100 = 42. כן.
שיטה עם GDB:
check_magic מחזיר 1 עבור 47.
ד. שימוש ב-jump:
קודם כל צריך לזהות את הכתובת של "Magic number found!". מהניתוח, זו הכתובת של ההוראה שמכינה את הargument ל-puts (לדוגמה 0x4011cc):
דילגנו ישירות על ה-test ועל ה-je, וקפצנו להדפסת "Magic number found!".
פתרון תרגיל 4 - שימוש ב-watchpoint¶
א. מציאת כתובת counter:
ב+ג. הגדרת watchpoint ומעקב:
GDB יעצור בכל שינוי:
עצירה 1:
counter עבר מ-0 ל-1, בפונקציה add_one.
עצירה 2:
counter עבר מ-1 ל-6, בפונקציה add_five.
עצירה 3:
counter עבר מ-6 ל-3, בפונקציה subtract_three.
עצירה 4:
counter עבר מ-3 ל-4, בפונקציה add_one.
עצירה 5:
counter עבר מ-4 ל-5, בפונקציה add_one.
ד. סיכום השינויים:
| שינוי | פונקציה | ערך ישן | ערך חדש | שינוי |
|---|---|---|---|---|
| 1 | add_one | 0 | 1 | +1 |
| 2 | add_five | 1 | 6 | +5 |
| 3 | subtract_three | 6 | 3 | -3 |
| 4 | add_one | 3 | 4 | +1 |
| 5 | add_one | 4 | 5 | +1 |
תוצאה סופית: 5 (בדיוק כמו שהתוכנית מדפיסה).
פתרון תרגיל 5 - סקריפט GDB מתקדם¶
א. ניתוח הדיסאסמבלי של validate:
בתוך הלולאה, נחפש את הוראת cmp שמשווה תו-תו. היא תיראה בערך כך:
0x40121a: movzx eax, byte [rbp-0x1e+...] ; תו מהקלט
0x40121e: movzx edx, byte [rbp-0x12+...] ; תו מהkey
0x401222: cmp al, dl ; השוואה
0x401224: jne 0x401240 ; אם שונה -> return 0
ב. סקריפט GDB:
נניח שב-cmp, al מכיל את התו מהקלט ו-dl מכיל את התו הצפוי (הכתובות עשויות להשתנות):
(gdb) break *0x401222
(gdb) commands
> silent
> printf "comparing: input=0x%02x ('%c') vs expected=0x%02x ('%c')\n", $al, $al, $dl, $dl
> continue
> end
(gdb) run
Enter code: AAAAA
פלט:
הלולאה נעצרת אחרי ההשוואה הראשונה כי התווים לא שווים (jne קופץ ל-return 0).
ג. הcode הנכון:
הערכים בkey הם:
- 0x48 = 'H'
- 0x65 = 'e'
- 0x6c = 'l'
- 0x6c = 'l'
- 0x6f = 'o'
הcode הנכון הוא Hello.
ד. אימות:
ה. מציאה סטטית בלי הרצה:
בפלט נחפש בתים שנראים כמו ASCII:
Contents of section .rodata:
2000 01000200 456e7465 7220636f 64653a20 ....Enter code:
2010 00256333 73004865 6c6c6f00 56616c69 .%63s.Hello.Vali
2020 64210a00 496e7661 6c69642e 0a00 d!..Invalid...
המחרוזת "Hello" נמצאת בoffset 0x2016 ב-.rodata. זה הkey שהפונקציה משווה אליו.
בנוסף, אפשר היה להשתמש ב-strings:
המחרוזת "Hello" מופיעה ברשימה. בשילוב עם הניתוח של הדיסאסמבלי (שראינו שיש לולאת השוואה תו-תו), אפשר להסיק שזה הcode.