לדלג לתוכן

0.2 סביבת העבודה פתרון

פתרון - הקמת סביבת העבודה

כאן נעבור על כל תרגיל עם הפקודות המדויקות, הפלט הצפוי, ופתרון לתקלות הנפוצות. כל הפלטים הם לדוגמה מ-Ubuntu 22.04 עם glibc 2.35 - אצלכם המספרים המדויקים (כתובות, offsets, גרסאות) עשויים להשתנות, וזה בסדר גמור.


פתרון תרגיל 1 - התקנת הבסיס והמהדר

sudo apt update
sudo apt install -y build-essential gdb gcc-multilib git python3 python3-pip python3-venv
gcc --version
gdb --version

הידור והרצה של קובץ ההדגמה:

gcc -o demo demo.c
# gcc will warn: warning: implicit declaration of function 'gets' - this is expected and fine
echo "hello" | ./demo

פלט צפוי:

input> got: hello

תקלות נפוצות:

  • אם gcc: command not found - החבילה build-essential לא הותקנה. הריצו שוב את שורת ה-apt.
  • אם אין לכם הרשאת sudo - צריך משתמש עם הרשאות מנהל. במכונה וירטואלית שהקמתם, המשתמש שיצרתם בהתקנה בדרך כלל בקבוצת sudo.

למה זה עבד: build-essential מביא את gcc ואת כותרות ה-libc, ו-gets עדיין קיים ב-libc (למרות שהוא הוסר מהתקן C11) אז הבינארי מתקמפל ורץ.


פתרון תרגיל 2 - התקנת pwntools ותוסף ה-gdb

התקנת pwntools בתוך סביבה וירטואלית:

python3 -m venv ~/pwn-venv
source ~/pwn-venv/bin/activate
pip install --upgrade pip
pip install pwntools
pwn --version

התקנת pwndbg:

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

בדיקה שהתוסף נטען:

gdb -q ./demo

בתוך gdb:

pwndbg> checksec
File:     /home/user/work/demo
Arch:     amd64
RELRO:      Full RELRO
Stack:      Canary found
NX:         NX enabled
PIE:        PIE enabled
pwndbg> quit

עצם ה-prompt שהשתנה ל-pwndbg> הוא הסימן הראשון שהתוסף חי. אם אתם רואים checksec נותן טבלה - מצוין.

תקלות נפוצות:

  • אם ה-prompt נשאר (gdb) ו-checksec נותן "Undefined command" - התוסף לא נטען. בדקו שקובץ ~/.gdbinit מכיל שורה שטוענת את pwndbg, ופתחו gdb מחדש.
  • אם התקנתם גם pwndbg וגם GEF - הם מתנגשים ב-~/.gdbinit. השאירו רק אחד.
  • אם pip install pwntools נכשל על ספריות מערכת חסרות, התקינו: sudo apt install -y python3-dev libffi-dev build-essential.

למה זה עבד: setup.sh מוסיף ל-~/.gdbinit שורת source שטוענת את pwndbg בכל הפעלת gdb, אז מאותו רגע כל gdb הוא gdb משודרג.


פתרון תרגיל 3 - הידור בכמה תצורות וקריאת checksec

הידור ארבע התצורות:

gcc -o demo_default demo.c
gcc -fno-stack-protector -o demo_nocanary demo.c
gcc -fno-stack-protector -z execstack -o demo_nonx demo.c
gcc -fno-stack-protector -no-pie -static -o demo_nopie_static demo.c

קריאת ה-checksec לכל אחד:

for f in demo_default demo_nocanary demo_nonx demo_nopie_static; do
  echo "=== $f ==="; checksec --file=$f
done

פלט צפוי (מקוצר לשדות המעניינים):

=== demo_default ===
RELRO: Full RELRO   Canary: found       NX: enabled    PIE: PIE enabled
=== demo_nocanary ===
RELRO: Full RELRO   Canary: No canary   NX: enabled    PIE: PIE enabled
=== demo_nonx ===
RELRO: Full RELRO   Canary: No canary   NX: disabled   PIE: PIE enabled
=== demo_nopie_static ===
RELRO: Partial      Canary: No canary   NX: enabled    PIE: No PIE

תשובות לשאלות:

  • הדגל -fno-stack-protector הוא זה שגרם ל-Stack Canary להיעלם. בלעדיו gcc מדליק canary אוטומטית על פונקציות עם buffers.
  • ב-demo_nonx הדגל -z execstack הפך את המחסנית לניתנת להרצה, אז NX הוא disabled. זה מסוכן כי עכשיו אפשר לכתוב shellcode ישירות על המחסנית ולקפוץ אליו - בדיוק מה שנעשה בפרק ה-shellcode.
  • הקובץ demo_nopie_static גדול בהרבה (מאות KB עד מגה במקום כמה KB), כי -static מכניס את כל ה-libc לתוך הבינארי במקום לקשר אותו בזמן ריצה. בגלל זה גם יש בו הרבה יותר גאדג'טים, כפי שנראה בתרגיל הבא.

הערה על 32 ביט: אם תרצו לבנות גרסת 32 ביט לתרגול עתידי, הוסיפו -m32:

gcc -m32 -fno-stack-protector -no-pie -o demo32 demo.c

למה זה עבד: כל דגל מכבה במדויק הגנה אחת, וה-checksec קורא את המטא-דאטה של ה-ELF (למשל את סגמנט GNU_STACK בשביל NX, ואת נוכחות __stack_chk_fail בשביל canary) ומדווח על התוצאה.


פתרון תרגיל 4 - שאר ארגז הכלים

התקנה:

pip install ROPgadget ropper
sudo apt install -y ruby ruby-dev ltrace strace
sudo gem install one_gadget
# optional, for foreign architectures:
sudo apt install -y qemu-user qemu-user-static gdb-multiarch

חיפוש גאדג'ט בבינארי הסטטי:

ROPgadget --binary ./demo_nopie_static --only "pop|ret" | grep "pop rdi"

פלט צפוי (הכתובת תשתנה):

0x0000000000401f4f : pop rdi ; ret

הבינארי הסטטי מלא בגאדג'טים כי -static הכניס את כל קוד ה-libc פנימה - אלפי פונקציות, ואיתן אלפי רצפי הוראות שמסתיימים ב-ret. בבינארי דינמי רגיל הגאדג'טים היושבים בקוד שלנו מעטים בהרבה.

הרצת one_gadget על ה-libc של המערכת:

ldd ./demo_default        # locates the path to libc.so.6
one_gadget /lib/x86_64-linux-gnu/libc.so.6

פלט צפוי (offsets ותנאים ישתנו בין גרסאות glibc):

0x50a37 execve("/bin/sh", rsp+0x40, environ)
constraints:
  rsp & 0xf == 0
  rcx == NULL
...

כל שורה היא offset בתוך ה-libc, ומתחתיה התנאים שחייבים להתקיים ברגע הקפיצה כדי שנקבל shell. נלמד לבחור ולעמוד בתנאים האלה בפרק ret2libc.

מעקב עם ltrace:

echo "hello" | ltrace ./demo_default

פלט צפוי:

printf("input> ")            = 7
gets(0x7ffe...)              = 0x7ffe...
printf("got: %s\n", "hello") = 11
+++ exited (status 0) +++

רואים במפורש את הקריאה ל-gets בלי בדיקת גבולות - בדיוק החולשה שנתקוף.

תקלות נפוצות:

  • אם gem install one_gadget נכשל בקומפילציה של הרחבות מקומיות - חסר ruby-dev. התקינו אותו והריצו שוב.
  • אם ROPgadget או ropper לא נמצאים אחרי pip, ודאו שה-venv פעיל (source ~/pwn-venv/bin/activate) או הוסיפו את ~/.local/bin ל-PATH.

פתרון תרגיל 5 - כיבוי ASLR ובדיקת גרסת glibc

cat /proc/sys/kernel/randomize_va_space      # usually 2 (full)
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
cat /proc/sys/kernel/randomize_va_space      # now 0

בדיקה חזותית שהכתובות התייצבו - הריצו פעמיים ורואים אותה כתובת מחסנית:

./demo_nopie_static &                       # or any binary
cat /proc/$!/maps | grep stack

גרסת ה-glibc:

ldd --version | head -1
# for example: ldd (Ubuntu GLIBC 2.35-0ubuntu3.8) 2.35

רשמו את המספר הזה. כשנעבוד מול אתגר מרוחק שרץ עם glibc אחר, נצטרך patchelf או pwninit כדי להריץ מקומית מול ה-libc של השרת - כמו שהוסבר בהרצאה.

זכרו: אחרי אתחול המכונה ה-ASLR חוזר לערך ברירת המחדל. אם אתם רוצים כיבוי קבוע זה עניין של הגדרת sysctl, אבל לרוב עדיף להשאיר כיבוי זמני רק לזמן הפיתוח.


פתרון אתגר הסיום - הרצה של הכל דרך pwntools

שמרו קובץ solve.py:

from pwn import *

context.binary = ELF('./demo_default')   # determines amd64 automatically
context.log_level = 'info'

p = process('./demo_default')
p.recvuntil(b'input> ')
p.sendline(b'hello from pwntools')
print(p.recvall(timeout=1).decode())

הרצה (מתוך ה-venv עם pwntools):

source ~/pwn-venv/bin/activate
python3 solve.py

פלט צפוי:

[*] '/home/user/work/demo_default'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Starting local process './demo_default': pid 12345
got: hello from pwntools
[+] Receiving all data: Done

למה זה עבד: context.binary = ELF(...) קרא מהקובץ שזה בינארי amd64 והדפיס את פרופיל ההגנות שלו, process הפעיל אותו כתת-תהליך, ו-pwntools ניהל את הצינור (stdin/stdout) בשבילנו. זה בדיוק השלד שנרחיב בכל שאר הקורס - רק שבמקום sendline תמים נשלח payload שדורס את הreturn address.

איך להכליל: החליפו process('./x') ב-remote('host', port) וקיבלתם exploit מרוחק. הוסיפו gdb.debug('./x', gdbscript='b vuln') במקום process כדי לפתוח את הבינארי ישירות תחת pwndbg. השלד הזה - ELF, פתיחת תהליך, שליחה, קבלה - הוא הבסיס לכל מה שנעשה מכאן והלאה.

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