Python逆向(二)—— pyc文件結構分析


一、前言

上一節我們知道了pyc文件是python在編譯過程中出現的主要中間過程文件。pyc文件是二進制的,可以由python虛擬機直接執行的程序。分析pyc文件的文件結構對於實現python編譯與反編譯就顯得十分重要。

Python代碼的編譯結果就是PyCodeObject對象。PyCodeObject對象可以由虛擬機加載后直接運行,而pyc文件就是PyCodeObject對象在硬盤上的保存形式。因此我們先分析PyCodeObject對象的結構,隨后再涉及pyc文件的二進制結構。

二、PyCodeObject對象結構分析

typedef struct {
    PyObject_HEAD
    int co_argcount;        /* 位置參數個數 */
    int co_nlocals;         /* 局部變量個數 */
    int co_stacksize;       /* 棧大小 */
    int co_flags;   
    PyObject *co_code;      /* 字節碼指令序列 */
    PyObject *co_consts;    /* 所有常量集合 */
    PyObject *co_names;     /* 所有符號名稱集合 */
    PyObject *co_varnames;  /* 局部變量名稱集合 */
    PyObject *co_freevars;  /* 閉包用的的變量名集合 */
    PyObject *co_cellvars;  /* 內部嵌套函數引用的變量名集合 */
    /* The rest doesn’t count for hash/cmp */
    PyObject *co_filename;  /* 代碼所在文件名 */
    PyObject *co_name;      /* 模塊名|函數名|類名 */
    int co_firstlineno;     /* 代碼塊在文件中的起始行號 */
    PyObject *co_lnotab;    /* 字節碼指令和行號的對應關系 */
    void *co_zombieframe;   /* for optimization only (see frameobject.c) */
} PyCodeObject;

上面就是PyCodeObject對象一般情況下包含的屬性名稱及數據類型,每個屬性在虛擬機執行pyc文件時都有其作用,隨后在編譯與反編譯的過程中我們會對上述出現的屬性一一分析。

三、pyc文件生成

python中使用marshal.dump的方法將PyCodeObject對象轉化為對應的二進制文件結構。每個字段在二進制文件中的結構如下圖:

byte表示占用1個字節,long表示占用4個字節,bytes表示該字段可能占用1到多個字節。需要說明的是,PyCodeObject對象中的每一個屬性及值都會按照一定的順序表示在二進制文件里。

pyc文件結構主要包括兩部分:pyc文件頭部表示和PyCodeObject對象部分。上面對PyCodeObject對象的二進制部分已經有了了解,pyc文件頭部比較簡單,在python2中只占用4個字節包含兩個字段magic和mtime,完整的pyc文件結構見下圖:

四、實例分析

上面我們對pyc文件結構已經有了理論上的了解,接下來通過一個實例對實際的二進制文件進行分析。
源文件test.py

s = "hello"                                                                                                                                                    
def func():
    a = 3 
    print s
func()

通過執行python2 -m py_compile test.py 可以生成編譯好的pyc文件test.pyc。

我們使用二進制編輯器打開test.pyc

  • pyc文件頭部
    • 前4個字節:03f3 0d0a,表示python版本
    • 5-8個字節:0e6b 905d,表示pyc文件修改時間
  • PyCodeObject對象二進制編譯結果
    • 第9字節:63,TYPE_CODE字段,也就是字符c,值為99,即0x63,表示接下為是一個PyCodeObject對象
  • PyCodeObject對象----全局參數:
    • 然后4個字節是0x00 0000 00,code block的位置參數個數co_argument,這里是0;
    • 再接着4個字節是0x00 0000 00, code block中的局部變量個數co_nlocals,這里是0;
    • 再接着4個字節是0x01 0000 00, code block需要的棧空間co_stacksize,這里是1;
    • 再接着4個字節是0x40 0000 00, co_flags,這里是64;
  • PyCodeObject對象----code block:
    • 1個字節0x73為TYPE_CODE字段, 表示該字段為string格式;
    • 4個字節0x1a00 0000表示code block段的數據部分占用0x1a個字節,即長度為26;
    • 接下來26個字節6400 ...... 6402 0053為該TYPE_CODE字段(數據類型string)部分,也就是pyc文件中包含的字節碼指令
  • 再往下的逐個TYPE_CODE字段都是重復結構的,用來表示PyCodeObject對象中的一些其他參數


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM