לדלג לתוכן

3.9 דיבוג עם GDB הרצאה

בהרצאה הזו נלמד איך להשתמש ב־GDB, הכלי החזק של עולם התכנות בשפת C (ושפות נוספות) לצורך ניפוי שגיאות (Debugging).
הכלי GDB (GNU Debugger) מאפשר לנו להריץ תוכנית שלב־אחר־שלב, לעצור אותה בכל נקודה, לבדוק ערכים של משתנים, לשנות זיכרון, לקפוץ בין פונקציות, לבדוק איפה הייתה קריסה – וכל זה מתוך סביבת פקודה טקסטואלית.

מבוא: למה צריך Debugger בכלל?

כשאתה כותב תוכנית – ייתכנו שגיאות לוגיות (התוכנית רצה, אבל לא עושה מה שציפית), שגיאות בזיכרון (גישה למקום לא תקין), או פשוט קוד שלא עושה כלום.
אז printf עוזר – אבל קשה להוסיף אותו בכל מקום, הוא איטי, ולא תמיד חושף את הבעיה.

הכלי GDB נותן שליטה בכל שלב בריצה. אתה יכול לעצור, להסתכל, להריץ פקודה, לבדוק ערך, לשנות משתנה – בזמן שהתוכנית "קפואה".

ניפוי שגיאות בעזרת GDB – מבט מהבסיס

לפני שלומדים פקודות או שיטות, צריך להבין מה בעצם GDB עושה.

כלי הGDB הוא debugger – כלי שמאפשר לנו להריץ תוכנית בקצב איטי ומבוקר, לעצור אותה בכל רגע, לבדוק ערכים בזיכרון, לבדוק מה קרה רגע לפני קריסה, ולהבין מה גורם לבאגים שקשה לאתר בקריאה רגילה של הקוד.

אבל GDB לא מדבג את הקוד C שלך. הוא מדבג את שפת המכונה.

כשאתה מקמפל תוכנית ב־C, התוצאה היא קובץ הרצה (ELF ב־Linux, EXE ב־Windows) – כלומר קוד בשפת מכונה שהמעבד יכול להריץ.
ו- GDB פועל על קובץ ההרצה הזה – ומאפשר לעצור בו, להריץ פקודות, לצפות בתוכן הזיכרון ובקוד האסמבלי של התוכנית בפועל.

מה הם Debugging Symbols ולמה צריך את -g

ברירת מחדל, כשאתה מקמפל קובץ C, הקומפיילר שומר רק את הקוד המכונתי – אין שם שום זכר לשמות הפונקציות שכתבת, שמות המשתנים שלך, ואפילו לא מספרי שורות.

כדי ש־GDB יוכל להראות לך איפה אתה נמצא בקוד המקור, לקרוא משתנים בשמותיהם, לעצור ב־main ועוד, – אתם חייבים לקמפל את הקוד עם הדגל -g.

gcc -g program.c -o program

ה־-g מבקש מהקומפיילר להוסיף לקובץ ההרצה debugging symbols – שהם פשוט מיפוי: איזה שורת קוד נמצאת באיזה כתובת בזיכרון, איזה משתנה נמצא באיזה offset, איך קוראים לפונקציה הזו, וכו'.

הם לא משפיעים על הריצה, הם רק עוזרים ל־GDB לתרגם את שפת המכונה לקוד שאתה כתבת.


הפעלת GDB

להרצת GDB על קובץ שהרגע קימפלת:

gdb ./program

תקבל את ה־prompt:

(gdb)

מכאן תוכלו להתחיל להשתמש בכל הפקודות של gdb.

הפעלת התוכנית מתוך GDB

כדי להריץ את התוכנית שלכם (מההתחלה), השתמשו בפקודה:

(gdb) run

אם התוכנית דורשת ארגומנטים:

(gdb) run arg1 arg2

עצירה בקוד – Breakpoints

כדי לעצור את התוכנית בנקודה מסוימת (לפני שהיא מגיעה לשם), השתמש ב־breakpoint. לדוגמה:

(gdb) break main

עוצר בכניסה לפונקציה main.
עכשיו תוכלו לעשות run ולראות שהתוכנה נעצרת בmain.
הריצו את הפקודה c (קיצור של continue) כדי לתת לתוכנה להמשיך לרוץ עד הסוף.

בצעו את הפקודה disass main כדי לראות את האסמבלי של פונקציית הmain.
הפלט יהיה ברמת אסמבלי (הוראות כמו mov, call, ret, וכו') עם כתובות זיכרון.
עכשיו, תוכלו לבצע break לכתובת מסיימת בזכרון, בחרו כתובת ובצעו:

(gdb) break *0x08048400

במקום break תוכלו לכתוב כקיצור (b)

פקודות לבקרה על ריצה

אחרי שעשינו break point בנקודה מסוימת באסמבלי שלנו, נוכל לבצע המון פקודות כדי לזוז בקוד.

  • continue – ממשיך לרוץ עד breakpoint הבא (או סוף התוכנית)
    קיצור הפקודה הוא (c)
  • nexti – מבצע את ההוראה הנוכחית ומתקדם לשורה הבאה (לא נכנס לפונקציות)
    קיצור הפקודה הוא (ni)
  • stepi – נכנס לתוך פונקציה בהוראה הנוכחית
    קיצור הפקודה הוא (si)
  • finish – יוצא מהפונקציה הנוכחית וממשיך לאחריה
    קיצור הפקודה הוא (f)

דוגמה:

(gdb) break main
(gdb) run
(gdb) nexti
(gdb) stepi
(gdb) finish

בדיקת מבנה התוכנית

אז אלו דברים נוכל לבדוק על המערכת בזמן breakpoint?
- פקודת backtrace (או bt) – מדפיס את ה־call stack (מי קרא למי עד כה)
- פקודת info frame – מידע על הstack frame הנוכחית
- פקודת frame – בוחר פריים כלשהו שנמצא ב־stack
- פקודת info breakpoints – מציג את כל הbreak point-ים
- פקודת delete – מוחק breakpoint מסוים
- פקודות disable / enable – מכבה/מדליק breakpoint מסוים זמנית
- פקודת info registers- מציגה את כל האוגרים במצב מסוים

פקודת x - examine

הפקודה x (קיצור של examine) ב־GDB מאפשרת לנו לבדוק את תוכן הזיכרון בתוכנית בזמן ריצה. היא אחת הפקודות המרכזיות לדיבוג ברמת שפת מכונה, במיוחד כשאנחנו רוצים לראות מה יש בזיכרון בכתובת מסוימת – בין אם זה מערך, מחרוזת, מצביע, או קוד אסמבלי.

התחביר הבסיסי של x

(gdb) x/[מספר][פורמט][יחידה] כתובת

רכיבי התחביר:

  • כתובת- לאיזה כתובת בזכרון לעשות dereference
  • [מספר] – כמה יחידות להדפיס (ברירת מחדל: 1)
  • [פורמט] – איך להציג את הערכים בזכרון
  • [יחידה] – גודל התצוגה (כלומר כמה בתים להציג בכל פעם)
    סוגי הפורמטים השונים:
  • x – הקסדצימלי (hex)
  • d – עשרוני (decimal)
  • u – עשרוני לא־חתום
  • t – בינארי
  • f – מספר ממשי (float)
  • s – מחרוזת (string)
  • i – אינסטרוקציה (הוראות אסמבלי)
  • c – תו (char)
    סוגי היחידות השונות:
  • b – byte (1 בית)
  • h – half-word (2 בתים)
  • w – word (4 בתים)
  • g – giant word (8 בתים)

לדוגמה: x/4xb 0x601000 – הצג 4 בתים כהקס.

ניתן לציין כתובת באמצעות אוגרים עם שימוש בשם האוגר בסימן $.

דוגמאות שימושיות

הצגת 10 מילים בזיכרון בכתובת מסוימת כהקסדצימליות

(gdb) x/10xw 0x601000

- מציג 10 "words" של 4 בתים כל אחד
- כתובת ההתחלה היא 0x601000
- כל ערך יודפס כהקסדצימלי (x)

הצגת תוכן של מערך שלמים

נניח שיש לכם:

int arr[] = {10, 20, 30, 40};

כדי לראות את הערכים בזמן ריצה, תוכלו להבין מה הכתובת של המשתנה arr על פי, או הפקודה print אם יש לכם symbole-ים בצורה הבאה:

(gdb) print &arr
$1 = 0x601040

או באמצעות הסתכלות על האסמבלי (פשוט disass לפונקציה שבה נמצא המשתנה) ומציאת הכתובת על פי האסמבלי.

לאחר שיש לנו את הכתובת, נוכל לבצע x כזה:

(gdb) x/4dw 0x601040

- 4d – 4 ערכים עשרוניים
- w – גודל של כל איבר (4 בתים)
- 0x601040 – הכתובת של המערך

הצגת מחרוזת מכתובת מסוימת

(gdb) x/s 0x601060

- יציג את המחרוזת שנמצאת שם עד \0

הצגת הוראות אסמבלי

(gdb) x/5i $rip

- i = instruction
- $rip = מיקום התוכנית (instruction pointer)
- יציג את 5 ההוראות הבאות שהמעבד יבצע
זה נותן מבט ישיר על קוד המכונה שירוץ.

שימוש עם משתנים או מצביעים

נניח שיש לכם:

char *ptr = buffer;

אפשר לבדוק:

(gdb) print ptr
$1 = 0x601080

(gdb) x/16xb ptr

- מציג 16 בתים שמצביע ptr מצביע אליהם
- כל ערך יודפס כהקס


קיצור – שימוש חוזר

אחרי שאתם מריצים x פעם אחת, אפשר לחזור על אותה פקודה עם enter.
למשל:

(gdb) x/4xw 0x601000
(gdb)
(gdb)

כל לחיצה תדפיס את 4 המילים הבאות בזיכרון.

נניח שאתם בתוך פונקציה כלשהי במהלך דיבוג עם GDB, ורוצה לראות את תוכן ה־stack frame הנוכחי — כלומר מה נמצא על מחסנית הקריאות (stack): משתנים מקומיים, כתובת חזרה לפונקציה הקודמת, פרמטרים לפונקציה ועוד.

הכלי GDB מאפשר לך לבדוק את ערכי הזיכרון ישירות, בעזרת הפקודה x, דרך מצביע ה־rbp (או ebp במערכות 32 ביט) שהוא בסיס ה־frame של הפונקציה הנוכחית.

דוגמה מלאה: צפייה ב־stack frame

נניח שיש לנו את הקוד הבא:

#include <stdio.h>

void print_values(int a, int b) {
    int sum = a + b;
    printf("sum = %d\n", sum);
}

int main() {
    print_values(10, 20);
    return 0;
}

הרצה עד לכניסה לפונקציה:

(gdb) break print_values
(gdb) run

צפייה בכתובת בסיס ה־frame:

(gdb) info registers rbp

תקבלו משהו כמו:

rbp            0x7fffffffe4f0

זוהי כתובת הבסיס של stack frame הנוכחי.

הצגת תוכן ה־stack:

(gdb) x/10xw $rbp

הסבר:
- 10 – נסתכל על 10 תאים (words)
- x – תצוגה הקסדצימלית
- w – גודל של כל תא: word (4 בתים במערכת 32 ביט, 8 בתים במערכת 64 ביט)
- $rbp – רשום של בסיס המחסנית

זה ידפיס את תוכן המחסנית מהנקודה של rbp והלאה, כולל:
- כתובת החזרה לפונקציה הקודמת
- משתנים מקומיים
- פרמטרים לפונקציה (בהתאם למערכת ול־calling convention)

הערה: מבנה stack frame משתנה לפי מערכת ההפעלה, קומפיילר, ו־architecture (x86 לעומת x86_64)

במערכת 64 ביט עם ABI סטנדרטי (System V), הפרמטרים הראשונים מועברים ברגיסטרים (rdi, rsi, rdx, וכו'), ולכן לא תמיד תראה אותם בתוך ה־stack. אבל תמיד תוכלו לראות את כתובת החזרה וערכים מקומיים שנשמרו במחסנית.

דיבוג C

אז למדנו איך לדבג קוד אסמבלי — איך לעצור בכתובות זיכרון, לצפות ברמות הנמוכות של הקוד, ואפילו להדפיס את המחסנית בזכות פקודת x.
לדבג אסמבלי קורה בדרך כלל כשאנחנו חוקרים תוכנה של מישהו אחר, ואין לנו גישה לsource code שלו.
אבל כשאנחנו מפתחים תוכנה משלנו, במיוחד בשפת C, יש לנו גישה לקוד המקור – ועם קימפול מתאים (-g), GDB מאפשר לנו לדבג את קוד ה־C עצמו – בצורה הרבה יותר נוחה, קריאה, ומהירה.

תצוגת קוד בזמן אמת: layout

כדי לדבג בצורה נוחה יותר – GDB כולל ממשק מבוסס טקסט שנקרא TUI (Text User Interface).
אפשר לעבור אליו בכל רגע באמצעות הפקודה:

(gdb) layout src

זה יפתח מסך מפוצל: למעלה – קוד המקור בשפת C, ולמטה – הפקודות שלכם.

אפשר גם להשתמש ב־layout asm אם אתם רוצים לראות קוד אסמבלי בצורה דומה.

ליציאה מהתצוגה: Ctrl + x ואז 1

עצירת התוכנית לפי קובץ ושורה: break

במקום לעצור לפי כתובת זיכרון, כשיש לנו קוד C – נוכל לעצור בקלות לפי שם קובץ ומספר שורה, או לפי שם פונקציה:

(gdb) break main             // עצור בתחילת main
(gdb) break 23               // עצור בשורה 23 בקובץ הראשי
(gdb) break myfunc.c:42      // עצור בשורה 42 בקובץ myfunc.c

זה הרבה יותר נוח מאשר להשתמש ב־disas ואז לנחש את הכתובת.

תצוגת משתנים: print

אחת הפקודות השימושיות ביותר. ברגע שהגעתם לנקודת עצירה, תוכלו להדפיס ערך של כל משתנה גלובלי, מקומי או מצביע:

(gdb) print x
(gdb) print arr[3]
(gdb) print *ptr
(gdb) print my_struct.field

אם אתם משתמשים ב־TUI, תוכלו לראות את הקוד למעלה ולשלב את זה בזמן אמת.

שליטה על זרימת הריצה: next ו־step

בניגוד ל־nexti ו־stepi שראינו קודם (שזזים הוראת אסמבלי אחת), ב־C נרצה לזוז שורת קוד אחת (שורת קוד בC יכולה להיות כמה שורות אסמבלי) בכל פעם:

  • הפקודה next – מבצע את השורה הנוכחית וממשיך לשורה הבאה, בלי להיכנס לפונקציות. (קיצור שלה זהn)
    (אם השורה היא foo(); הוא יבצע את foo() במלואה ואז יעבור לשורה הבאה.)
  • הפקודה stepנכנס לתוך הפונקציה שמריצים כרגע. (קיצור שלה זה s)
int a = 5;
int b = a * 2;
(gdb) break main
(gdb) run
(gdb) next
(gdb) print a   // תראה שהוא 5
(gdb) next
(gdb) print b   // תראה שהוא 10

בדיקת משתנים מקומיים: info locals

רוצים לראות את כל המשתנים המקומיים בפונקציה הנוכחית? פשוט הריצו:

(gdb) info locals

אם נכנסתם לפונקציה גדולה עם הרבה משתנים – זה שימושי במיוחד. (כמובן פשוט יותר מאפשר להדפיס בתים בstack מrbp ולחשב offset-ים.)

בדיקת כל המשתנים המוגדרים:

(gdb) info variables

(זה מדפיס את כל המשתנים שה־GDB מכיר בקובץ — כולל גלובליים.)
שזה כמובן פשוט יותר מאשר לעשות x על כתובות של משתנים גלובלים ב data segment.


שינוי ערכים בזמן ריצה: set

אפשר לשנות כל משתנה תוך כדי הריצה – וזה שימושי לבדיקת התנהגות של הקוד בלי לשנות את הקוד עצמו:

(gdb) set variable x = 42
(gdb) set *(arr + 1) = 99

אחרי זה תוכלו לעשות print ולוודא שזה אכן השתנה.

int square(int x) {
    return x * x;
}

int main() {
    int a = 4;
    int b = square(a);
    return b;
}
(gdb) break main
(gdb) run
(gdb) next          // מגיע לשורת a = 4
(gdb) next          // מגיע לשורת b = square(a)
(gdb) step          // נכנס לתוך square
(gdb) print x       // x = 4
(gdb) next          // return x * x
(gdb) finish        // חזרה לmain
(gdb) print b       // b = 16

עצירה כשהערך משתנה: watch

הפקודה watch עוצרת את הריצה בכל פעם שערך של ביטוי משתנה.
בשונה מ־break, שלא אכפת לו מה מצב המשתנים – כאן אנחנו מגדירים תנאי עצירה שמבוסס על שינויים בזמן הריצה.

דוגמה:

int count = 0;
while (count < 10) {
    count++;
}

ב־GDB:

(gdb) break main
(gdb) run
(gdb) watch count
(gdb) continue

התוכנית תיעצר בכל פעם שהמשתנה count משתנה.
כל עצירה תראה לך את הערך הישן והחדש.

אפשר גם לצפות בשדה בתוך struct, איבר במערך, מצביע וכו':

(gdb) watch arr[2]
(gdb) watch my_struct.value

break עם תנאים

לפעמים לא רוצים לעצור בכל פעם, אלא רק כשהתנאי מתקיים.

נניח שיש לנו לולאה:

for (int i = 0; i < 1000; i++) {
    if (arr[i] == 42) {
        // משהו חשוב
    }
}

ואתם לא רוצים לעצור בכל איטרציה – אלא רק כש־i == 500.

ב־GDB:

(gdb) break loop.c:12 if i == 500

או לפי משתנה:

(gdb) break my_func if x > 100 && y == 3

אפשר גם להוסיף תנאי אחרי שה־break נוצר:

(gdb) condition 1 i == 500

(1 זה מספר ה־breakpoint, אותו רואים ב־info breakpoints)


display – הצגה אוטומטית בכל צעד

כדי לא לרשום print x בכל שלב, GDB מאפשר להוסיף משתנים ל־רשימת תצוגה אוטומטית.

(gdb) display x

עכשיו, בכל פעם שהתוכנית נעצרת (ב־next, step, break וכו'), GDB אוטומטית ידפיס את ערך x.

רשימה של כל המשתנים שמוצגים ככה:

(gdb) info display

להסרה:

(gdb) undisplay 1

(1 הוא מספר הפריט מהרשימה – תראה אותו ב־info display)

דוגמה:

(gdb) break main
(gdb) run
(gdb) display i
(gdb) next
(gdb) next
// בכל צעד  תראה את i בלי לרשום כלום

שלושת הפקודות האלה שימושיות במיוחד כשאתם:

  • עוקבים אחרי משתנה בעייתי שפתאום משתנה
  • רוצים לבדוק באיזו נקודה בדיוק נגרמת בעיה
  • לא רוצים לרשום print כל הזמן

טיפול בקריסה

קריסה בתוכנית – כמו Segmentation Fault – נובעת כאשר אתה ניגש לזיכרון שאסור לכם לגשת אליו.
זה יכול לקרות בגלל:

  • מצביע לא מאותחל
  • גישה מחוץ לגבולות מערך
  • ניסיון לקרוא מ־NULL
  • בעיות בקצאת זיכרון (malloc) (נדבר בהמשך על מה זה malloc)

ברגע שתוכנית שקימפלתם עם -g קורסת – GDB עוצר אותה בדיוק בנקודת הקריסה.
נראה הודעה כמו:

Program received signal SIGSEGV, Segmentation fault.

משם נוכל לנתח איפה בדיוק הקוד נכשל, מה הערך של המשתנים בזמן הקריסה, ואיזה פונקציות הובילו לשם.

1. backtrace – הצגת call stack

(gdb) backtrace

מראה את כל הפונקציות שנקראו עד רגע הקריסה – החל מה־main, דרך כל הפונקציות שקראו אחת לשנייה, ועד הפונקציה שהתפוצצה.

הפקודה הזו עוזרת להבין:
- מי קרא למי
- איך הגענו למצב השגוי
- באיזה קובץ ומספר שורה התרחשו הקריאות

2. frame – מעבר בין פריימים

אפשר לעבור לפריים ספציפי ב־call stack:

(gdb) frame 1
(gdb) info locals
(gdb) print some_var

ככה תוכלו לבדוק משתנים בכל שלב במסלול הקריאות.

3. list – הצגת קוד המקור

(gdb) list

מציג את השורות האחרונות בקובץ הקוד – עוזר לראות מה בדיוק קרה סביב שורת הקריסה.

אם אתם יודע את שם הקובץ:

(gdb) list myfile.c:45

דוגמה לתוכנית עם קריסה

#include <stdio.h>

int main() {
    int *ptr = NULL;
    *ptr = 5; // קריסה
    return 0;
}
gcc -g program.c -o program
gdb ./program
(gdb) run
Program received signal SIGSEGV
(gdb) backtrace
(gdb) print ptr
(gdb) list

שימוש ב־watch -location

כבר הכרנו את watch, שעוצרת את התוכנית כשביטוי משנה ערך.
אבל לפעמים אנחנו רוצים לדעת מתי נכתב משהו לכתובת מסוימת, בלי קשר לשינוי ערך.

למשל, אם מישהו דורך על משתנה בטעות בזיכרון – הפקודה הבאה תעצור את התוכנית בדיוק כשזה קורה:

(gdb) watch -location my_var

או:

(gdb) watch -location *(int *)0x601000

הפקודה watch -location עוצרת את התוכנית בכל כתיבה לזיכרון בכתובת הנתונה, גם אם הערך שנכתב שווה לערך הקודם.

שימושי מאוד לאיתור דריסה בזיכרון.

ניתוח core dump אחרי קריסה

לפעמים התוכנית קורסת מחוץ ל־GDB, ואתם לא הספקתם להריץ אותה בתוך debugger.
אבל אפשר לגרום למערכת לשמור קובץ core – תצלום זיכרון של התוכנית בזמן הקריסה.

שלב 1: הפעלת יצירת core dump

ulimit -c unlimited

זה מאפשר למערכת לכתוב קובץ core כשהתוכנית קורסת. (נדבר בהמשך הקורס על פקודת הulimit)

שלב 2: הרצת התוכנית שגורמת לקריסה

./program
Segmentation fault (core dumped)

שלב 3: פתיחת הקובץ ב־GDB

gdb ./program core

כעת תוכלו להשתמש ב־GDB בדיוק כאילו התוכנית עדיין רצה – לבדוק backtrace, להדפיס משתנים, לעבור על frames, להסתכל בזיכרון – והכל אחרי שהקריסה קרתה בפועל.

יציאה מ־GDB

(gdb) quit

מה זה .gdbinit

כאשר מריצים את GDB, הוא קודם כל מחפש קובץ בשם .gdbinit בתיקיית הבית (~) או בתיקייה הנוכחית.
קובץ זה מכיל פקודות GDB שמורצות אוטומטית עם פתיחת הסשן – ממש כמו .bashrc ל־Bash.

בעזרת הקובץ הזה ניתן:

  • להגדיר קיצור מקשים
  • לטעון סקריפטים
  • לקבוע Breakpoints מראש
  • להפעיל הרחבות
  • לשנות צבעים, תצוגה, Layout ועוד

דוגמה בסיסית לתוכן של .gdbinit:

set disassembly-flavor intel
set pagination off
set confirm off

ישנם המון פלאגינים שאנשים מכל העולם כתבו לgdb באמצעות הgdbinit.

פלאגינים ל־GDB

כלי הGDB הוא כלי חזק בפני עצמו, אך ניתן להרחיב אותו משמעותית על־ידי פלאגינים (תוספים) שכתובים ב־Python.
פלאגינים נפוצים כוללים:
- כלי הpwndbg – תוסף מתקדם לדיבוג של תוכניות בשפת C, כולל מבני זיכרון, heap, stack, הוראות, ועוד המון
- כלי הgef (GDB Enhanced Features) – תוסף עם ממשק מינימליסטי אך עשיר.
- כלי הPEDA – פלאגין בסיסי עם הרחבות מועילות.

אנחנו נתמקד ב־pwndbg.

התקנת pwndbg

שלב 1: התקנת GDB עם תמיכה ב־Python

וודאו שה־GDB שלכם תומך ב־Python 3:

gdb --version

ואז בתוך GDB:

(gdb) python print("hello")

אם קיבלתם פלט תקין – הכל בסדר.
אם לא, התקינו מחדש GDB דרך המערכת או דרך source.

שלב 2: הורדת pwndbg

git clone https://github.com/pwndbg/pwndbg
cd pwndbg
./setup.sh

הפקודה setup.sh תבצע:

  • בניית הפלאגין
  • קישור לקובץ ~/.gdbinit
  • התקנת תלויות (כולל pip packages)

בסיום, הריצו שוב GDB ותראו את הלוגו של pwndbg.

מה pwndbg נותן?

  • תצוגת זיכרון עשירה – stack, heap, code, ומצב אוגרים בצבעים נוחים.
  • פקודות חדשות – מאות פקודות חדשות שנוספו ל־GDB.
  • שיפור הפקודות הרגילות – הפקודות הקיימות כמו x, info registers מוצגות טוב יותר.
  • פקודות Heap, Tcache, ROP, Canary, Syscalls (נדבר בהמשך הקורס)
  • עוזר לנו מאוד כשאנחנו רוצים לחקור תוכנה של מישהו, או למצוא חולשות (נדבר בהמשך הקורס על זה)

שימוש בסיסי ב־pwndbg

כאשר אתם עושים break בpwndbg, או s, n, finish, si, ni או כל פקודה כזאת או אחרת, אתם תראו מסך עשיר שמתאר אל מצב האוגרים, הקוד, המחסנית, הheap הרלוונטי והכל.
זה נקרא context, תוכלו גם להשתמש בפקודה context כדי להציג את זה בכל הזמן.

context

חיפוש בזיכרון

search "hello"
search 0xdeadbeef

פקודות shell מה־gdb

shell ls -l

מידע על המערכת

vmmap        // מפת הזיכרון של התהליך
telescope    // הצצה על זיכרון בשרשרת מצביעים

נדבר בהמשך כל אזורי זכרון בלינוקס, ובניהם נשתמש בפקודה הזו.

למעשה ישנם עוד המון פקודות עם pwndbg, נדבר עליהן בהמשך, כרגע- תהנו מהcontext העשיר שנפתח אליכם כל פעם שאתם משתמשים בדיבגר.