317
0

כיצד Malware-ים רותמים את נפלאות הקריפטוגרפיה לטובתם?

317
זמן קריאה: 13 דקות

שמעתם על AES? לא? טוב נו.
אולי על Base64 Encoding???
XOR Encoding ? גם לא?
מזל טוב🥳 אתם חיים באיגלו (אני יודע – זה כיף) אבל אני הולך לעזור לכם לצאת ממנו😉

אז על מה אנחנו הולכים לדבר?

כרגיל אנחנו הולכים לדבר על Malware-ים (אחרת בשביל מה אני פה 😊), נראה איך Malware-ים משתמשים באלגוריתמי הצפנה מורכבים כמו AES ושיטות הצפנה “יותר פשוטות לכאורה” כמו Base64 לצורך הסתרת הפונקציונאליות שלהן.
תוקפים ירצו לעשות זאת, כדי להקשות על ה – Malware Analyst מלהשיג הבנה מלאה של מה הפוגען עושה ואיך הוא עושה זאת.

*הערת שוליים מעניינת – בהרבה מקרים תוקפים דווקא יעדיפו להשתמש בשיטות ה – Encryption היותר פשוטות כמו: Base64 Encoding או XOR Encoding דווקא בגלל שהן:

  • “חסכוניות במקום” – ניתן להשתמש בהן ב – Space-Constrained Environments ואף ב – Shellcode-ים למיניהם.
  • “פחות גלויות לעין” – הן אינן דורשות קריאה לפונקציות מתוך ה – Cryptographic API של הצפנה מסוימת ולכן יותר “שקטות” אל מול עיני ה – Analyst.
  • “יעילות” – בגלל שמדובר בשיטות הצפנה יחסית מצומצמות שאינן דורשות מספר רב של שורות קוד ו/או קריאה לפונקציות API חיצוניות, הן פחות משפיעות על ביצועי ה – Malware בזמן הריצה.

סקירה קצרה על Encryption Methods

XOR Encryption

Arguably השיטה הכי By the Book שנעשה בה שימוש, כזה או אחר, בקרב Malware-ים.
במימוש הפשטני ביותר, בעצם משתמשים בתו (Byte) המשמש כמפתח, למשל: 0x3C.
אם על כל תו במחרוזת שלנו נבצע פעולת XOR עם המפתח, נקבל הצגה שונה לגמרי של המחרוזת הקיימת, חסרת משמעות בעיני אדם.

למשל, הכתובת: http://badsite.com עם ביצוע פעולת ה – XOR, בעזרת המפתח – 0x2d, על כל תו ותו תתורגם ל – VJJN…_ZMWJ[.]QS.

סה”כ נשמע לא כזה נורא אהה?
העניין מסתבך כשלוקחים את השיטה הזו ומשחקים איתה קצת😈
למשל, בהינתן מחרוזת ASCII בעלת משמעות כמו כתובת IP או Domain, או אף שמות של פונקציות עצמן בהן הפוגען משתמש – נוכל להשתמש ב – תוכן המחרוזת עצמה, לצורך ביצוע ההצפנה.
נוכל, להפעיל את פעולת ה – XOR בתחילת המחרוזת או בסופה ( או תכל’ס בכל מיקום במחרוזת אותו נבחר) על תו כלשהו, תו הפלט שנקבל מפעולת ה – XOR ישמש אותנו כ – “מפתח” לביצוע פעולת ה – XOR עם התו הבא במחרוזת וכך הלאה…
שיטה זו פותחת עבור התוקף מגוון אדיר של אפשרויות הצפנה עם שימוש בפעולת XOR פשוטה, להתחיל לרוץ מתו מסוים במחרוזת, להשתמש בלולאות מקוננות ממקום מסוים וכ’ו…

Base64 Encryption

כמו הקודמת, בגלל מגוון המימושים השונים שלה, אחת ההצפנות המיושמות בקרב Malware Writers.
שיטה זו בעצם ממירה Data בהצגה בינארית לסט של 64 תווים.
קיימים מספר schemes עבור הסוגים השונים של Base64 Encoding הקיימים Out-There אבל רובן מכילות את התווים: [0-9,a-z,A-Z] ועוד שני תווים, לרוב +,/ או = בו נעשה שימוש עבור Padding של אורך המחרוזת שתתאים לפורמט.
ההמרה מתבצעת כך: כל 3 Byte-ים של Binary Data מומרים ל – 4 Byte-ים של Base64 Data.

רק כדי לסבר את האוזן (או את העין, לא יודע), המחרוזת ATT מיוצגת בעזרת 3 ה – Byte-ים הבאים בהקסהדצימאלי: 0x41 0x54 0x54 (כל תו ב – Ascii הוא 8 ביטים שזה Byte אחד שאלו שני תווים ב – Hex).
אם נמיר אותו לבינארי נקבל את הייצוג המוצג בתצלום למעלה (שורה שניה).
נתייחס לכל 6 ביטים בתור תו בודד ונמיר אותם להצגה דצימלית, כלומר 010000 יומר ל – 16 בדצימאלי, 010101 יומר ל – 21 וכך הלאה…

עכשיו, אם ניגש לטבלת התווים שמייצגת את Base64 בפורמט שלנו:

ונלך ל – Index-ים ה – 16, ה – 21, ה – 17 וה – 20, נקבל את התווים: QVRU בהתאמה 😉.

גם כאן, לרשותו של התוקף מגוון אפשרויות רחב למימוש ה – Base64 Encoding.
הוא יכול למשל לשנות את ה – Indexing Table להיות כזו בעלת בסדר שונה כמו כאן:

מה שישפיע על כל ה – Indexing והאופן בו תתבצע ההמרה.

שימוש ב – Cryptography API Functions

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

  • ביצוע Brute-Forcing במטרה לפענח את המקור, מתוקף מורכבות המפתח וההצפנה פשוט לא אפשרי (יקח עשרות שנים, כן, עשרות…).
  • הדרך איך האלגוריתם עובד גלויה ונגישה לעיון, אך ללא המפתח זה בלתי אפשרי לפענח את המקור.
  • כמו שציינו קודם, שימוש באלגוריתמי הצפנה בקטגוריה זו, לרוב יותר קלים לזיהוי בעת ניתוח ה – Malware, זה מתבטא בשימוש בקבועים אינפורמטיביים (consts), ייבוא ספריות ופונקציות, ונוספים…

נסקור בקצרה את אלגוריתם ההצפנה הסימטרי – AES:
1. זהו אלגוריתם סימטרי משמע אותו המפתח משמש להצפנה ולפיענוח – נזכור זאת בעת ניתוח הפוגען, נשאף למצוא את המפתח לצורך ביצוע ה – Decoding.
2. האלגוריתם בעצם לוקח 128 ביטים של תוכן והופך אותו ל – 128 ביטים של Cipher Text (תוכן מוצפן ולא קריא).
3. המפתח המשמש להצפנה יכול להיות בגדלים של 128 ביטים, 192 ביטים או 256 ביטים.
כאשר כל גודל מציין את מספר ה – Round-ים שיבוצעו במהלך האלגוריתם על התוכן אותו נרצה להצפין (10 Round-ים, 12 Round-ים או 14 Round-ים בהתאמה).
4. האלגוריתם בעצם לוקח 16 Byte-ים (שאלו בעצם 128 ביטים), מסדר אותם ב – Grid של 4×4 ומבצע עליהם פעולות של: XOR -> Byte Substitution -> Shifting Rows -> Mixing Columns -> Adding Round Key, כאשר כל סבב כזה (פרט לשלב ה – XOR שקורה בהתחלה ומסתמך על Key מוסתר) נקרא Round.

5. בגדול נעשה גם שימוש במנגנון הנקרא Key Scheduling על מנת לא להשתמש בכל Round באותו מפתח, אך לא ניכנס לזה כרגע😄.
6. בעיקרון רוב אלגוריתמי ההצפנה המודרניים מתבססים על Substitution Box ו- Permutation Box, כאשר שלב ה – Substitution Bytes שצוין קודם הוא בעצם Substitution Box.
במילה, Substitution Box זו בעצם Lookup Table כאשר כל Byte ממומפה ל – Byte אחר בהתבסס על פונקציה מסוימת.
כהערת שוליים לחובבי המתמטיקה, S-Box זו טרנספורמציה לא לינארית מהצורה:

כלומר, כל פונקציה – fi בשדה, מקבלת n ביטים בתור קלט ומחזירה את fi לכל Xi באופן הבא (כלומר m ביטים).
משמע, פונקציה בשדה תהיה מהצורה:

כאשר לכל f1,f2,…,fm כזו: fi היא בעצם פונקציה בוליאנית מהצורה:

משמע, כל פונקציה בוליאנית fi כזו מחזירה לנו 0 או 1.
ולכן, הפלט הסופי שנקבל יהיה m ביטים, או יותר מדויק m-יה סדורה מהצורה:

כך בעצם מתקבל הפלט של שלב ה – Substitution Box לכל Byte (יחסית בפרטי פרטים).
במקרה שלנו כיוון שדיברנו על AES, ה – n מייצג בעצם 128 ביטים.
כל התהליך ה – mapping הזה שהצגנו, זה בעצם Round אחד (כמו שהוזכר קודם) ובהתאם לאלגוריתם יבוצעו מספר כאלו 🙂
כאן נכנסת החשיבות של ה – Secret Key שהוזכר קודם.
ה – Key מחולק לכמה “Chunk-ים” כאשר על כל Chunk בעצם מבוצע XOR עם הפלט שהתקבל אחרי כל Round שעשינו – אם לא היינו עושים זאת, יכולנו פשוט לפענח את האלגוריתם ע”י ביצוע הפעולות שעשינו בסדר הפוך (בהינתן גישה לסדר הפעולות עצמן וללא צורך במפתח 😉 – שלב זה נקרא Key Scheduling.
*לשלב ה – Permutation Box לא ניכנס בכתבה זו 😊.

השקט שלפני הסערה

טוב, אז אחרי ש”הרבצתי בכם קצת תורה” (וקצת מתמטיקה), נצלול לניתוח חלקי של Malware Sample שמכיל כמה מהעקרונות שהוצגו כאן.
אנחנו נראה שהוא בעצם מתבסס על גישה של Custom Encoding – הוא משתמש ב – XOR Encryption, ב – Base64 וגם ב – AES Encryption בצורה הייחודית שהוא בחר לממש.

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

  1. זיהוי סוג ה – Encoding בו ה – Malware משתמש (אם בכלל), ביניהם: AES, Base64, XOR Encoding ונוספים…
  2. במידה ויש שימוש ב – Encoding, מה הן הפונקציות שמטרתן ביצוע ה – Encoding.
  3. איך נוכל להשתמש בפונקציות האלו, בסוג ה – Encoding ו/או בכתובות בזיכרון בהן ה – Encoding מתרחש, כדי לבצע את שלב ה – Decoding (פיענוח).

אז בואו נתחיל לרברס 🧐

התחלנו עם שלב ה – Basic Static Analysis וראינו קיום של המחרוזות הבאות ב – Data Segment של ה – Malware:

  • CDEFGHIJKLMNOPQRSTUVWXYZABcdefghijklmnopqrstuvwxyzab0123456789+/ -> זהו בעצם ה – Base64 Index Table (שימו לב לשינוי סדר התווים ב – Index Table כמו שצוין קודם) בו נעשה שימוש לצורך Encoding כלשהו אותו ה – Malware יבצע.
  • בנוסף למספר פונקציות מעניינות וביניהן: WriteFile, CreateThread ונוספות…אנחנו יכולים לראות מספר Error Message Strings אינפורמטיביים: Data not multiple of Block Size, Incorrect Key Length ו – Empty Key.
    נוכל לנחש שמדובר בהצפנה כלשהי בה יש חשיבות לגודל ה – Block ו- אורך ה – Key.
  • בנוסף לכך, אנחנו יכולים לראות שלא מעט String-ים מוצגים בתור ג’יבריש מה שאכן מאשש שה – Malware משתמש ב – Encryption כזה או אחר.

בשלב ה – Basic Dynamic Analysis, ראינו כי ה – Malware מנסה לתקשר החוצה ב – Port: 8910 ל – domain שזיהינו בשלב הקודם.
לאחר מכן אנו רואים כי נפתח חלון cmd.exe שפשוט ממתין – נוכל להסיק כי ייתכן וה – Malware מנסה להריץ Reverse Shell כאשר בעצם פקודות ישלחו אל ה – Malware ב – Port שראינו קודם, להרצה מקומית בעמדה.

הערת אגב – נוכל להשתמש ב – Netcat כדי להרים listener ב – Port: 8910 ולהשתמש ב – ApateDNS או INetSim כדי לגרום ל – Malware להפנות את כל הבקשות שהוא שולח ל – Domain החשוד, להגיע ל – 127.0.0.1.
נוכל לראות כי ה – Malware מנסה להריץ פקודות כמו “dir” ואנו יכולים לראות המון Encrypted Data שמתקבל דרך ה – Connection שנוצר.

בשימוש ב – PEiD, ספציפית ב – Plugin של KANAL, נוכל לראות כי יש כתובות בזיכרון בהן זוהו קבועים ורמת אנטרופיה גבוהה ( במילה, אנטרופיה מציינת כתובות בזיכרון בהן ה – Data יותר רנדומאלי מהרגיל, מה שמצביע על Encryption כלשהו שמומש).
בנוסף, גם נוכל לראות כי הסריקה זיהתה את השימוש ב – S-box Structure שמיושם באלגוריתמי Encryption למיניהם (מוצג כ – [S] ו – [S-inv] בתצלום):

RIJNDAEL זה הוא שם נוסף לאלגוריתם ההצפנה – AES – מאמת את ההשערה הראשונית שלנו אודות שימוש באלגוריתם קריפטוגרפי כלשהו.
אם נמשיך בניתוח הכתובות בזיכרון שמאופיינות ברמת אנטרופיה גבוהה, נבחין כי בכתובת 0x004120A4 נמצא ה – Base64 Index Table שראינו קודם.

נוכל להסיק כי ה – Malware משתמש ב – Base64 Encoding עם Custom Index Table ו – AES Encryption.

אם נריץ חיפוש אחר כל פקודות ה – xor שקיימות בקוד ה – Assembly שלנו נראה שיש לא מעט פקודות כאלו:

נתעלם מכל הפקודות בהן הפקודות שייכות לפונקציות ששייכות לספריות Windows API ופקודות בהן מתבצע ניקוי של register, כלומר פקודות מהצורה: xor eax,eax.
נבצע Cross-Reference לכתובות שנשארו לנו לאחר הסינון (לאחר סינון נגיע סה”כ ל – 68 כתובות שעלינו לנתח) עם הכתובות שמצאנו קודם בסריקת האנטרופיה ונגלה כי קיימות 6 פונקציות עיקריות שמכילות את הפקודות האלו.

הנה קוד לדוגמה שלקוח מאחת הפונקציות הללו:


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

בהסתמך על העובדה כי נצפה לראות Reference כלשהו ל – AES Encryption ננסה להבין איך פונקציות ה – xor שמצאנו קשורות לכך.

אחרי קצת Cross-Referencing לפונקציות ה – xor, נגלה כי פונקציית ה – main היא זו שקוראת לפונקציה ששינינו את שמה להיות xor_func1:

כיוון שאנחנו כבר מצפים לראות איזה קשר להצפנת AES, חדי העין יבחינו שה – Offset ל – String שהפונקציה מקבלת כפרמטר, לא רק שהוא נראה כמו Key כלשהו, העובדה שהוא מורכב מ – 16 תווים שהם 16 Byte-ים (כל תו ב – Ascii הוא Byte אחד, סה”כ 128 ביטים) ישירות מדליק נורה שמדובר בהצפנת AES מהסוג: AES-128 שמתבססת על Key באורך 16 Byte-ים.

נחזור ל – Bigger Picture לרגע

ראינו קודם, כי מתבצעת קריאה לפונקציה xor_func1 שמקבלת מספר פרמטרים וביניהם מחרוזת הנראת כמו AES Key.
אחרי זה ראינו קריאות לפונקציות WSAStartup ו – WSASocketA שמטרתן לבצע Initialization לחיבור לרשת.
לאחר מכן, מגיע קטע הקוד הבא:

מתבצעות מספר קריאת נוספות שמטרתן: ביצוע DNS Resolving ל – domain שמועבר כפרמטר, המרה של ה – Port (מוצג כ: 8910) מ – Little Endian ל – Big Endian לצורך ה – Network Convention בעת התקשורת, ביצוע connect להתחלת התקשורת וקריאה לפונקציה נוספת sub_4015B7.

אם נתחיל לנתח את הפונקציה sub_4015B7 נגלה מספר דברים מעניינים:
1. ה – Malware יוצר Process של cmd.exe:

2.מתבצעת יצירה של שני Thread-ים (בהמשך נגלה כי כל אחד מיועד למטרה ספציפית):

3. בנוסף נראה יצירה וקשירה של Pipe-ים ל – Standard Out, Input, Error כמו שנעשה לרוב כשיוצרים תקשורת בין Standard Input ל – Output:

עקב העובדה שמצאנו Base64 Index Table, נמצא את המיקום בזיכרון בו הוא נשמר ( ב – Data Segment כמובן) ונבצע Cross-Reference כדי לראות איפה נעשה בו שימוש.
נגלה כי נעשה reference אליו בפונקציה sub_40103F שאליה מבוצעת קריאה מ – sub_401082 וזו מכילה לולאה מקוננת.
לבסוף, נגלה כי הפונקציה sub_401082 מועברת כפרמטר לאחד ה – Thread-ים שראינו קודם (בכתובת ה – StartAddress שאותה ה – Thread יריץ).

חשיבות הבנת הסדר הכרונולוגי

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

  1. קיימת פונקציה שמבצעת Decode בהסתמך על ה – Base64 Index Table שנקראת ישירות אחרי הקריאה ל – CreateProcess כדי ליצור את cmd.exe.
    לאחר מכן נוצר socket connection עם הקריאות (WSAStartup, WSASocketA ופונקציות נוספות מהספרייה Ws2_32.dll) ליצירת תקשורת ל – Domain הזדוני.
  2. נוצר Pipe שיעביר את ה – Input/Output מה – Socket שנוצר, ל – cmd.exe.
  3. ניתוח הקריאות השייכות ל – AES Encryption (ניתוח קצת מורכב, ולכן לא מוצג כאן) מגלה כי הפונקציה שאחראית על ביצוע ה – AES Encryption מועברת כפרמטר ל – Thread השני שראינו,וזה קורה לאחר שה – Process המדובר – cmd.exe כבר התחיל לרוץ.
    לבסוף, אנו עדים לקריאה לפונקציה WriteFile על מנת לכתוב ל – Cmd Console (והוא בתורו יבצע Redirect של ה – Output חזרה דרך ה – Socket שנוצר, החוצה לרשת).

תובנות ו – For God’s Sake, קצת סדר

ה – Malware שראינו בעצם יוצר Reverse Shell כאשר ה – Data שהוא שולח חזרה ל – C2
Server עובר Encryption.

מבחינת הסדר הכרונולוגי והתמונה הגדולה, הבנו כי כל הפקודות שנשלחו מה – C2 ל – Reverse Shell נשלחות Encoded בעזרת Base64 ועוברות Decoding בעזרת ה – Custom Index Table שמצאנו.

מניתוח קצת יותר עמוק, והרבה Cross-Reference-ים להבנת הרצף הכרונולוגי ו”-הגורמים הפועלים” הבנו כי כל התגובות שנשלחות חזרה ל – C2 Server עוברות AES Encryption בעזרת המפתח: ijklmnopqrstuvwx, רגע לפני שליחתן חזרה.

יצא לנו לראות קצת הסבר על AES Encryption: קצת על איך הוא עובד, על מה הוא מתבסס, קצת הסבר מתמטי (כפרע על אלגברה לינארית😍) ואיך זה מתבטא בקוד ה – Assembly של Malware Sample.

לדעתי הנקודה הכי חשובה, ראינו מה ה – Workflow שנרצה ליישם כשנחקור Malware-ים שמשתמשים ב – Encryption-ים למיניהם:
1. נרצה לזהות באיזה סוג של Encryption/Encoding ה – Malware עושה שימוש.
2. לגלות איזה פונקציות מבצעות את ה – Encryption/Encoding ואילו כתובות נוספות בזיכרון
קשורות לתהליך (למשל String-ים ב – Data Segment).
3. איך לבצע Pivoting סביב כתובת/פונקציה או כל פריט מידע אחר שמצאנו (כמובן בעזרת
שלל הכלים שעומדים לרשותנו) כדי להתקדם להבנת הפונקציונאליות המלאה של ה – Malware.

נקודה נוספת ואחרונה שלצערי לא הספקתי להציג כאן (ולדעתי החלק היותר מגניב בתהליך) – שלב ה – Decoding.
את ה – Decoding ניתן לבצע במספר דרכים, המרכזיות שבהן:
1. Manual Decoding -בגישה הזו, נזהה את הפונקציות שמבצעות את ה – Decoding, נריץ את ה – Malware ב – Debugger ונראה את ה – String-ים מפוענחים בזמן ריצה.
אם לא נצליח לזהות את הפונקציות האלו, או שמלכתחילה ה – Malware לא מבצע Decoding באף שלב במהלך הריצה – אכלנו אותה…
2. Decoding Using Coding – נוכל לכתוב script שיבצע את ה – Decoding עבורנו.
With that being said, נצטרך לתת לו את כל המידע הדרוש לו לביצוע ה – Decoding.
קרי, אם מדובר ב – Base64 נצטרך לתת לו את ה – Index Table, אם מדובר ב – AES או DES, נצטרך לתת לו את הסיסמא (שנהיה חייבים לחלץ מהקוד) ואת ה – mode של ההצפנה (למשל: AES מכיל מספר mode-ים: CBC או EBC וכ’ו…) – במילים אחרות, יש לא מעט Data שעלינו קודם לחלץ מהקוד, כדי שהקוד שלנו יוכל לרוץ ולבצע את מה שנפקוד עליו.
3. הדובדבן שבקצפת – Instrumentation🤩.
ברגע שנזהה את הפונקציות שאחראיות לביצוע ה – Decoding וה – Encoding ואת הפרמטרים שהן מקבלות, נוכל לשנות את ה – Workflow של ריצת התוכנית כך שתבצע את ה – Decoding עבורנו – עם ה – Data שנספק לה.
נוכל לעשות זאת בעזרת Plugin-ים שנכתוב ל – OllyDbg (או יותר נכון לגרסה היותר מתקדמת שלה – ImmDbg) שיבצעו מודיפיקציה לערכים של ה – Register-ים ולכתובות בזיכרון על מנת לבצע את ה – Decoding עבורנו.
זה מצריך הבנה דיי עמוקה של מה קורה מאחורי קלעים, להקצות Buffer-ים מהם נכתוב ונקרא, שינוי אוגר ה – EIP (ואוגרים נוספים בהתאם),ועוד…
זה אמנם לא הכי קל ליישום אך ברגע שמבינים את זה לעומק, זה ניתן ליישום בחלק נכבד מה – Malware-ים איי שם בחוץ – > לגמרי Skill שכדאי לרכוש ל – Long Run.

**נוכל גם ליישם את זה בצורה חלקית ב – Debugger עצמו, במידה וההצפנה שבה ה – Malware משתמש היא Reversible (אותו אלגוריתם להצפנה ולפיענוח) נוכל פשוט לתת לפונקציה שמבצעת את ה – Encoding, את ה – Encoded Data ולקבל אותו מפוענח בסוף הריצה שלה…
לא הייתי בונה רק על זה , כי זה לא תמיד ניתן ליישום.

עוד כתבה הגיעה לסיומה, מקווה שאהבתם חבר’ה, גם אם היו חלקים שלא הבנתם אבל סקרנתי אתכם לקרוא ולהעמיק בנושא מסוים – אני את שלי עשיתי 😇
מוזמנים לתת פידבק, הערות והארות לכתבות הבאות 😉

דן דורפמן
WRITEN BY

דן דורפמן

סטודנט למדעי המחשב באוניברסיטה הפתוחה.
SecOps Engineer & Automation Developer.
בעל תשוקה לתחום ה- Malware Analysis & Reverse Engineering.
בזמני הפנוי אני: לומד דברים חדשים, משחק כדורסל, שומע מוזיקה, ומדבר על החיים עם החבר'ה ;)

כתיבת תגובה

האימייל לא יוצג באתר. שדות החובה מסומנים *