google CTF js 2.0


אתגר גוגל שני בנושא javascript
במדריך זה אציג לכם את הפתרון שלי לאתגר ה-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:
פונקציה open_safe
נלחץ על הפונקציה וזה יעביר אותנו ל-source:
כפתור המוביל לפונקציה open_safe
נחפש את open_safe בקובץ ונראה את הקוד הבא:
קוד המקור של הפונקציה open_safe שמבצעת את הפונקציה x לאחר קבלת סיסמא
בקוד הזה יש לנו ביטוי רגולרי שאם הוא לא מתקיים או שאם פונקציה x מחזירה False אז הכספת מדפיסה לנו Access denied אחרת נקבל Access granted.

פונקציה x מוגדרת למעלה וניתן לראות שהיא Minified לכן נעשה לה prettify באמצעות הדפדפן:
חלוקת הפונקציה לשישה חלקים
חילקתי את הקוד 7 לחלקים עבורכם, כדי שיהיה קל יותר להבין מה הקוד הזה עושה:

1. בתחילת הקוד מתבצעת הגדרה של 3 פונקציות עזר לתוכנית, שינו את הפונקציות למבנה שלהם ב-python כך שיהיה קל יותר להשתמש בהם.
  • הפונקציה ord ממירה תו לערך הדצימלי שלו.
  • הפונקציה chr ממירה ערך דצימלי לתו, בעצם ההופכי של ord.
  • הפונקציה str ממירה משתנה ל-String.
2. בחלק השני אנו רואים פונקציה מעניינת שמקבלת טקסט כלשהו ומבצעת עליו 2 פעולות מתמטיות שנראות מוזר יחסית.

בדרך כלל כאשר אתם רואים קטע קוד שכזה, מדובר באלגוריתם קיים כלשהו שמבצע משהו מוכר, לכן חיפשתי בגוגל את המילה algorithm % 65521 וקיבלתי את האלגוריתם adler-32:
חיפוש בגוגל מראה שמדובר ב-adler-32
מכאן ניתן להסיק שהפונקציה מבצעת חישוב חתימה לטקסט כלשהו, כך שאם משהו ישלח ל-h סימן שהקוד מנסה לחתום אותו, תוצאת החתימה היא 2 בתים של b ועוד 2 בתים של a.

3. הפונקציה הזאת מבצעת xor בין המידע שהיא מקבלת וארבעת התווים בחתימה, משרשרת אותם ומחזירה. קוד פשוט יחסית, אין מה להוסיף כאן.

4. לולאת Anti-Debugging אשר התפקיד שלה לקדם את a ל-1000 ועל הדרך אם ה-developer tools פתוח היא חוסמת אותנו מ-debugging באמצעות הקריאה לפונקציה debugger.

על מנת לעקוף את קטע הקוד כל מה שעלינו לעשות הוא לשנות בזמן הרצה את a ל-999 ובכך הפונקציה לא תפריע לנו:
פקודת debugger
5. הפקודה הבאה משנה את x לתוצאת החתימה של:
str(x)
שזה קוד המקור של הפונקציה עצמה:
קוד המקור של הפונקציה
על זה מבצעים חתימה של alder ומקבלים:
130,30,10,154
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(х))//᧢
בכך קיבלנו את הקוד:
х==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
כדי לפתור את התרגיל הרבה יותר מהר.

הקוד של האתגר:

Share this post