לדלג לתוכן

7.3 GDB מתקדם פתרון

פתרון - GDB מתקדם

פתרון תרגיל 1 - מציאת סיסמה בבינארי עם GDB

א+ב. מציאת הסיסמה:

gdb ./passcheck
(gdb) set disassembly-flavor intel
(gdb) break strcmp@plt
(gdb) run
Enter password: test123

התוכנית נעצרת ב-strcmp. נבדוק את הארגומנטים:

(gdb) x/s $rdi
0x7fffffffe3e0: "test123"
(gdb) x/s $rsi
0x7fffffffe400: "OpenSesame"

rdi מכיל את הקלט שלנו ("test123"), ו-rsi מכיל את הסיסמה ("OpenSesame").

ג. אימות:

(gdb) run
Enter password: OpenSesame
Access granted!

ד. עקיפת הבדיקה:

ראשית, נמצא את הכתובת שאחרי הcall ל-strcmp:

(gdb) disas main

נמצא שורה כמו:

0x40123a:  call   strcmp@plt
0x40123f:  test   eax, eax
0x401241:  jne    0x401260

נגדיר breakpoint אחרי ה-call:

(gdb) break *main
(gdb) run
Enter password: wrong

נקדם עד שנגיע אחרי הcall ל-strcmp:

(gdb) break *0x40123f
(gdb) continue

עכשיו eax מכיל ערך שונה מ-0 (כי המחרוזות לא שוות). נשנה:

(gdb) set $eax = 0
(gdb) continue
Access granted!

הצלחנו לעקוף את הבדיקה. strcmp החזיר ערך שונה מ-0 (מחרוזות שונות), אבל שינינו את eax ל-0 כך שtest eax, eax יגדיר ZF, ו-jne לא יקפוץ.


פתרון תרגיל 2 - מעקב אחרי ארגומנטים של פונקציות

א. מעקב אחרי fopen:

gdb ./reader
(gdb) set disassembly-flavor intel
(gdb) break fopen@plt
(gdb) run /tmp/testfile.txt

כשנעצרים:

(gdb) x/s $rdi
0x7fffffffe7a8: "/tmp/testfile.txt"
(gdb) x/s $rsi
0x402010: "r"

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(buf=0x7fffffffe3d0, size=256, file=0x5555555592a0)

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: %s

printf נקראת עם format string "%s" והארגומנט השני (rsi) מכיל את תוכן השורה שנקראה מהקובץ.


פתרון תרגיל 3 - איתור נקודת ההחלטה ועקיפתה

א. ניתוח main:

gdb ./magic
(gdb) set disassembly-flavor intel
(gdb) disas 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"

ב. שינוי ערך ההחזרה:

(gdb) break *0x4011c8
(gdb) run
Enter a number: 999

נעצרים אחרי check_magic:

(gdb) print $eax
$1 = 0

check_magic החזיר 0 (999 הוא לא מספר הקסם). נשנה:

(gdb) set $eax = 1
(gdb) continue
Magic number found!

הצלחנו לעקוף את הבדיקה.

ג. מציאת מספר הקסם:

שיטה מתמטית: (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 -> 9
7 = 63, 63+13 = 76, 76%100 = 76. לא.
num = 47 -> 47*7 = 329, 329+13 = 342, 342%100 = 42. כן.

שיטה עם GDB:

(gdb) break check_magic
(gdb) run
Enter a number: 47
(gdb) finish
(gdb) print $eax
$1 = 1

check_magic מחזיר 1 עבור 47.

ד. שימוש ב-jump:

קודם כל צריך לזהות את הכתובת של "Magic number found!". מהניתוח, זו הכתובת של ההוראה שמכינה את הargument ל-puts (לדוגמה 0x4011cc):

(gdb) break *0x4011c8
(gdb) run
Enter a number: 1
(gdb) jump *0x4011cc
Magic number found!

דילגנו ישירות על ה-test ועל ה-je, וקפצנו להדפסת "Magic number found!".


פתרון תרגיל 4 - שימוש ב-watchpoint

א. מציאת כתובת counter:

gdb ./counter
(gdb) info variables counter

0x0000000000404030  counter

ב+ג. הגדרת watchpoint ומעקב:

(gdb) watch *(int*)0x404030
(gdb) run

GDB יעצור בכל שינוי:

עצירה 1:

Hardware watchpoint: *(int*)0x404030

Old value = 0
New value = 1

(gdb) backtrace
#0  add_one ()
#1  main ()

counter עבר מ-0 ל-1, בפונקציה add_one.

(gdb) continue

עצירה 2:

Old value = 1
New value = 6

(gdb) backtrace
#0  add_five ()
#1  main ()

counter עבר מ-1 ל-6, בפונקציה add_five.

עצירה 3:

Old value = 6
New value = 3

(gdb) backtrace
#0  subtract_three ()
#1  main ()

counter עבר מ-6 ל-3, בפונקציה subtract_three.

עצירה 4:

Old value = 3
New value = 4

(gdb) backtrace
#0  add_one ()
#1  main ()

counter עבר מ-3 ל-4, בפונקציה add_one.

עצירה 5:

Old value = 4
New value = 5

(gdb) backtrace
#0  add_one ()
#1  main ()

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:

gdb ./validate
(gdb) set disassembly-flavor intel
(gdb) disas 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

פלט:

comparing: input=0x41 ('A') vs expected=0x48 ('H')

הלולאה נעצרת אחרי ההשוואה הראשונה כי התווים לא שווים (jne קופץ ל-return 0).

ג. הcode הנכון:

הערכים בkey הם:
- 0x48 = 'H'
- 0x65 = 'e'
- 0x6c = 'l'
- 0x6c = 'l'
- 0x6f = 'o'

הcode הנכון הוא Hello.

ד. אימות:

(gdb) run
Enter code: Hello
Valid!

ה. מציאה סטטית בלי הרצה:

objdump -s -j .rodata validate

בפלט נחפש בתים שנראים כמו 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:

strings validate

Enter code:
%63s
Hello
Valid!
Invalid.

המחרוזת "Hello" מופיעה ברשימה. בשילוב עם הניתוח של הדיסאסמבלי (שראינו שיש לולאת השוואה תו-תו), אפשר להסיק שזה הcode.