最近做題老是遇到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_FALSE
和JUMP_FORWARD
一般用於分支判斷跳轉。POP_JUMP_IF_FALSE
表示條件結果為FALSE
就跳轉到目標偏移指令。JUMP_FORWARD
直接跳轉到目標偏移指令。
9、函數調用
參考: