9.4 מפת גישה לאתגרים הקשים הרצאה
הגענו לקצה של pwnable.kr. שתי הקטגוריות האחרונות, Grotesque ו-Hacker's Secret, הן לא עוד סט של אתגרים - הן קפיצת מדרגה. פה כבר לא מספיק לזהות באג ולכתוב ROP chain: אתגר בודד יכול לדרוש הרצה סימבולית של מבוך ענק, ניתוח מודול kernel, מציאת באג בתוך אמולטור שלם, או שחזור של חולשת kernel אמיתית ממכשיר אנדרואיד. אף אחד לא פותר את כל אלה מהראש. מה שכן עושים חוקרי חולשות מנוסים זה טריאז' - מסתכלים על אתגר, ממיינים אותו לקטגוריית טכניקה מוכרת, ומפעילים את הכלי הנכון. ההרצאה הזו היא בדיוק זה: מפת גישה. לא פתרונות מלאים (חלק מהאתגרים האלה היו הופכים את השיעור לספר), אלא מיון של כל אתגר לפי הטכניקה שהוא דורש ולפי הפרק בקורס שכבר לימד אתכם אותה. המטרה: כשתפתחו אתגר קשה, תדעו מאיפה להתחיל.
שתי הקטגוריות הקשות - Grotesque ו-Hacker's Secret¶
כזכור מפרק המבוא ל-pwnable.kr, ארבע הקטגוריות עולות בקושי: Toddler's Bottle, Rookiss, Grotesque, ו-Hacker's Secret. את השתיים הראשונות כיסינו לרוחב לאורך הקורס. השתיים האחרונות שונות באופיין:
- קטגוריית Grotesque ("מכוער אבל טעים") - אתגרים ארוכים שדורשים לשרשר כמה טכניקות, או להוציא כלי כבד מהארגז (הרצה סימבולית, RE של ארכיטקטורה זרה, heap exploitation דרך interface תפריטים). הבאג לא תמיד צועק מהדף. לפעמים צריך קודם להבין מבנה נתונים שלם.
- קטגוריית Hacker's Secret ("סודות של האקר") - הרמה הגבוהה באתר. פה יש אמולטורים שלמים לניתוח, חולשות kernel אמיתיות מהעולם (עם CVE ותאריך), ובריחות מ-sandbox. חלק מהאתגרים האלה הם למעשה תרגיל בשחזור מחקר שכבר פורסם, לא בהמצאה מאפס.
שימו לב לנקודה חשובה: המפה הזו לא נותנת לכם exploit מוכן. חלק מהאתגרים דורשים חומרה מסוימת, kernel מסוים, או פשוט הרבה שעות RE שאי אפשר לקצר. מה שהמפה כן נותנת: לכל אתגר, איזו משפחת טכניקה הוא, איזה פרק כבר לימד אתכם אותה, ומה הצעד הראשון החכם.
שיטת הטריאז' - triage¶
לפני שנכנסים לרשימות, בואו נבסס את השיטה. כשאתם ניגשים לאתגר קשה שלא ראיתם, אתם לא "מתחילים לפצח" - אתם קודם ממיינים. הריקון (recon) הראשוני זהה תמיד:
file ./chall # ארכיטקטורה? 32/64? ARM/MIPS? סטטי/דינמי?
checksec --file=./chall # אילו הגנות? NX, canary, PIE, RELRO
strings -n 8 ./chall # רמזים: שמות פונקציות, נתיבים, הודעות, /dev/...
nm ./chall | head # סימבולים - יש win()? יש main ברור?
./chall < /dev/null # מה זה בכלל עושה? תפריט? קורא stdin? מאזין?
מהפלט הזה אתם מסווגים את האתגר לאחת מהמשפחות. הנה עץ ההחלטה שאני מריץ בראש:
new hard challenge
|
+-------------------+--------------------+
| | |
runs in the kernel? is it an emulator? regular userland?
| | |
.ko module / VM/CPU/MMU memory bug or
/dev/xxx / inside a program? logic bug in
device CVE | the process
| RE of the emulator +------+------+-------+------+
kernel analysis + + OOB bug in | | | |
CVE reproduction address translation heap huge foreign random
(chapter 9) CRUD maze arch input/
(7) (angr) (8) automation
(angr/scripting)
ברגע שמיינתם, כל השאר הוא הפעלת ידע שכבר יש לכם מהפרקים הקודמים. עכשיו נעבור על שתי הקטגוריות ונמיין כל אתגר.
אשכולות הטכניקה ב-Grotesque¶
הטבלה ממיינת כל אתגר למשפחת הטכניקה הדומיננטית שלו ולפרק שמכסה אותה. חלק מהסיווגים הם לפי הריקון הראשוני והשם - כשתפתחו את האתגר, אמתו מול הקוד:
| הטכניקה הדומיננטית | האתגרים | פרק / כלי בקורס |
|---|---|---|
| הרצה סימבולית - symbolic execution | maze, hunter | angr (מוצג פה) |
| ייצור exploit אוטומטי - AEG | aeg, asg | angr / angrop (פה) |
| הheap exploitation דרך interface תפריטים | note | פרק 7 - exploitation ה-Heap |
| קוד מכונה מוגבל - constrained shellcode | ascii, asm3 | פרק 3 - Shellcode |
| ארכיטקטורה זרה - MIPS | mipstake | פרק 8 - ARM ו-MIPS |
| ניתוח מודול kernel - rootkit | rootkit | פרק 9 - kernel exploitation |
| באג לוגי / חיפוש תחת אילוצים | coin2, cmd3, sizcaller | פרקים 1-2 |
| הבנת פורמט בינארי - ELF | elf, lfh | פרק 8 (ELF) / פרק 7 (heap של Windows) |
| RE כבד וטריאז' פרטני | wtf, starcraft | פרק 8 - reverse engineering |
עכשיו אסטרטגיית פתיחה קצרה לכל אשכול:
מבוכים והרצה סימבולית - maze, hunter¶
האתגר maze הוא מבוך ענק: התוכנית קוראת סדרת מהלכים, וכל מהלך מסתעף. מספר המסלולים אקספוננציאלי, אז brute force ידני לא ריאלי. פה נכנסת הרצה סימבולית: במקום לנחש קלט, נותנים לכלי לחקור את כל המסלולים בבת אחת עם קלט "סימבולי" (משתנה, לא ערך קונקרטי), ומבקשים ממנו למצוא קלט שמגיע לכתובת ה"ניצחון" ונמנע מכתובת ה"מוות". אותו רעיון בדיוק חל על hunter. הכלי הוא angr, ונראה אותו בסעיף נפרד.
ייצור exploit אוטומטי - aeg, asg¶
השם aeg הוא ראשי תיבות של Automatic Exploit Generation. באתגר הזה השרת מזרים אליכם בינארים vulnerable בזה אחר זה, ולכל אחד יש חלון זמן קצר לייצר exploit - ידנית זה בלתי אפשרי. הרעיון: הרצה סימבולית שמזהה מתי מצביע ההוראה (rip) הופך לבלתי מאולץ (unconstrained), כלומר התוקף שולט בו, ואז מאלצת אותו לכתובת מטרה ומחלצת את הקלט. זה בדיוק מה ש-angr נבנה בשבילו, ונדגים סקלטון עובד. הסיווג של asg הוא לפי האופי - אוטומציה של exploitation על סדרת מטרות. בכל מקרה, הפרימיטיבים עצמם (overflow מחסנית, ret2win) הם מפרקים 2-4, וכל מה שחדש פה הוא האוטומציה.
הheap exploitation דרך interface תפריטים - note¶
האתגר note הוא מנהל פתקים קלאסי: תפריט של add / edit / delete / view. זה הדפוס הכי נפוץ בheap exploitation אמיתי, ואתם כבר מכירים אותו מפרק 7. הצעד הראשון: למפות איזו אופציה קוראת ל-malloc, איזו ל-free, ואיפה יש קריאה אחרי שחרור (UAF) או overflow. משם זה tcache/fastbin poisoning כמו שלמדנו.
קוד מכונה מוגבל - ascii, asm3¶
באתגר ascii הקלט חייב להיות מתווים מודפסים (ASCII) בלבד, אז shellcode רגיל לא יעבור את הפילטר. הפתרון הוא shellcode אלפאנומרי - בדיוק החומר של פרק 3 (shellcode מוגבל). האתגר asm3 הוא במשפחה של asm/asm2: כותבים shellcode שקורא לקבצים או ל-syscall מסוים תחת אילוצי מקום או תווים. הכלים: pwn.asm, pwn.shellcraft, ומקודדים אלפאנומריים.
ארכיטקטורה זרה - mipstake¶
השם mipstake הוא משחק מילים על MIPS. זה אתגר בארכיטקטורת MIPS, ופה כל האינטואיציה של x86 לא רלוונטית: אין מחסנית שמנהלת return address באותה צורה (הכתובת ב-$ra), יש delay slot אחרי קפיצות, וה-syscalls שונים. זה בדיוק פרק 8 (ARM ו-MIPS). מריצים ומנפים עם qemu-mips ו-gdb-multiarch.
מודול kernel - rootkit¶
האתגר rootkit הוא ניתוח kernel: מודול נטען (.ko) שמסתיר משהו או מגן על הדגל. הצעד הראשון הוא RE של המודול - איזה syscall הוא מיירט (hook), מה הוא מסתיר, ואיך לגרום לו לחשוף את מה שאנחנו רוצים. זה מתחבר ישירות לפרק 9. הכלים: objdump/IDA על ה-.ko, ו-/proc, dmesg, וקריאות ioctl כדי לתקשר איתו.
לוגיקה וחיפוש תחת אילוצים - coin2, cmd3, sizcaller¶
האתגר coin2 הוא הגרסה הקשה של coin1: משחק ניחוש עם משוב, שדורש חיפוש יעיל (בינארי / תורת האינפורמציה) תחת אילוצי זמן קשוחים - כלומר סקריפט מהיר ב-pwntools, לא ניחוש ידני. האתגר cmd3 הוא הזרקת פקודות עם פילטר מחמיר יותר מ-cmd1/cmd2: צריך לעקוף רשימת תווים או ארגומנטים אסורים. הסיווג של sizcaller הוא לפי השם - בעיית גודל/אריתמטיקה שמובילה לקריאה שגויה; מטפלים בו כבאג לוגי או שלמות מספרים מפרקים 1-2. בכל המקרים האלה, קראו את המקור והבינו את הלוגיקה לפני שאתם נוגעים בזיכרון.
פורמט בינארי - elf, lfh¶
האתגר elf דורש הבנה של מבנה קובץ ELF - לפעמים לזייף כותרות, לפעמים לפרסר אותן. זה חומר של פרק 8. השם lfh מרמז על Low Fragmentation Heap, מנגנון ה-heap של Windows: זה אתגר heap exploitation אבל של האלוקטור של Windows, לא של glibc. עקרונות ה-heap מפרק 7 חלים, אבל מבני הנתונים שונים - צריך להכיר את ה-LFH הספציפי.
הreverse engineering כבדה - wtf ו-starcraft¶
שני אלה הם "פתח וראה": אתגרים עם control flow חריג או לוגיקה משחקית שדורשים RE יסודי לפני שבכלל אפשר לסווג את הבאג. הצעד הראשון זהה - strings, דיסאסמבלי, והרצה תחת gdb כדי להבין מה התוכנית עושה. אחרי הריקון, הם בדרך כלל נופלים לאחת המשפחות שכבר ראינו.
אשכולות הטכניקה ב-Hacker's Secret¶
הקטגוריה הזו נשענת פחות על "טריק" ויותר על ידע רחב: אמולטורים, kernel, ומחקר CVE אמיתי.
| הטכניקה הדומיננטית | האתגרים | פרק / כלי בקורס |
|---|---|---|
| באג באמולטור - MMU / CPU | softmmu, dos4fun | פרק 8 (אמולטורים) + בטיחות זיכרון |
| שחזור חולשת kernel אמיתית - device CVE | towelroot, exynos | פרק 9 + מחקר CVE |
| ניתוח kernel / malware | malware, kcrc, nuclear | פרק 9 - kernel exploitation |
| בינארי מינימלי - RE כבד | tiny_hard, asm2 | פרק 8 (בינארי זעיר) / פרק 3 |
| בריחה מ-sandbox | pwnsandbox | פרק 9 - טכניקות מתקדמות |
אסטרטגיית פתיחה לכל אשכול:
באג באמולטור - softmmu, dos4fun¶
האתגר softmmu הוא אמולטור עם MMU תוכנתי (soft MMU): התוכנית מדמה מעבד עם תרגום כתובות משלה. הבאג כמעט תמיד יושב בתרגום הכתובת - גישה מחוץ לתחום (OOB) שבה כתובת "וירטואלית" של הגיסט מתורגמת לכתובת מחוץ לזיכרון שהוקצה לו, ונותנת קריאה/כתיבה בזיכרון של המארח. הצעד הראשון: RE של פונקציית התרגום, ולחפש איפה חסרה בדיקת גבולות. אותו רעיון חל על dos4fun, שהוא אמולטור בסגנון DOS/real-mode - מחפשים באג בלוגיקת האמולציה. זה מתחבר לפרק 8 (מפרשים ומכונות וירטואליות), רק שהמטרה הסופית היא r/w בזיכרון המארח ומשם shell.
שחזור חולשת kernel אמיתית - towelroot, exynos¶
שני האתגרים האלה הם שחזור של חולשות kernel אמיתיות ומתועדות ממכשירי אנדרואיד:
- האתגר towelroot מבוסס על CVE-2014-3153, באג ב-
futex(הפעולהFUTEX_REQUEUE) שגילה geohot והשתמש בו לרוט של מכשירי אנדרואיד. הצעד הראשון הוא מחקר: לקרוא את ה-CVE ואת הכתיבה המקורית, להבין מה הפרימיטיב (קורפשן של מבנה על מחסנית הkernel), ולשחזר את הchain שמובילה מ-userland ל-root. - האתגר exynos מבוסס על החולשה ב-
/dev/exynos-memבמכשירי Samsung מבוססי Exynos (CVE-2012-6422), שאיפשרה ל-userland למפות זיכרון פיזי שרירותי ולכתוב לזיכרון הkernel. הצעד הראשון זהה: לקרוא את פרסום החולשה המקורי, ולהבין איך מיפוי הזיכרון הפיזי הופך לכתיבה לkernel.
בשני המקרים אתם לא ממציאים כלום - אתם מבצעים מחקר CVE ומשחזרים. נרחיב על תהליך המחקר בסעיף נפרד.
ניתוח kernel ו-malware - malware, kcrc, nuclear¶
האתגר malware דורש reverse של דגימה זדונית - להבין מה היא עושה ולחלץ ממנה את הדגל או את ההתנהגות. הסיווג של kcrc הוא לפי השם: בדיקת CRC ברמת הkernel, שצריך להבין ולזייף מולה קלט תקין. הסיווג של nuclear הוא לפי הריקון - סווגו אותו אחרי file/strings, סביר שהוא RE/לוגיקה. לכל אלה הכלי הוא RE סבלני: דיסאסמבלר, מעקב דינמי, והבנה של מה התוכנית מצפה לו.
בינארי מינימלי - tiny_hard, asm2¶
האתגר tiny_hard הוא הגרסה הקשה של tiny_easy מפרק 8: בינארי זעיר עם כמעט אפס גאדג'טים ואפס מידע. הטריק הקלאסי הוא לשלוט במחסנית דרך משתני הסביבה וה-argv שהkernel מניח בכניסה, ולבנות משם shellcode או ROP מינימלי. האתגר asm2 הוא במשפחת ה-asm - shellcode ברמת syscall תחת אילוצים, חומר של פרק 3.
בריחה מ-sandbox - pwnsandbox¶
האתגר pwnsandbox נותן לכם סביבה מוגבלת (seccomp, namespace, או sandbox מותאם) ומבקש לברוח ממנה. הצעד הראשון: להבין בדיוק מה ה-sandbox אוסר (אילו syscalls חסומים? seccomp-tools dump הוא הכלי), ואז למצוא נתיב שעוקף את ההגבלה - למשל syscall שלא נחסם, או באג במימוש ה-sandbox עצמו.
הרצה סימבולית ו-AEG - angr¶
הכלי החדש שהפרק הזה מוסיף לארגז הוא angr - מסגרת הרצה סימבולית בפייתון. במקום להריץ תוכנית עם קלט קונקרטי אחד, angr מריץ אותה עם קלט סימבולי ובונה, לכל מסלול, מערכת אילוצים (constraints) על הקלט. אחר כך פותר SMT מוצא קלט קונקרטי שמספק את האילוצים של המסלול שרצינו. זה בדיוק הכלי למבוכים (maze, hunter) ולבסיס של AEG.
פתרון מבוך - maze¶
התבנית הבסיסית: טוענים את הבינארי, נותנים ל-angr לחקור עד שהוא מגיע לכתובת ה"ניצחון", ומחלצים את ה-stdin שהוביל לשם:
import angr
# auto_load_libs=False speeds things up a lot - we don't load libc symbolically
proj = angr.Project('./maze', auto_load_libs=False)
state = proj.factory.entry_state()
simgr = proj.factory.simulation_manager(state)
# WIN = the address that prints "You win" ; LOSE = the address of "You lose"
simgr.explore(find=0x400b3d, avoid=0x400b70)
if simgr.found:
solution = simgr.found[0]
print(solution.posix.dumps(0)) # the input (stdin) that solves the maze
else:
print('no path found')
את הכתובות WIN ו-LOSE מוצאים בדיסאסמבלי (objdump -d ./maze, או מחפשים את המחרוזות עם strings/gdb ומאיפה ניגשים אליהן). זהו - במקום לפתור מבוך אקספוננציאלי ביד, angr חוקר את כל המסלולים ופולט את הקלט המנצח.
שלד של AEG¶
הרעיון של AEG: להריץ סימבולית עם קלט סימבולי, ולחכות למצב שבו מצביע ההוראה הופך לבלתי מאולץ - כלומר הקלט שלנו זרם ל-rip ואנחנו שולטים בו. אז מוסיפים אילוץ ש-rip שווה לכתובת המטרה (למשל win, או כתובת shellcode), ומבקשים מהפותר את הקלט:
import angr, claripy
proj = angr.Project('./vuln', auto_load_libs=False)
# a generously-sized symbolic input that will flow through the vulnerable call
sym_stdin = claripy.BVS('stdin', 8 * 200)
state = proj.factory.full_init_state(
stdin=sym_stdin,
add_options=angr.options.unicorn,
)
# save_unconstrained=True keeps states where the pc register becomes symbolic
simgr = proj.factory.simulation_manager(state, save_unconstrained=True)
# run until we find a state where we control pc
while not simgr.unconstrained and (simgr.active or simgr.deferred):
simgr.step()
crash = simgr.unconstrained[0]
# now pc is symbolic - force it to our target and extract the input
WIN = 0x4011b6
crash.add_constraints(crash.regs.pc == WIN)
assert crash.satisfiable()
payload = crash.posix.dumps(0)
open('payload.bin', 'wb').write(payload)
print('offset -> pc under our control; payload written')
הסקלטון הזה עובד על בינארי הדגמה פשוט: overflow מחסנית בלי canary, בלי PIE (כך שכתובת היעד קבועה), עם קריאה כמו gets(buf). הוא מוצא לבד את ה-offset ל-rip ובונה payload שקופץ ל-WIN. בפועל ל-AEG אמיתי מוסיפים שכבות: זיהוי אוטומטי של הגנות, בחירת ROP עם angrop, וטיפול בקלט לא מיושר. אבל העיקרון - "חכה ל-pc סימבולי, אלץ אותו, פתור" - הוא הלב, וזה מה שהופך אתגר כמו aeg מבלתי אפשרי ידנית לפתיר בסקריפט.
חשוב לסייג: angr חזק אבל לא קסם. על מבוכים ובינארים קטנים הוא מבריק. על בינארי גדול עם לולאות כבדות או קריפטו הוא יתפוצץ (state explosion), ואז חוזרים לניתוח ידני או משלבים - פותרים ידנית עד לקטע הקשה ומפעילים angr רק עליו.
מחקר CVE להתקני מציאות - towelroot ו-exynos¶
אתגרים כמו towelroot ו-exynos הם סוג אחר לגמרי של עבודה: לא מוצאים באג חדש, אלא משחזרים מחקר שכבר פורסם. תהליך העבודה:
1. identify -> from the name and strings, which published vulnerability does the challenge refer to?
2. research -> read the CVE, the original write-up, and the public PoC code.
3. primitive -> what's the primitive? (write to kernel memory? struct corruption? physical mapping?)
4. match -> which kernel/device version does the challenge run? match the offsets to it.
5. reproduce -> build the LPE chain from userland to root and verify it.
לדוגמה, עבור towelroot מזהים את CVE-2014-3153 (הבאג ב-FUTEX_REQUEUE), קוראים את ההסבר של geohot, ומבינים שהפרימיטיב הוא קורפשן מבנה waiter על מחסנית הkernel שמאפשרת בסופו של דבר כתיבה לכתובת נשלטת. עבור exynos מזהים את CVE-2012-6422, קוראים את הפרסום על /dev/exynos-mem, ומבינים שהחולשה היא שהקובץ הזה מאפשר ל-userland mmap של זיכרון פיזי שרירותי - כולל הזיכרון של הkernel עצמו - ומשם כותבים ל-cred של התהליך או ל-syscall table. הנקודה: כאן ה"פתרון" הוא ביבליוגרפי לפחות כמו טכני. חוקר חולשות טוב יודע לקרוא CVE, למצוא PoC, ולשחזר.
כלים למפה¶
לצד הכלים שאתם כבר מכירים (gdb/pwndbg, checksec, ROPgadget, one_gadget, objdump, pwntools), האתגרים הקשים מוסיפים כמה כלים:
- הכלי angr - הרצה סימבולית. למבוכים, AEG, ומציאת קלט שמגיע ליעד. עם
angropלבניית ROP אוטומטית. - הכלי qemu-user (
qemu-mips,qemu-arm) יחד עםgdb-multiarch- להרצה וניפוי של בינארים בארכיטקטורה זרה (mipstake). - הכלי seccomp-tools (
seccomp-tools dump ./chall) - לחילוץ מדיניות ה-seccomp באתגרי sandbox. - דיסאסמבלר מלא (IDA / Ghidra / Binary Ninja) - ל-RE כבד של אמולטורים, malware, ומודולי kernel.
- מסד CVE ומנועי חיפוש (NVD, כתיבות מקוריות, exploit-db) - לאתגרי ה-device CVE. זה כלי לכל דבר.
סיכום¶
- שתי הקטגוריות הקשות ב-pwnable.kr, Grotesque ו-Hacker's Secret, לא נפתרות מהראש - נפתרות בטריאז': ריקון, מיון למשפחת טכניקה, הפעלת הכלי הנכון.
- הריקון תמיד זהה:
file,checksec,strings,nm, והרצה ראשונית. הפלט קובע לאיזו משפחה האתגר שייך. - אתגרי Grotesque מתמפים לפרקים שכבר למדתם: heap (note) לפרק 7, shellcode מוגבל (ascii, asm3) לפרק 3, MIPS (mipstake) לפרק 8, kernel (rootkit) לפרק 9, ולוגיקה (coin2, cmd3) לפרקים 1-2. החדשים - maze ו-hunter ל-angr, ו-aeg לאוטומציה.
- אתגרי Hacker's Secret מוסיפים אמולטורים (softmmu, dos4fun) עם באגי OOB בתרגום כתובות, ואת אתגרי ה-device CVE (towelroot, exynos) שהם שחזור מחקר מפורסם, לא המצאה.
- הכלי החדש של הפרק הוא angr: למבוכים חוקרים עם
explore(find, avoid)ומחלציםposix.dumps(0); ל-AEG מחכים ל-pc בלתי מאולץ, מאלצים אותו ליעד, ופותרים. - לאתגרי CVE, תהליך העבודה הוא מחקר: מזהים את החולשה המפורסמת, קוראים את הפרסום וה-PoC, מבינים את הפרימיטיב, ומשחזרים. לדעת לקרוא CVE היא מיומנות אמיתית של חוקר.