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

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

נלחץ על הפונקציה וזה יעביר אותנו ל-source:

נחפש את open_safe בקובץ ונראה את הקוד הבא:

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

חילקתי את הקוד 7 לחלקים עבורכם, כדי שיהיה קל יותר להבין מה הקוד הזה עושה:
1. בתחילת הקוד מתבצעת הגדרה של 3 פונקציות עזר לתוכנית, שינו את הפונקציות למבנה שלהם ב-python כך שיהיה קל יותר להשתמש בהם.
- הפונקציה ord ממירה תו לערך הדצימלי שלו.
- הפונקציה chr ממירה ערך דצימלי לתו, בעצם ההופכי של ord.
- הפונקציה str ממירה משתנה ל-String.
בדרך כלל כאשר אתם רואים קטע קוד שכזה, מדובר באלגוריתם קיים כלשהו שמבצע משהו מוכר, לכן חיפשתי בגוגל את המילה algorithm % 65521 וקיבלתי את האלגוריתם adler-32:

מכאן ניתן להסיק שהפונקציה מבצעת חישוב חתימה לטקסט כלשהו, כך שאם משהו ישלח ל-h סימן שהקוד מנסה לחתום אותו, תוצאת החתימה היא 2 בתים של b ועוד 2 בתים של a.
3. הפונקציה הזאת מבצעת xor בין המידע שהיא מקבלת וארבעת התווים בחתימה, משרשרת אותם ומחזירה. קוד פשוט יחסית, אין מה להוסיף כאן.
4. לולאת Anti-Debugging אשר התפקיד שלה לקדם את a ל-1000 ועל הדרך אם ה-developer tools פתוח היא חוסמת אותנו מ-debugging באמצעות הקריאה לפונקציה debugger.
על מנת לעקוף את קטע הקוד כל מה שעלינו לעשות הוא לשנות בזמן הרצה את a ל-999 ובכך הפונקציה לא תפריע לנו:

5. הפקודה הבאה משנה את x לתוצאת החתימה של:
`
str(x)
שזה קוד המקור של הפונקציה עצמה:

על זה מבצעים חתימה של alder ומקבלים:

6. מימוש טכניקת Anti-Debugging נוספת אשר ממירה את הביטוי הרגולרי לטקסט ובעת הדפסה מעבירה אותו לפונקציה c אשר תוקעת את הדפדפן.
7. שימוש ב-with והרצת הקוד הבא:
`
with (source)
return eval('eval(c(source,x))')
הפונקציה with מכינה את source ל-scope כך שלא נצטרך לבצע קריאה ל-source.source למידע נוסף ראה את הסרטון לפתרון האתגר בתחילת המדריך.
כמו כן הפונקציה מבצעת:
`
eval(c(source,x))
מה שבעצם מריץ את התוכן של:
`
c(source,x)
את הערכים אנו יודעים מכיוון שמדובר בתוצאות החתימה של הפונקציה והביטוי הרגולרי שהוגדר, אם נכניס אותם לקוד הבא:

בכך קיבלנו את הקוד:
`
х==c('¢×&Ê´cʯ¬$¶³´}ÍÈ´T©Ð8ͳÍ|Ô÷aÈÐÝ&¨þJ',h(х))//᧢
קוד זה שוב מבצע את הפעולה h על х רק שהפעם מדובר ב-x של הקלט שלנו ועליו מבצעים את פעולת ה-xor את קטע הקוד הזה נמיר גם כן לקוד python עם הלוגיקה הבאה:
תחילה ניצור תור משימות עם 8 פועלים:
`
from queue import Queue
from threading import Thread
worker_thread = 8
my_queue = Queue()
לאחר מכן נגדיר את המשימה שלנו- לחפש את התווים a ו-b המתאימים שאחרי שיכנסו לקוד adler-32 יתנו לנו חתימה הגיונית שמכילה את ה-FLAG, התווך ל-a ו-b הוא בין 0 ל-65521 מכיוון שזה התווך של adler-32:
`
for a in range(5600, 65521):
for b in range(0, 65521):
data = [chr(b >> 8) , chr(b & 0xFF) ,chr(a >> 8) , chr(a & 0xFF)]
my_queue.put(data)
לאחר שהגדרנו את המשימה נכתוב את הקוד של הפועלים, הפועלים יבצעו את חישוב ה-xor על החתימה שקיבלנו ויבדקו האם היא מתאימה לביטוי הרגולרי של התוכנית שלנו שראינו בהתחלה, אם אכן יש התאמה הקוד ידפיס את התוצאה.
`
def worker_func(q):
while True:
data = q.get()
print_at_exit = True
looking_for = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_@!?-"
a = '¢×&Ê´cʯ¬$¶³´}ÍÈ´T©Ð8ͳÍ|Ô÷aÈÐÝ&¨þJ'
b = data
output = []
for i in range(len(a)):
xor = chr(ord(str(a[i])) ^ ord(str(b[i % 4])))
if not xor in looking_for:
print_at_exit = False
break
output.append(xor)
if print_at_exit:
print ("".join(output))
q.task_done()
לקוד לוקח כשעה למצוא את ה-FLAG, כמובן שניתן לעדכן את הקוד ולהשתמש ב-
`
multiprocessing
על מנת שנוכל להריץ את הקוד על מספר תהליכים ובכך לקבל את התוצאה בזמן קצר יותר, אני ממליץ להשתמש ב:
`
multiprocessing.Pool
וב:
`
pool.apply_async
כדי לפתור את התרגיל הרבה יותר מהר.
הקוד של האתגר: