google CTF js 1.0
במדריך זה אציג לכם את הפתרון שלי לאתגר ה-JS SAFE 1.0 מ-GoogleCTF2018 Beginners, חברת Google מדי שנה נוהגת להוציא סדרה של אתגרים מרתקים בשלל נושאים כגון:
- CRYPTO
- MISC
- PWN
- RE
- WEB
אני משתדל מדי שנה לפתור לפחות אתגר web אחד – שניים, מדובר באתגרים יחסית קשים ביחס לתחרויות CTF אחרות שאני משתתף בהם. בנוסף לתחרות, הפעם גוגל הוציאה גם סדרה של אתגרים למתחילים.
האתגרים מאוד קלים ומי שחדש בתחום אני מאוד ממליץ לו לנסות לפתור אותם. בכיתות שאני מלמד אני אף נותן את התרגילים כשיעורי בית ומציג את הפתרונות בכיתה.
המטרה באתגר js_safe_1 היא להבין כיצד עובד הקוד ולמצוא את הסיסמא לכספת.
על מנת ללמד אתכם את שיטת החשיבה וכיצד פותרים אתגרים כאלה, הקלטתי עבורכם את הסרטון הבא:
אתגר זה מתחיל כאשר אתם מקבלים את הקובץ js_safe_1 להורד הקובץ

על מנת שתוכלו למצוא את הפונקציה שמטפלת בהזנת הנתונים לשדה הinput לחצו על inspect ועברו ללשונית Event Listeners:

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

חפשו את הפונקציה open_safe בקובץ מה שנראה כך:

בקוד הזה יש לנו ביטוי רגולרי שאם הוא לא מתקיים או שאם הפונקציה x מחזירה False הכספת מדפיסה לנו Access denied אחרת נקבל Access granted.
פונקציה x מוגדרת למעלה ומכילה את הקוד הבא, נחלק את הקוד למספר חלקים בצורה הבאה:

פירוט לחלוקה שבצעתי:
1. בחלק זה אנו רואים מחרוזת מסוג Unicode שמכילה המון תווים, אם תתמקדו בתווים תשימו לב שחלקם מכילים את שמות הפונקציות מתוך env.
2. חלק זה מכיל את המשתנה env אשר מכיל מספר פעולות ברירת מחדל שאיתן אנו יכולים לייצר כל פונקציה שנרצה בזמן ריצה, הפעולות הן:
-
(x,y) => x[y]
-
Function.constructor.apply.apply(x,y)
-
(x,y) => x+y
-
(x) => String.fromCharCode(x)
- • הפקודות הבאות מכילות את הספרות 0 ו-1, ספרות אלו משמשות את הפונקציות הדינמיות לצורך יצירת מספרים שונים באמצעות פעולות מתמטיות.
-
new TextEncoder().encode(password)
הפעולה הזאת ממירה טקסט ל-byte array שאנו צריכים עבור פעולות מסוימות, בדרך כלל פעולות קריפטוגרפיה. - הפעולה האחרונה h היא ערך החזרה שיחזור בתוכנה שלנו ועליו להיות 0 אחרת נקבל את התשובה false.
פעולה זו מחזירה לנו את הערך במיקום הy בתוך x.
פעולה זו מאפשרת לנו ליצור פונקציות בצורה דינמית ולהריץ אותן, כתיב כזה ניתן לראות בדרך כלל באתגרים. לי פחות יצא לכתוב משהו כזה מרצון חופשי.
להלן דוגמה לשימוש בפונקציה עם 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);
הקוד מאפשר הן לשרשר טקסט כדי ליצור מילים שיהפכו לפקודות והן לחבר מספרים כדי להגיע לערכים דצימליים של תווים ב-ASCII.
קוד זה ממיר תו דצימלי לתו ASCII כדי ליצור אותיות שמרכיבות מילים שהן בעצם יהפכו לפקודות באמצעות apply.apply.

ניתן לראות שיש ריצה על ה-code בקפיצות של 4 שמתחלקות ל:
- lhs – ערך התוצאה של הפעולה
- fn – פונקציה כלשהי שיש לבצע
- arg1 – משתנה ראשון
- arg2 – משתנה שני
כמובן שייקח הרבה זמן כדי לבחון תרגיל כזה ב-debugger, לכן חשבתי על "קיצור הדרך" הבא - נשתמש ב-console על מנת להדפיס את כל הפעולות ולבחון את התוצאה.
כך הגעתי לפקודות הבאות, הוסיפו אותן לקוד שלכם והריצו:
console.log(i, env[lhs], env[fn], arg1 + "=" + env[arg1], arg2 + "=" + env[arg2]);
console.log("-".repeat(50));
הסתכלו ב-console ותראו את הפעולות מתבצעות:

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

לאחר מכן ניתן לראות את הפונקציה sha-256 שמריצים על הסיסמא שלנו בפורמט byte array והתוצאה היא:

כך שאם נבדוק זאת בעצמנו באמצעות python:

נקב 59 בבסיס הקסדצימלי שווה ערך ל-89 בבסיס דצימאלי (התו הראשון בconsole) כך שקיבלנו את אותו הדבר.
לאחר מכן ניתן לראות ביצוע פעולת xor בין ה-sha256 של הסיסמא שלנו מול sha256 שחושב על ידי האתגר ואז ביצוע or עם המשתנה h שזה ערך החזרה שלנו

עלינו להמשיך בשיטה זו עד שנעבור על כל ה-sha256 ונגיע לתוצאה האחרונה שנשארה ב-h, נוסף על העובדה שעלינו להחזיר את h עם הערך 0, אנו יכולים להסיק שהדרך היחידה לקבל זאת היא אם נבצע את הפעולות המתמטיות על אותו ה-hash.
כדי להגיע ל-hash אנו ניקח את כל התווים שאנו משווים מולם באמצעות הקוד הבא:
let output = []
let cursor = 964;
if ( i == cursor )
{
cursor += 20
output.push(env[arg2]);
}
ונקבל:

אם ניקח את 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.
ונקבל:

נכניס את הסיסמא, ובכך נפתור את האתגר:
