Python3 字节码详解
文章目錄
- 前言
- 什么是 py 字節(jié)碼?
- 變量
- 常用數(shù)據(jù)類型
- list
- dict
- slice
- 循環(huán)
- while
- for
- if
- 其他指令
- 后記
前言
在逆向的時(shí)候遇到過反編譯 py 字節(jié)碼,之前也就沒咋在意,啥不會查就完事兒了,好家伙,省賽讓我給遇到了,直接嚶嚶嚶😭,但還好解出來了;
今天趁這個(gè)機(jī)會,系統(tǒng)的學(xué)習(xí)一下,以防下次陰溝里翻船,本博文的 Python 版本是3.8.5,版本不同形成的字節(jié)碼會略有不同,但是大同小異;
【記】2021年第四屆浙江省大學(xué)生網(wǎng)絡(luò)安全技能挑戰(zhàn)賽:
- CSDN
- 個(gè)人博客
?
什么是 py 字節(jié)碼?
Python 代碼先被編譯為字節(jié)碼后,再由 Python 虛擬機(jī)來執(zhí)行字節(jié)碼,Python 的字節(jié)碼是一種類似匯編指令的中間語言,一個(gè) Python 語句會對應(yīng)若干字節(jié)碼指令,虛擬機(jī)一條一條執(zhí)行字節(jié)碼指令,從而完成程序執(zhí)行。
Python 的 dis 模塊支持對 Python 代碼進(jìn)行反匯編, 生成字節(jié)碼指令。
結(jié)構(gòu):
源碼行號 | 指令在函數(shù)中的偏移 | 指令符號 | 指令參數(shù) | 實(shí)際參數(shù)值源碼:
str = [88, 117, 124, 124, 127, 48, 71, 127, 98, 124, 116, 48, 61, 61, 121, 116, 33, 32, 100, 62]def test():for st in str:print(chr(st^16),end='')test()字節(jié)碼:
1 0 LOAD_CONST 0 (0)2 LOAD_CONST 1 (None)4 IMPORT_NAME 0 (dis)6 STORE_NAME 0 (dis)3 8 LOAD_CONST 2 (88)10 LOAD_CONST 3 (117)12 LOAD_CONST 4 (124)14 LOAD_CONST 4 (124)16 LOAD_CONST 5 (127)18 LOAD_CONST 6 (48)20 LOAD_CONST 7 (71)22 LOAD_CONST 5 (127)24 LOAD_CONST 8 (98)26 LOAD_CONST 4 (124)28 LOAD_CONST 9 (116)30 LOAD_CONST 6 (48)32 LOAD_CONST 10 (61)34 LOAD_CONST 10 (61)36 LOAD_CONST 11 (121)38 LOAD_CONST 9 (116)40 LOAD_CONST 12 (33)42 LOAD_CONST 13 (32)44 LOAD_CONST 14 (100)46 LOAD_CONST 15 (62)48 BUILD_LIST 2050 STORE_NAME 1 (str)5 52 LOAD_CONST 16 (<code object test at 0x0170E2F8, file "1.py", line 5>)54 LOAD_CONST 17 ('test')56 MAKE_FUNCTION 058 STORE_NAME 2 (test)9 60 LOAD_NAME 2 (test)62 CALL_FUNCTION 064 POP_TOP66 LOAD_CONST 1 (None)68 RETURN_VALUEDisassembly of <code object test at 0x0170E2F8, file "1.py", line 5>:6 0 LOAD_GLOBAL 0 (str)2 GET_ITER>> 4 FOR_ITER 24 (to 30)6 STORE_FAST 0 (st)7 8 LOAD_GLOBAL 1 (print)10 LOAD_GLOBAL 2 (chr)12 LOAD_FAST 0 (st)14 LOAD_CONST 1 (16)16 BINARY_XOR18 CALL_FUNCTION 120 LOAD_CONST 2 ('')22 LOAD_CONST 3 (('end',))24 CALL_FUNCTION_KW 226 POP_TOP28 JUMP_ABSOLUTE 4>> 30 LOAD_CONST 0 (None)32 RETURN_VALUE稍后會詳細(xì)介紹;
?
變量
1、CONST
LOAD_CONST 加載 const 變量,比如數(shù)值、字符串等等,一般用于傳給函數(shù)的參數(shù);
11 52 LOAD_NAME 2 (test)54 LOAD_CONST 16 ('nice')56 CALL_FUNCTION 158 POP_TOP test('nice')?
2、局部變量
- LOAD_FAST 一般加載局部變量的值,也就是讀取值,用于計(jì)算或者函數(shù)調(diào)用傳參等;
- STORE_FAST 一般用于保存值到局部變量;
那問題來了,函數(shù)的形參也是局部變量,如何區(qū)分出是函數(shù)形參還是其他局部變量呢?
我們可以自己寫一段代碼推敲一下:
import disstr = ''def test(arg):str = 'idi10t'print(arg,str)dis.dis(test) 6 0 LOAD_CONST 1 ('idi10t')2 STORE_FAST 1 (str) 7 4 LOAD_GLOBAL 0 (print)6 LOAD_FAST 0 (arg) 8 LOAD_FAST 1 (str) 10 CALL_FUNCTION 2 12 POP_TOP14 LOAD_CONST 0 (None) 16 RETURN_VALUE可以得出結(jié)論:形參沒有初始化,也就是從函數(shù)開始到 LOAD_FAST 該變量的位置,如果沒有看到 STORE_FAST,那么該變量就是函數(shù)形參;而其他局部變量在使用之前肯定會使用 STORE_FAST 進(jìn)行初始化。
?
3、全局變量
- LOAD_GLOBAL 用來加載全局變量,包括指定函數(shù)名,類名,模塊名等全局符號;
- STORE_GLOBAL 用來給全局變量賦值;
?
常用數(shù)據(jù)類型
list
BUILD_LIST 用于創(chuàng)建一個(gè) list 結(jié)構(gòu):
str = [88, 117, 124, 124, 127, 48, 71, 127, 98, 124, 116, 48, 61, 61, 121, 116, 33, 32, 100, 62] 3 8 LOAD_CONST 2 (88)10 LOAD_CONST 3 (117)12 LOAD_CONST 4 (124)14 LOAD_CONST 4 (124)16 LOAD_CONST 5 (127)18 LOAD_CONST 6 (48)20 LOAD_CONST 7 (71)22 LOAD_CONST 5 (127)24 LOAD_CONST 8 (98)26 LOAD_CONST 4 (124)28 LOAD_CONST 9 (116)30 LOAD_CONST 6 (48)32 LOAD_CONST 10 (61)34 LOAD_CONST 10 (61)36 LOAD_CONST 11 (121)38 LOAD_CONST 9 (116)40 LOAD_CONST 12 (33)42 LOAD_CONST 13 (32)44 LOAD_CONST 14 (100)46 LOAD_CONST 15 (62)48 BUILD_LIST 2050 STORE_NAME 1 (str)再看看另一種的 list 創(chuàng)建方式:
str = [88, 117, 124, 124, 127, 48, 71, 127, 98, 124, 116, 48, 61, 61, 121, 116, 33, 32, 100, 62][x for x in str if x != 48] 1 0 LOAD_CONST 0 (88)2 LOAD_CONST 1 (117)4 LOAD_CONST 2 (124)6 LOAD_CONST 2 (124)8 LOAD_CONST 3 (127)10 LOAD_CONST 4 (48)12 LOAD_CONST 5 (71)14 LOAD_CONST 3 (127)16 LOAD_CONST 6 (98)18 LOAD_CONST 2 (124)20 LOAD_CONST 7 (116)22 LOAD_CONST 4 (48)24 LOAD_CONST 8 (61)26 LOAD_CONST 8 (61)28 LOAD_CONST 9 (121)30 LOAD_CONST 7 (116)32 LOAD_CONST 10 (33)34 LOAD_CONST 11 (32)36 LOAD_CONST 12 (100)38 LOAD_CONST 13 (62)40 BUILD_LIST 2042 STORE_NAME 0 (str)3 44 LOAD_CONST 14 (<code object <listcomp> at 0x016FE2F8, file "1.py", line 3>)46 LOAD_CONST 15 ('<listcomp>')48 MAKE_FUNCTION 050 LOAD_NAME 0 (str)52 GET_ITER54 CALL_FUNCTION 156 POP_TOP58 LOAD_CONST 16 (None)60 RETURN_VALUEDisassembly of <code object <listcomp> at 0x016FE2F8, file "1.py", line 3>:3 0 BUILD_LIST 0 # 創(chuàng)建 list,為賦值給某變量2 LOAD_FAST 0 (.0)>> 4 FOR_ITER 16 (to 22)6 STORE_FAST 1 (x)8 LOAD_FAST 1 (x)10 LOAD_CONST 0 (48)12 COMPARE_OP 3 (!=)14 POP_JUMP_IF_FALSE 4 # 不滿足條件則 break16 LOAD_FAST 1 (x) # 讀取滿足條件的 x18 LIST_APPEND 2 # 把每個(gè)滿足條件的 x 存入 list20 JUMP_ABSOLUTE 4>> 22 RETURN_VALUE?
dict
- BUILD_MAP 用于創(chuàng)建一個(gè)空的 dict;
- STORE_NAME 用于初始化 dict 的內(nèi)容;
?
slice
這里直接借用了大佬博文的數(shù)據(jù);
BUILD_SLICE 用于創(chuàng)建 slice,對于 list、元組、字符串都可以使用 slice 的方式進(jìn)行訪問。
但是要注意 BUILD_SLICE 用于 [x:y:z] 這種類型的 slice,結(jié)合 BINARY_SUBSCR 讀取 slice 的值,結(jié)合 STORE_SUBSCR 用于修改 slice 的值。
另外 SLICE + n 用于 [a:b] 類型的訪問,STORE_SLICE + n 用于 [a:b] 類型的修改,其中 n 表示如下:
SLICE+0() Implements TOS = TOS[:].SLICE+1() Implements TOS = TOS1[TOS:].SLICE+2() Implements TOS = TOS1[:TOS].SLICE+3() Implements TOS = TOS2[TOS1:TOS]. 13 0 LOAD_CONST 1 (1)3 LOAD_CONST 2 (2)6 LOAD_CONST 3 (3)9 BUILD_LIST 312 STORE_FAST 0 (k1) //k1 = [1, 2, 3]14 15 LOAD_CONST 4 (10)18 BUILD_LIST 121 LOAD_FAST 0 (k1)24 LOAD_CONST 5 (0)27 LOAD_CONST 1 (1)30 LOAD_CONST 1 (1)33 BUILD_SLICE 336 STORE_SUBSCR //k1[0:1:1] = [10]15 37 LOAD_CONST 6 (11)40 BUILD_LIST 143 LOAD_FAST 0 (k1)46 LOAD_CONST 1 (1)49 LOAD_CONST 2 (2)52 STORE_SLICE+3 //k1[1:2] = [11]16 53 LOAD_FAST 0 (k1)56 LOAD_CONST 1 (1)59 LOAD_CONST 2 (2)62 SLICE+363 STORE_FAST 1 (a) //a = k1[1:2]17 66 LOAD_FAST 0 (k1)69 LOAD_CONST 5 (0)72 LOAD_CONST 1 (1)75 LOAD_CONST 1 (1)78 BUILD_SLICE 381 BINARY_SUBSCR82 STORE_FAST 2 (b) //b = k1[0:1:1]?
循環(huán)
while
Python3.8 及以上就沒有 SETUP_LOOP 了,
大致意思就是將循環(huán)塊送入到堆棧當(dāng)中去,
i = 0 while i < 10:i += 1 1 0 LOAD_CONST 0 (0)2 STORE_NAME 0 (i)2 >> 4 LOAD_NAME 0 (i)6 LOAD_CONST 1 (10)8 COMPARE_OP 0 (<)10 POP_JUMP_IF_FALSE 223 12 LOAD_NAME 0 (i)14 LOAD_CONST 2 (1)16 INPLACE_ADD18 STORE_NAME 0 (i)20 JUMP_ABSOLUTE 4>> 22 LOAD_CONST 3 (None)24 RETURN_VALUE?
for
Python 中典型的 for in 結(jié)構(gòu):
for i in range(8): 2 4 LOAD_NAME 1 (range)6 LOAD_CONST 1 (8)8 CALL_FUNCTION 110 GET_ITER>> 12 FOR_ITER 38 (to 52)14 STORE_NAME 2 (i)...50 JUMP_ABSOLUTE 12>> 52 LOAD_CONST 6 (None)?
if
POP_JUMP_IF_FALSE 和 JUMP_FORWARD 一般用于分支判斷跳轉(zhuǎn):
- POP_JUMP_IF_FALSE 表示條件結(jié)果為 FALSE 就跳轉(zhuǎn)到目標(biāo)偏移指令;
- JUMP_FORWARD 直接跳轉(zhuǎn)到目標(biāo)偏移指令;
?
其他指令
上述就是比較常用的一些指令了,當(dāng)然還有更多的指令,這里就不一一介紹了,詳情見官方文檔,這里的是 Python3.8 版本的官方文檔;
?
后記
開卷有益,多多益善;
參考:
官方文檔
Python內(nèi)置模塊dis.py源碼詳解
死磕python字節(jié)碼-手工還原python源碼
?
總結(jié)
以上是生活随笔為你收集整理的Python3 字节码详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 小型移动 webApp Demo 知识点
- 下一篇: mysql long类型_怒肝两个月My