python逆向之pyc文件


最近做題老是遇到python逆向,沒有經驗,查了一些資料,記錄一下

 

 

Python是一門解釋性語言,沒有嚴格意義上的編譯和匯編過程

解釋型語言沒有嚴格編譯匯編過程,由解釋器將代碼塊按需要變運行邊翻譯給機器執行。因此解釋型語言一度存在運行效率底,重復解釋的問題。

但是通過對解釋器的優化!可以提高解釋型語言的運行效率。Python就屬於這一種編程語言。

一、pyc文件

1、pyc文件概述

Pyc文件是py編譯過程中產生的字節碼文件,可以由虛擬機直接執行,是python將目標源碼編譯成字節碼以后在磁盤上的文件形式

 

2、pyc文件結構(python 2.6.2 和 python3.8)

 

/* Bytecode object */

typedef struct {

    PyObject_HEAD

    int co_argcount;            /*  Code Block的位置參數個數,比如說一個函數的位置參數個數*/

    int co_nlocals;             /*  Code Block中局部變量的個數,包括其中位置參數的個數 */

    int co_stacksize;           /* 執行該段Code Block需要的棧空間 */

    int co_flags;               /* CO_..., see below */

    PyObject *co_code;          /* Code Block編譯所得的字節碼指令序列。以PyStingObjet的形式存在 */

    PyObject *co_consts;        /* PyTupleObject對象,保存CodeBlock中的所常量 */

    PyObject *co_names;         /* PyTupleObject對象,保存CodeBlock中的所有符號 */

    PyObject *co_varnames;      /* Code Block中的局部變量名集合 */

    PyObject *co_freevars;      /* Python實現閉包需要用的東西 */

    PyObject *co_cellvars;      /* Code Block中內部嵌套函數所引用的局部變量名集合 */

    /* The rest doesn't count for hash/cmp */

    PyObject *co_filename;      /* Code Block所對應的.py文件的完整路徑 */

    PyObject *co_name;          /* Code Block的名字,通常是函數名或類名 */

    int co_firstlineno;         /* Code Block在對應的.py文件中起始行 */

    PyObject *co_lnotab;        /* 字節碼指令與.py文件中source code行號的對應關系,以PyStringObject的形式存在 */

    void *co_zombieframe;     /* for optimization only (see frameobject.c) */

} PyCodeObject;

 python3.8:

typedef struct {
    PyObject_HEAD
    int co_argcount;            /* #arguments, except *args */
    int co_posonlyargcount;     /* #positional only arguments */
    int co_kwonlyargcount;      /* #keyword only arguments */
    int co_nlocals;             /* #local variables */
    int co_stacksize;           /* #entries needed for evaluation stack */
    int co_flags;               /* CO_..., see below */
    int co_firstlineno;         /* first source line number */
    PyObject *co_code;          /* instruction opcodes */
    PyObject *co_consts;        /* list (constants used) */
    PyObject *co_names;         /* list of strings (names used) */
    PyObject *co_varnames;      /* tuple of strings (local variable names) */
    PyObject *co_freevars;      /* tuple of strings (free variable names) */
    PyObject *co_cellvars;      /* tuple of strings (cell variable names) */
    /* The rest aren't used in either hash or comparisons, except for co_name,
       used in both. This is done to preserve the name and line number
       for tracebacks and debuggers; otherwise, constant de-duplication
       would collapse identical functions/lambdas defined on different lines.
    */
    Py_ssize_t *co_cell2arg;    /* Maps cell vars which are arguments. */
    PyObject *co_filename;      /* unicode (where it was loaded from) */
    PyObject *co_name;          /* unicode (name, for reference) */
    PyObject *co_lnotab;        /* string (encoding addr<->lineno mapping) See
                                   Objects/lnotab_notes.txt for details. */
    void *co_zombieframe;       /* for optimization only (see frameobject.c) */
    PyObject *co_weakreflist;   /* to support weakrefs to code objects */
    /* Scratch space for extra data relating to the code object.
       Type is a void* to keep the format private in codeobject.c to force
       people to go through the proper APIs. */
    void *co_extra;

    /* Per opcodes just-in-time cache
     *
     * To reduce cache size, we use indirect mapping from opcode index to
     * cache object:
     *   cache = co_opcache[co_opcache_map[next_instr - first_instr] - 1]
     */

    // co_opcache_map is indexed by (next_instr - first_instr).
    //  * 0 means there is no cache for this opcode.
    //  * n > 0 means there is cache in co_opcache[n-1].
    unsigned char *co_opcache_map;
    _PyOpcache *co_opcache;
    int co_opcache_flag;  // used to determine when create a cache.
    unsigned char co_opcache_size;  // length of co_opcache.
} PyCodeObject;

 

抄自  https://basicbit.cn/2018/12/20/2018-12-20-Python%E5%8F%8D%E7%BC%96%E8%AF%91%EF%BC%9F%E5%85%88%E8%81%8A%E8%81%8Apyc%E7%BB%93%E6%9E%84%E5%90%A7/

Python代碼的編譯結果就是PyCodeObject對象

每個pyCodeObject代表一個CodeBlock ,也可以稱之為作用域,一個pyc文件中不止一個Code Block,一個文件,函數,類,都會對應一個Code Block

 

(這張圖針對python2

long :四個字節

byte :1個字節

bytes :多個字節

 

Pyc文件結構在內存中的分布

https://www.cnblogs.com/blili/p/11799483.html

 

 

3、pyc文件的生成

 

Python -m compileall .py文件

 

 

 

 

4、實例

 

 

 

 

用二進制工具打開:

 

 

 

0x00h ~ 0x03h :03 F3 0D 0A是魔數,用於區分不同版本的python低字0d0a就是\r\n

0x04h ~ 0x07h :BB 67 F2 61 代表着修改時間

 

0x08: 0x63 ,他是一個的標識(TYPE_CODE)

0x09h ~ 0x0ch :00 00 00 00 代表全局 code block的位置的參數數量(co_argument)

0x0dh ~ 0x10h :00 00 00 00 代表code block的局部變量參數個數(co_nlocals)

0x11h ~ 0x14h :01 00 00 00 代表棧空間,該codeblock的棧空間為1(co_stacksize)

0x15h ~ 0x18h :40 00 00 00 (co_flags)

 

緊接着

0x19h : 0x73  PyStringObject(TYPE_STRING)的標志,在這之后就是字節碼了,PyCodeObject的字節碼序列是用PyStringObject對象來保存的,

0x1ah :  0x27 代表字節碼的大小,也就是0x27大小

這表示從 0x1bh ~ 0x41h 即是為該TYPE_CODE字段(數據類型string)部分,也就是pyc文件中包含的字節碼指令

 

證明:

 

 

先介紹幾個函數

marshal模塊:

 

 該模塊經常用於處理python的反序列化

Load函數

 

 

 

Co_consts函數:

 

 

Dis模塊

 

dis函數

 

 

 

結合函數我們可以發現我們字節碼的大小正好是39(0x27)

 

 

Co_code 在0x44的位置結束了,從0x45的位置開始就是 co_consts 的內容了 這里保存了code block的常量

 

0x45h 對應的0x28應該是標識符

0x46h ~ 0x49h : 04 00 00 00 是元素個數

0x4ah :0x73  TYPE_CODE為 0x74,數據類型是PyStringObject

后面的4個字節為字符串長度:

0x4bh ~ 0x4eh : 09 00 00 00  代表着 PyStringObject的字符長度為9

0x4fh ~ 0x57h : 48 65 6c 6c 6f 20 70 79 63 即是那長度為9的字符串

0x58h : 0x69  TYPE_CODE 為 0x69 則數據類型為int ,后面的4個字節為內容

0x59h ~ 0x5ch  : 0A 00 00 00 即為內容

0x5dh : 0x63 ,TYPE_CODE為0x63,數據類型為PyCodeObject ,與上面一樣分析

……

剩下的就不分析了,都差不多

 

 

二、python字節碼解讀

 

 

 

 python3 中每個字節碼的大小都為2,(一字節操作數,一字節參數)

快速查看字節碼的方法:python2 -m dis ./py

1、 常量

 

常量用 LOAD_CONST表示

2、局部變量

 

 

LOAD_FAST一般加載局部變量的值,也就是讀取值,用於計算或者函數調用傳參等。
STORE_FAST一般用於保存值到局部變量。

 

 

3、全局變量

 

LOAD_GLOBAL用來加載全局變量,包括指定函數名,類名,模塊名等全局符號。

STORE_GLOBAL用來給全局變量賦值。

 

4、數據類型list

 

 

5、數據類型dict

 

 

6、數學運算

 

 

7、for循環

 

SETUP_LOOP用於開始一個循環。SETUP_LOOP 25 (to 28)28表示循環退出點

GET_ITER 表示開始迭代

FOR_ITER 表示繼續iter開始下一個

8、if

 

POP_JUMP_IF_FALSEJUMP_FORWARD一般用於分支判斷跳轉。POP_JUMP_IF_FALSE表示條件結果為FALSE就跳轉到目標偏移指令。JUMP_FORWARD直接跳轉到目標偏移指令。

9、函數調用

 

 

參考:

https://basicbit.cn/2018/12/20/2018-12-20-Python%E5%8F%8D%E7%BC%96%E8%AF%91%EF%BC%9F%E5%85%88%E8%81%8A%E8%81%8Apyc%E7%BB%93%E6%9E%84%E5%90%A7/

https://www.cnblogs.com/blili/p/11799398.html

https://bbs.pediy.com/thread-246683.htm#msg_header_h3_6


免責聲明!

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



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