V8 Engine's (Dis-)Security
מאת שי מרדכי | 24 בדצמבר 2025
Mobile Security Researcher | Reverse Engineer | Vulnerability Researcher
כל סטודנט למדעי המחשב, מכיר שהעולם מתחלק לשניים - קומפיילר ואינטרפרטר - או בשפת ה-WEB, "Frontend, Backend". בעולם המודרני, מסתבר שיש בעיקר "אפור".
אם נסכם את חולשות האבטחה של 2025, הכל מתחיל ומסתיים במושג אחד: בידוד.
הבידוד הפיזי: Code vs Data
בעולם הישן, בקומפיילר (כמו C), הייתה הפרדה ברורה: הפקודות נשמרות ב-Code Section עם הרשאת הרצה, והמשתנים ב-Data Section עם הרשאת כתיבה בלבד. מערכת ההפעלה אכפה בידוד תפקידים – DATA לעולם לא יהפוך ל-CODE.
בזמן הקומפילציה, הקומפיילר מכניס את הלוגיקה של התכנית בתור פקודות מכונה ל-Code Section, ומשאיר את המשתנים ב-Data Section, כדי שבזמן הריצה, ישאר למעבד רק להריץ את כל ה-Code ביחד. הבידוד בין DATA ל-CODE נשמר פיזית ע"י ה-Flag המוכר - No Execute (NX).
בזכות היעילות של קוד מקומפל, שפות כאלה שימשו את מפתחי ה-Backend עבור שרתי WEB.
מנגד, האינטרפרטר (כמו ה-JS הישנה) הציע ניידות ובטיחות – הכל רץ בתוך "כלא" מוגן שבו הקוד מעולם לא נגע ישירות במעבד. שפות כאלה אומצו ע"י מפתחי ה-Frontend.
המנוע בדפדפן (שבד"כ כתוב ב-C++) קורא שורת JS, מפרש אותה לקוד ביניים בגודל בייט (bytecode) ומכניס ל-RAM לאזור DATA ללא הרשאת הרצה.
מנוע הדפדפן צריך לעבור כל שורה ב-Data שנוצרה ב-RAM ולהגיד *"אה, אני מכיר את הפעולה הזו, יש לי אותו הדבר רק ב-C++ בקוד של עצמי"*, ומבצע אותה כאילו היא חלק ממנו (באמת), כדי שהמעבד יוכל להריץ אותה מיד. זה מצריך מכל דפדפן להחזיק את סט הפקודות הבסיסיות שהמעבד יכול להריץ.
אפשר להגיד שמבחינת לוגים, באינטרפרטר לא ניתן לדעת האם הפקודה שהורצה, היא של אתר כזה או אחר, כי מבחינת ה-Syscalls זה הקוד של הדפדפן שהורץ.
מתכנת ה-Frontend מספק רק את ה-Script שיכנס בתור DATA ל-RAM. את התהליך העיקרי מבצע הדפדפן. זה מאפשר גמישות וניידות – אותו אתר ירוץ אצל מגוון מעבדים, אך האחריות להרצה הסופית הוטלה על הדפדפנים. המחיר של זה כמובן איטיות בביצועים.
מהפכת ה-V8 והפריצה של חומות ה-JIT
באופן עקרוני, מה אם יום אחד, האינטרפרטר יחליט שנמאס לו לפרש פקודה, לראות שהיא קיימת אצלו ולהריץ אותה, בטענה שרוב הפקודות כבר חוזרות על עצמן?
במצב כזה, הוא ירצה לקצר זמן, ובמקום רק לקרוא מה-DATA ואז להריץ מה-CODE (של עצמו), הוא ירצה להריץ ישירות מה-DATA. הייתם מסכימים לו?
*(זה כמו לתת ל-Obsidian אפשרות להריץ את הסיכום שכתבת... כמובן שהתשובה היא לא).*
למרות זאת, Google היתה הראשונה להסכים לזה בשנת 2008, כדי לקצר את זמן התגובה של הדפדפן שלה בצורה משמעותית, באמצעות מנוע V8.
הוא נבנה באמצעות טכנולוגית JIT (Just-In-Time Compilation) שהיתה קיימת אצל קומפיילרים, והוחלט לבנות את אותו הדבר לאינטרפרטר – צעד שהביא להרחבת וקטור תקיפה רחב לדפדפנים. ה-JIT מתלבש על האינטרפרטר ומאפשר לו לכתוב DATA ולהריץ מאותו מקום ב-RAM (מקטע זיכרון שמוגדר כ-RWX), בלי חסימה של Flag NX.
עם הוספת אופטימיזציות ושכבות רבות לקוד, מנוע ה-V8 נהיה מהיר יותר, אבל הפך למבנה "ספגטי" מורכב ומלא בבאגים ארכיטקטוניים.
React2Shell ועידן ה-FullStack Security
היום, בגלל שה-JIT מאפשר למידע (Data) להפוך לפקודה (Execute) בזמן אמת, אתרים מודרניים משתמשים באותה שפה (JS) בשני הצדדים ל-Frontend ול-Backend (ארכיטקטורות כמו Next.js). הנוחות הזו עלתה לנו החודש בחולשה חדשה - React2Shell, שבה קבוצות תקיפה ניצלו את הדינמיות הזו כדי להזריק פקודות ישירות לשרת.
המעבר של התעשייה ל-XDR ב-2020 לא היה רק שינוי טכנולוגי, אלא שינוי תפיסתי. הבנו שהבידוד בין ה-Web ל-Endpoint מת. כחוקר אבטחה, אני מאמין שהעתיד שייך למי שיודע לחבר את הנקודות – מה-JavaScript ב-Browser ועד ל-Instruction Pointer בזיכרון. זהו ה-FullStack Security האמיתי.
בתור משתמשים פרטיים, תמיד נחמד לעבור לסביבת עבודה בטוחה יותר. אני משתמש ב-Fedora Kinoite OS, וכך הקבצים הרגישים במערכת הם Immutable, ורוב האפליקציות רצות ב-Sandboxes נפרדים (Flatpak).
לגבי ה-V8, יש לו חלופות במהירויות דומות עם פחות סיבוך בקוד, אבל תמיד אפשר לוותר קצת על מהירות הדפדפן לטובת אבטחה, ולכבות לגמרי את ה-JIT כדי לתת ל-OS שלכם לבצע את מה שהיא צריכה - להגן לכם על המעבד.
🔬 Malware Analysis Field Notes | 5.7.26
תובנת מחקר (חוק ברזל בניתוח דינמי):
ה-Hook לא יוצב על לוגיקת הנוזקה המעורבלת, אלא על פונקציות התשתית של ה-VM (כגון new Function() או BaseDexClassLoader) המבצעות את הטעינה בפועל.
- קימפול DATA בזמן אמת מאפשר לנוזקה להריץ Shellcode, שבעבר היה נחלת השפות המתקמפלות בלבד.
- זה מאפשר גם לחוקרים להריץ Hooks תוך כדי ריצת האפליקציה עבור Dynamic Analysis דרך Frida (המבוססת בעצמה על מנוע V8).
- האתגר שמציב מנוע ה-V8: לעומת מה שאני עושה כרגע (ליירט טרנזקציה באפליקציית אנדרואיד), כשמדובר בניתוח Shellcode מקומפל ע"י V8 זה נהיה מורכב. אי אפשר לדעת מראש מה שמות הפונקציות להצבת ה-Hooks. הפתרונות: להציב Hooks בפונקציות ה-V8 לפני הקימפול, או ליירט Syscalls.
fetch() ➔ .text() ➔ new Function() ➔ runner() - אותה בעיה בדיוק נתקלתי גם ב-Hook לטעינת קובץ מבחוץ. שהפקודה ב-Java קוראת לפונקציית
DexLoaderשעושה את הפקודה האמיתית (אני חושבload). - אותו רעיון: ה-Framework, אם זה JAVA או JS, משתמש בפקודות ה-VM כגון ART JVM או JIT.