7.3 GDB מתקדם תרגול
תרגול - GDB מתקדם¶
תרגיל 1 - מציאת סיסמה בבינארי עם GDB¶
כתבו את התוכנית הבאה, קמפלו אותה בלי -g, ואז התייחסו אליה כאילו לא ראיתם את הקוד:
#include <stdio.h>
#include <string.h>
int main(void) {
char input[32];
char password[] = "OpenSesame";
printf("Enter password: ");
scanf("%31s", input);
if (strcmp(input, password) == 0) {
printf("Access granted!\n");
} else {
printf("Access denied.\n");
}
return 0;
}
קמפלו:
עכשיו, בלי להסתכל על הקוד, השתמשו ב-GDB כדי למצוא את הסיסמה:
א. פתחו ב-GDB, הגדירו breakpoint על strcmp@plt, והריצו את התוכנית עם קלט כלשהו.
ב. כשנעצרים ב-strcmp, בדקו את הארגומנטים:
מה שני הארגומנטים? אחד מהם הוא הקלט שלכם ואחד הוא הסיסמה.
ג. עכשיו שמצאתם את הסיסמה, הריצו שוב עם הסיסמה הנכונה ווודאו שמקבלים "Access granted!".
ד. אתגר נוסף: בלי למצוא את הסיסמה, שנו את ערך ההחזרה של strcmp כדי "לעקוף" את הבדיקה. (רמז: strcmp מחזיר 0 כשהמחרוזות שוות, ו-eax מכיל את ערך ההחזרה)
תרגיל 2 - מעקב אחרי ארגומנטים של פונקציות¶
כתבו תוכנית שמקבלת שם קובץ כארגומנט וקוראת את תוכנו:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <filename>\n", argv[0]);
return 1;
}
FILE *f = fopen(argv[1], "r");
if (f == NULL) {
perror("fopen");
return 1;
}
char buf[256];
while (fgets(buf, sizeof(buf), f) != NULL) {
printf("%s", buf);
}
fclose(f);
return 0;
}
קמפלו בלי -g:
צרו קובץ טקסט לבדיקה:
א. הגדירו breakpoint על fopen@plt. הריצו run /tmp/testfile.txt. כשנעצרים:
- מה הארגומנט הראשון (rdi)? בדקו עם x/s $rdi
- מה הארגומנט השני (rsi)? בדקו עם x/s $rsi
ב. הגדירו breakpoint על fgets@plt עם commands שמדפיסים את הארגומנטים ועושים continue אוטומטית:
(gdb) break fgets@plt
(gdb) commands
> printf "fgets(buf=%p, size=%d, file=%p)\n", $rdi, $rsi, $rdx
> continue
> end
(gdb) continue
כמה פעמים fgets נקראת? מה הגודל שמועבר?
ג. הגדירו breakpoint על printf@plt עם commands שמדפיסים את format string. עקבו אחרי מה שמודפס.
תרגיל 3 - איתור נקודת ההחלטה ועקיפתה¶
כתבו תוכנית שבודקת אם מספר הוא "מספר הקסם":
#include <stdio.h>
#include <stdlib.h>
int check_magic(int num) {
return (num * 7 + 13) % 100 == 42;
}
int main(void) {
int num;
printf("Enter a number: ");
scanf("%d", &num);
if (check_magic(num)) {
printf("Magic number found!\n");
} else {
printf("Not a magic number.\n");
}
return 0;
}
קמפלו בלי -g:
א. עשו disassemble main ומצאו:
- את הקריאה ל-check_magic
- את ההוראה שבודקת את ערך ההחזרה (test/cmp אחרי ה-call)
- את הקפיצה המותנית שמחליטה בין "Magic number found!" ל-"Not a magic number."
ב. הגדירו breakpoint אחרי הקריאה ל-check_magic. הריצו עם מספר כלשהו. בדקו מה ערך eax (ערך ההחזרה). שנו את eax ל-1 ועשו continue. האם קיבלתם "Magic number found!" למרות שהזנתם מספר לא נכון?
ג. מצאו מהו מספר הקסם הנכון. (רמז: הגדירו breakpoint בתוך check_magic, צעדו עם ni, ועקבו אחרי החישוב. או חשבו מתמטית: (num * 7 + 13) % 100 == 42)
ד. השתמשו ב-jump כדי לדלג ישירות על הבדיקה ולהגיע להדפסת "Magic number found!" בלי לשנות אוגרים.
תרגיל 4 - שימוש ב-watchpoint¶
כתבו תוכנית עם משתנה גלובלי שמשתנה באופן "מסתורי":
#include <stdio.h>
int counter = 0;
void add_one(void) {
counter++;
}
void add_five(void) {
counter += 5;
}
void subtract_three(void) {
counter -= 3;
}
int main(void) {
add_one();
add_five();
subtract_three();
add_one();
add_one();
printf("Final counter = %d\n", counter);
return 0;
}
קמפלו בלי -g:
א. מצאו את הכתובת של המשתנה הגלובלי counter:
או:
ב. הגדירו watchpoint על counter:
ג. הריצו את התוכנית ועקבו אחרי כל שינוי. GDB יעצור בכל פעם ש-counter משתנה ויראה את הערך הישן והחדש. לכל עצירה, בדקו עם backtrace מאיזו פונקציה השינוי הגיע.
ד. תעדו: מה סדר השינויים? אילו פונקציות שינו את counter ובכמה?
תרגיל 5 - סקריפט GDB מתקדם¶
כתבו תוכנית שמקבלת מחרוזת ובודקת אותה תו-תו:
#include <stdio.h>
#include <string.h>
int validate(const char *input) {
const char key[] = {0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x00};
int len = strlen(key);
if ((int)strlen(input) != len)
return 0;
for (int i = 0; i < len; i++) {
if (input[i] != key[i])
return 0;
}
return 1;
}
int main(void) {
char buf[64];
printf("Enter code: ");
scanf("%63s", buf);
if (validate(buf)) {
printf("Valid!\n");
} else {
printf("Invalid.\n");
}
return 0;
}
קמפלו בלי -g:
א. עשו disassemble validate ומצאו את הלולאה שמשווה תו-תו.
ב. הגדירו breakpoint בנקודת ההשוואה (ה-cmp שבתוך הלולאה). כתבו סקריפט GDB שמדפיס את התו מהקלט ואת התו הצפוי בכל איטרציה:
(gdb) break *<address_of_cmp>
(gdb) commands
> printf "input[%d] = 0x%02x ('%c'), expected = 0x%02x ('%c')\n", ...
> continue
> end
(רמז: תצטרכו לזהות באילו אוגרים נמצאים התווים שמושווים, על פי ההוראות שלפני ה-cmp)
ג. מהו הcode הנכון? (רמז: הערכים 0x48, 0x65, 0x6c, 0x6c, 0x6f הם קודי ASCII)
ד. הריצו שוב עם הcode הנכון ווודאו שמקבלים "Valid!".
ה. אתגר: מצאו את הcode בלי להריץ את התוכנית בכלל - רק עם objdump -s -j .rodata validate או strings וניתוח הדיסאסמבלי.