BinaryVision

Reversing Python Bytecode

מאת בתאריך 06/01/11, תחת כללי

מבוא

מתחיל להיות נפוץ נושא השימוש בשפות סקריפט לכתיבת קבצי EXE.
במיוחד בחברות Outsourcing שכל המטרה שלהן לכתוב מהר וקל.
אחרי שהחברה מסיימת לכתוב, מביאה מוצר, צריך להבין מה לעזאזל הם עשו שם.
Reversing קלאסי לקובץ EXE שהוא בעצם Py2EXE, לא כלכך עובד.
אז מה עושים?

מודול Dis

מחיפושים באינטרנט דבר ראשון שנתקלתי בו,
זה שיש מודול מובנה בPython להצגת הBytecode בצורה יותר ברורה, ושמו Dis.
http://docs.python.org/library/dis.html
הוא יודע לקבל code object, ולפתוח אותו למשהו קצת יותר קריא.
זה לא נותן את המקור, אבל זה מפתיע כמה זה קרוב.
בואו ננסה:

def test():
 a = 5
 b = 6
 if a == 5:
  b = 7
 return b

עכשיו נפרק את זה בעזרת Dis

>>> import dis
>>> dis.dis(test.func_code)
 2           0 LOAD_CONST               1 (5)
           3 STORE_FAST               0 (a)

 3           6 LOAD_CONST               2 (6)
           9 STORE_FAST               1 (b)

 4          12 LOAD_FAST                0 (a)
          15 LOAD_CONST               1 (5)
          18 COMPARE_OP               2 (==)
          21 JUMP_IF_FALSE           10 (to 34)
          24 POP_TOP

 5          25 LOAD_CONST               3 (7)
          28 STORE_FAST               1 (b)
          31 JUMP_FORWARD             1 (to 35)
      >>   34 POP_TOP

 6     >>   35 LOAD_FAST                1 (b)
          38 RETURN_VALUE

זה מדהים עד כמה זה מדוייק, כולל שמות משתנים והכל.
המספר הראשון זה מספר השורה בקוד מקור…
המספר שמופיע לפני כל מילה זה מספר הByte
המילה, זה הפעולה (לדוגמה LOAD_CONST)
המספר אחריה זה פרמטר, ובסוגריים זה הפיכת הפרמטר למשהו משמעותי אם אפשר.

נעבור לפי הסדר, ונראה כמה קל זה לבנות חזרה את הקוד:
שורה 2, LOAD_CONST טוען קבוע למחסנית, הקבוע הוא באינדקס 1, אבל הערך שלו הוא 5.
ואז STORE_FAST שומר את הערך מהמחסנית לתוך המשתנה באינדקס 0, ששמו a.
מה כל פעולה עושה מתועד היטב בקישור למודול DIS, ומה שנשאר זה להבין שהפקודה היא:

a = 5

שורה 3, אותו דבר, נעבור לשורה 4.
טוענים את a למחסנית, טוענים 5, ועושים COMPARE_OP עם ==.
הפעולה משווה בין השניים האחרונים במחסנית במחזירה את התשובה למחסנית.
עד כה יש לנו a == 5.
JUMP_IF_FALSE בודק את הערך האחרון במחסנית, ואם הוא FALSE קובץ לByte 34.
POP_TOP שולף את הערך האחרון במחסנית.
אז אם נרצה לשחזר את זה, צריך להבין שבמקרה שהערך הוא שקר, הוא מדלג _מעל_ קטע הקוד הבא, ז"א אם נרצה לרשום את זה רצוף, אז במקרה של אמת, הוא נכנס לקטע קוד, אז התנאי נראה כך:

 if a == 5:
     #... continue here ...
 #... if false jump to here ...

ואפשר להמשיך כך הלאה עד שבונים מחדש את הפונקצייה…

py2exe

הייצור שקוראים לו py2exe אורז את סקריפט הPython, כל ספריות, וכל דבר שצריך כדי להריץ אותן, לתוך EXE אחד.
והוא עוזה את זה בצורה הבאה:
כל הDLLים, וכל הספריות שעושים להן Import, הוא מקמפל לBytecode, ושם בZIP, בתוך הEXE.
ניתן לחלץ אותו ממש בקלות ע"י 7Zip שמאתר אותו, ופשוט נותן להוציא מתוכו קבצים.
עכשיו צריך, רק למצוא את הסקריפט הראשי.
הוא נשמר כResource בEXE, אפשר בקלות לשלוף אותו עם Visual Studio.
פותחים EXE עם התוכנה, והוא מציג את כל הResources, אחד מהם זה הקוד המקומפל.

רק משהו קטן

קובץ .pyc משוייך לPython שאיתו בנו אותו, וזה לא סתם, כי הBytecode משתנה.
כל הקבצים שנמצאים בZIP ניתן לעשות להן import כל עוד הגרסה של הפייטון תואמת,
ואחרי זה אפשר להריץ dis.dis על המודול כולו. (אפשר גם על פונקציות בנפרד).
עם הResource העניין קצת יותר בעייתי, כי הוא לא בדיוק Library, אלא Marshalled, בעזרת מודול marshal.
בשימוש במודול אפשר לשלוף את הCode Object הראשי, וממנו למצוא כל Code Object אחר, ולהעביר אותו לDIS גם כן.

פרויקט

חשבתי לעשות Python Decompiler,
זה מתחיל להיות קשה כשמגיעים לתנאים מסובכים, ולולאות עם תנאים מסובכים בתוכן.
אני חושב שזה אפשרי לחזור לקוד מקור בצורה אוטומטית, מה דעתכם?
(יש כמה נסיונות לDecompiler באינארנט, לא ראיתי אחד נורמלי…)

:, , ,
6 תגובות:
  1. Uriel Katz

    שחזרתי קובץ urls.py (קובץ routing של django) עם אתר שנקרא:
    http://www.depython.net/
    כתבתי על זה פה:
    http://www.urielkatz.com/archive/detail/getting-your-dear-py-files-pyc-files/

  2. TAsn

    Fate: קול מאוד, ומאכזב שאין דבר כזה, כי נראה שפייתון כבר עושה לך את רוב העבודה…

    אוריאל: מגניב 🙂 אבל חבל שזה באתר ושזה כ"כ מוגבל…

  3. Ratinho

    פוסט מגניב, נחמד לשמוע שאתה עוד חי Fate…

  4. spdr

    אחלה פוסט פייט,
    אולי הגיע הזמן שאני אלמד פייטון באמת במקום להיות מיושן 🙂

    keep’em coming!

  5. TAsn

    ספדר, פרל זה קול. הוא משמעותית יותר נוח לניתוח טקטס.

  6. :)

    עוד מעט חצי שנה מאז שהתפרסם הפוסט הזה ….
    רק לי זה מדליק נורה אדומה?

השאר תגובה

מחפש משהו?

תשתמש בטופס למטה כדי לחפש באתר: