google CTF js 1.0


הכנסת קוד שגוי תציג לנו Access Denied
במדריך זה אציג לכם את הפתרון שלי לאתגר ה-JS SAFE 1.0 מ-GoogleCTF2018 Beginners, חברת Google מדי שנה נוהגת להוציא סדרה של אתגרים מרתקים בשלל נושאים כגון:

  • CRYPTO
  • MISC
  • PWN
  • RE
  • WEB

אני משתדל מדי שנה לפתור לפחות אתגר web אחד – שניים, מדובר באתגרים יחסית קשים ביחס לתחרויות CTF אחרות שאני משתתף בהם. בנוסף לתחרות, הפעם גוגל הוציאה גם סדרה של אתגרים למתחילים.

האתגרים מאוד קלים ומי שחדש בתחום אני מאוד ממליץ לו לנסות לפתור אותם. בכיתות שאני מלמד אני אף נותן את התרגילים כשיעורי בית ומציג את הפתרונות בכיתה.

המטרה באתגר js_safe_1 היא להבין כיצד עובד הקוד ולמצוא את הסיסמא לכספת.

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

אתגר זה מתחיל כאשר אתם מקבלים את הקובץ js_safe_1 להורד הקובץ
אתגר גוגל המבקש סיסמא לכספת
על מנת שתוכלו למצוא את הפונקציה שמטפלת בהזנת הנתונים לשדה הinput לחצו על inspect ועברו ללשונית Event Listeners:
הפונקציה open_safe
כעת לחצו על הפונקציה וזה יעביר אתכם ישירות ללשונית source:
עצירת הקוד בזמן ריצה בפונקציה open_safe
חפשו את הפונקציה open_safe בקובץ מה שנראה כך:
קוד המקור של הפונקציה open_safe
בקוד הזה יש לנו ביטוי רגולרי שאם הוא לא מתקיים או שאם הפונקציה x מחזירה False הכספת מדפיסה לנו Access denied אחרת נקבל Access granted.

פונקציה x מוגדרת למעלה ומכילה את הקוד הבא, נחלק את הקוד למספר חלקים בצורה הבאה:
חלוקת הקוד ל-3 חלקים שנפרט בהמשך
פירוט לחלוקה שבצעתי:

1. בחלק זה אנו רואים מחרוזת מסוג Unicode שמכילה המון תווים, אם תתמקדו בתווים תשימו לב שחלקם מכילים את שמות הפונקציות מתוך env.

2. חלק זה מכיל את המשתנה env אשר מכיל מספר פעולות ברירת מחדל שאיתן אנו יכולים לייצר כל פונקציה שנרצה בזמן ריצה, הפעולות הן:
  • (x,y) => x[y]

  • פעולה זו מחזירה לנו את הערך במיקום הy בתוך x.
  • Function.constructor.apply.apply(x,y)

  • פעולה זו מאפשרת לנו ליצור פונקציות בצורה דינמית ולהריץ אותן, כתיב כזה ניתן לראות בדרך כלל באתגרים. לי פחות יצא לכתוב משהו כזה מרצון חופשי.

    להלן דוגמה לשימוש בפונקציה עם apply ועם apply.apply:
    a = {
        test1: (x,y) => Function.constructor.apply(x, y),
    	test2: (x,y) => Function.constructor.apply.apply(x, y)
    };
    
    roman_apply = a["test1"](this,[["x","y"],"return x+y"]);
    console.log(roman_apply(2,3));
    
    roman_apply_apply = a["test2"](roman_apply,[,[2,3]]);
    console.log(roman_apply_apply);
    










  • (x,y) => x+y


  • הקוד מאפשר הן לשרשר טקסט כדי ליצור מילים שיהפכו לפקודות והן לחבר מספרים כדי להגיע לערכים דצימליים של תווים ב-ASCII.
  • (x) => String.fromCharCode(x)


  • קוד זה ממיר תו דצימלי לתו ASCII כדי ליצור אותיות שמרכיבות מילים שהן בעצם יהפכו לפקודות באמצעות apply.apply.
  • • הפקודות הבאות מכילות את הספרות 0 ו-1, ספרות אלו משמשות את הפונקציות הדינמיות לצורך יצירת מספרים שונים באמצעות פעולות מתמטיות.
  • new TextEncoder().encode(password)

    הפעולה הזאת ממירה טקסט ל-byte array שאנו צריכים עבור פעולות מסוימות, בדרך כלל פעולות קריפטוגרפיה.
  • הפעולה האחרונה h היא ערך החזרה שיחזור בתוכנה שלנו ועליו להיות 0 אחרת נקבל את התשובה false.
3. בחלק הזה אנו רואים את הלולאה הבאה:

לולאה המכילה לוגיקה שתוצג בהמשך
ניתן לראות שיש ריצה על ה-code בקפיצות של 4 שמתחלקות ל:
  • lhs – ערך התוצאה של הפעולה
  • fn – פונקציה כלשהי שיש לבצע
  • arg1 – משתנה ראשון
  • arg2 – משתנה שני
בנוסף יש לנו יצירת פונקציה, הרצה ושמירת ערך התוצאה, כך בעצם קוד זה יוצר לנו את כל שאר הלוגיקה בצורה דינמית. ממליץ לכם לבחון את הקוד באמצעות debugger ולראות בעצמכם כיצד הפונקציות נוצרות בזמן ריצה.

כמובן שייקח הרבה זמן כדי לבחון תרגיל כזה ב-debugger, לכן חשבתי על "קיצור הדרך" הבא - נשתמש ב-console על מנת להדפיס את כל הפעולות ולבחון את התוצאה.

כך הגעתי לפקודות הבאות, הוסיפו אותן לקוד שלכם והריצו:

console.log(i, env[lhs], env[fn], arg1 + "=" + env[arg1], arg2 + "=" + env[arg2]);
console.log("-".repeat(50));




הסתכלו ב-console ותראו את הפעולות מתבצעות:
הדפסת תוצאות הריצה של הלולאה
תחילה ניתן לראות שיש יצירה של אותיות ומספרים עבור יצירת מילים כגון המילה return שתשמש אותנו בפונקציות בהמשך.
איתור פונקציית sha256
לאחר מכן ניתן לראות את הפונקציה sha-256 שמריצים על הסיסמא שלנו בפורמט byte array והתוצאה היא:
רשימת תווים הנכנסים ל-sha256
כך שאם נבדוק זאת בעצמנו באמצעות python:
שימוש בpython לצורך ביצוע פעולה זהה
נקב 59 בבסיס הקסדצימלי שווה ערך ל-89 בבסיס דצימאלי (התו הראשון בconsole) כך שקיבלנו את אותו הדבר.

לאחר מכן ניתן לראות ביצוע פעולת xor בין ה-sha256 של הסיסמא שלנו מול sha256 שחושב על ידי האתגר ואז ביצוע or עם המשתנה h שזה ערך החזרה שלנו
הצגת הפונקציה שמבצעת פעולת xor
עלינו להמשיך בשיטה זו עד שנעבור על כל ה-sha256 ונגיע לתוצאה האחרונה שנשארה ב-h, נוסף על העובדה שעלינו להחזיר את h עם הערך 0, אנו יכולים להסיק שהדרך היחידה לקבל זאת היא אם נבצע את הפעולות המתמטיות על אותו ה-hash.

כדי להגיע ל-hash אנו ניקח את כל התווים שאנו משווים מולם באמצעות הקוד הבא:
let output = []
let cursor = 964;

if ( i == cursor )
{
	cursor += 20
	output.push(env[arg2]);
}









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

output = [ "%.2x" % i for i in [230, 104, 96, 84, 111, 24,205, 187, 205, 134, 179, 94, 24, 181, 37, 191, 252, 103, 247, 114, 198, 80, 206, 223, 227, 255, 122, 0, 38, 250, 29, 238]]
"".join(output)





נכניס לקוד python שלנו ונקבל: e66860546f18cdbbcd86b35e18b525bffc67f772c650cedfe3ff7a0026fa1dee

נשתמש ברמז שהופיע בתחילת האתגר, ונחפש בגוגל את ה-sha256 שיצא לנו: TODO: check if they can just use Google to get the password once they understand how this works.

ונקבל:
חיפוש ה-hash בגוגל נותן לנו Passw0rd!
נכניס את הסיסמא, ובכך נפתור את האתגר:
Access Granted

Share this post