0.4 מבוא לpwntools תרגול
תרגול - מבוא לpwntools¶
בתרגול הזה נעבור מקריאה לכתיבה. נבנה שני בינאריים קטנים משלנו, ונכתוב מולם סקריפטים אמיתיים ב-pwntools: קודם רק לדבר עם התוכנית, אחר כך למצוא offset עם cyclic, ובסוף לנתב את זרימת התוכנית לכתובת שאנחנו בוחרים. אין כאן exploit מתוחכם - המטרה היא שהאצבעות שלכם יכירו את הכלים, כי מכאן והלאה נשתמש בהם בכל אתגר.
עבדו על מכונת לינוקס (או WSL) עם gcc ו-pwntools מותקנים. אם עוד לא התקנתם:
הכנה - בניית הבינאריים¶
צרו קובץ בשם echo_server.c:
#include <stdio.h>
#include <unistd.h>
int main() {
setvbuf(stdout, NULL, _IONBF, 0);
char buf[128];
puts("echo server ready");
while (1) {
printf("> ");
if (!fgets(buf, sizeof(buf), stdin))
break;
printf("you said: %s", buf);
}
return 0;
}
וקובץ שני בשם vuln.c:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void win() {
puts("congrats, flow was redirected!");
system("/bin/sh");
}
void vuln() {
char buf[64];
puts("send me your data:");
read(0, buf, 256); // reads 256 bytes into a 64-byte buffer - overflow
}
int main() {
setvbuf(stdout, NULL, _IONBF, 0);
vuln();
puts("bye");
return 0;
}
הרכיבו את שניהם. שימו לב לדגלים על vuln - אנחנו מכבים בכוונה canary ו-PIE כדי שהתרגיל יתמקד ב-pwntools ולא בעקיפת הגנות (את אלה נלמד בפרקים הבאים):
לפני שמתחילים, בדקו איזה הגנות פעילות:
אתם אמורים לראות ש-NX פעיל, אבל Canary ו-PIE כבויים. כלומר: כתובות הקוד קבועות בין הרצות, ואין canary שיעצור את הoverflow. מושלם לשלב הזה.
תרגיל 1 - לדבר עם התוכנית¶
כתבו סקריפט sol1.py שמריץ את echo_server, שולח אליו מחרוזת, וקורא בחזרה את השורה שהשרת החזיר. הדפיסו את מה שקיבלתם.
מה צריך לקרות: אתם שולחים hello, והסקריפט מדפיס שהתקבל you said: hello.
- רמז: התחילו ב-
from pwn import *ו-io = process('./echo_server'). - רמז: השרת מדפיס
>לפני כל קלט. השתמשו ב-sendlineafter(b'> ', ...)כדי להסתנכרן איתו. - רמז: לקריאת התשובה,
recvline_contains(b'you said')יחזיר בדיוק את השורה הרלוונטית. - רמז: זכרו שהכל בבתים.
b'hello', לא'hello'.
הרחבה: הוסיפו לולאה ששולחת שלוש מחרוזות שונות וקוראת שלוש תשובות. הפעילו context.log_level = 'debug' וראו את התעבורה הגולמית.
תרגיל 2 - למצוא את ה-offset עם cyclic¶
עכשיו עוברים ל-vuln. הפונקציה vuln קוראת 256 בתים לתוך buffer של 64 - overflow קלאסית. המטרה שלכם בתרגיל הזה היא רק אחת: לגלות בכמה בתים בדיוק צריך לדרוס עד שמגיעים להreturn address (ה-RIP).
כתבו סקריפט sol2.py שמזין ל-vuln רצף cyclic, נותן לו לקרוס, ומחשב את ה-offset אוטומטית.
- רמז: אפשרו core dumps בטרמינל עם
ulimit -c unlimitedלפני ההרצה. - רמז:
io.sendline(cyclic(200)), ואזio.wait()כדי לחכות לקריסה. - רמז:
core = io.corefileנותן לכם את ה-core dump. הערך שנחת ב-RIP נמצא ב-core.rip. - רמז: ב-64 ביט קחו את 4 הבתים הנמוכים:
cyclic_find(pack(core.rip)[:4]). - רמז: אם ה-core לא נוצר, אפשר במקום זה להריץ תחת
gdb.debug, לקרוס, ולהסתכל ידנית על הערך ב-RSP/RIP עם pwndbg, ואזpwn cyclic -l <value>.
מה צריך לצאת: מספר. חשבו לבד - כמה בתים אתם מצפים לראות, לפי גודל הbuffer (64) פלוס ה-rbp השמור (8)? האם המספר שקיבלתם תואם?
תרגיל 3 - לנתב את הזרימה ל-win¶
עכשיו הפאנץ'. יש לכם offset, ויש בבינארי פונקציה win שפותחת shell. חברו את שני החלקים: דרסו את הreturn address בכתובת של win, כדי שכש-vuln תחזור - היא תקפוץ ל-win במקום ל-main.
כתבו סקריפט sol3.py שבונה payload, שולח אותו, ומעביר אתכם ל-shell.
- רמז: טענו
exe = context.binary = ELF('./vuln'), וקחו את הכתובת עםexe.sym['win']. - רמז: השתמשו ב-
flat({offset: exe.sym['win']})כדי לבנות padding עד ה-offset ואז את הכתובת. - רמז: הבינארי מדפיס
send me your data:לפני הקריאה.sendlineafterיעזור לכם להסתנכרן. - רמז: סיימו ב-
io.interactive(), ואז הקלידוidאוlsכדי לוודא שאתם ב-shell. - רמז חשוב על alignment: אם ה-shell לא נפתח והתוכנית קורסת דווקא אחרי שהגעתם ל-
win, זו כמעט תמיד בעיית alignment מחסנית ל-16 בתים (הוראתmovapsבתוךsystem). הכניסו גאדג'טretבודד לפניwinכדי ליישר. חשבו איךROP(exe).find_gadget(['ret'])עוזר כאן.
הרחבה: הפכו את הסקריפט לתבנית מלאה עם פונקציית start שתומכת ב-GDB וב-REMOTE, בדיוק כמו בהרצאה. הריצו python3 sol3.py GDB, שימו נקודת עצירה על vuln, וצפו ב-RSP רגע לפני ה-ret כדי לראות את הכתובת שלכם יושבת שם.
לאן ממשיכים¶
בשלושת התרגילים האלה עשיתם בקטן את מה שנעשה בגדול לאורך כל הקורס: דיברתם עם תוכנית, מצאתם offset עם cyclic, ובניתם payload שמנתב את הזרימה. בפרק הבא נכיר את pwnable.kr ונתחיל להשתמש בכלים האלה מול אתגרים אמיתיים. הפתרון המלא, כולל תבנית שתוכלו לשכפל, נמצא בקובץ הפתרון.