死磕python字節碼手工還原python源碼,牛皮!

死磕python字節碼手工還原python源碼,牛皮!

一、前言

> Python 代碼先被編譯為字節碼後,再由Python虛擬機來執行字節碼, Python的字節碼是一種類似彙編指令的中間語言, 一個Python語句會對應若干字節碼指令,虛擬機一條一條執行字節碼指令, 從而完成程序執行。

Python dis 模塊支持對Python代碼進行反彙編, 生成字節碼指令。dis.dis()將CPython字節碼轉為可讀的偽代碼(類似於彙編代碼)。結構如下:

死磕python字節碼手工還原python源碼,牛皮!

其實就是這樣的結構:

源碼行號 | 指令在函數中的偏移 | 指令符號 | 指令參數 | 實際參數值

本文福利:私信回覆【PDF】可獲取Python電子書一套

二、變量

1.const

LOAD_CONST加載const變量,比如數值、字符串等等,一般用於傳給函數的參數

死磕python字節碼手工還原python源碼,牛皮!

轉為python代碼就是:

死磕python字節碼手工還原python源碼,牛皮!

2.局部變量

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

死磕python字節碼手工還原python源碼,牛皮!

這段bytecode轉為python就是:

死磕python字節碼手工還原python源碼,牛皮!

函數的形參也是局部變量,如何區分出是函數形參還是其他局部變量呢?

形參沒有初始化,也就是從函數開始到LOAD_FAST該變量的位置,如果沒有看到STORE_FAST,那麼該變量就是函數形參。

而其他局部變量在使用之前肯定會使用STORE_FAST進行初始化。具體看下面的實例:

死磕python字節碼手工還原python源碼,牛皮!

對應的python代碼如下,對比一下就一目瞭然。

死磕python字節碼手工還原python源碼,牛皮!

3.全局變量

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

死磕python字節碼手工還原python源碼,牛皮!

對應的python代碼

死磕python字節碼手工還原python源碼,牛皮!

三、常用數據類型

1.list

BUILD_LIST用於創建一個list結構。

死磕python字節碼手工還原python源碼,牛皮!

對應python代碼是:

死磕python字節碼手工還原python源碼,牛皮!

另外再看看一種常見的創建list的方式如下:

死磕python字節碼手工還原python源碼,牛皮!

一個實例bytecode如下:

死磕python字節碼手工還原python源碼,牛皮!

轉為python代碼是:

死磕python字節碼手工還原python源碼,牛皮!

2.dict

BUILD_MAP用於創建一個空的dict。STORE_MAP用於初始化dict的內容。

死磕python字節碼手工還原python源碼,牛皮!

對應的python代碼是:

死磕python字節碼手工還原python源碼,牛皮!

再看看修改dict的bytecode:

死磕python字節碼手工還原python源碼,牛皮!

對應的python代碼是:

死磕python字節碼手工還原python源碼,牛皮!

3.slice

BUILD_SLICE用於創建slice。

對於list、元組、字符串都可以使用slice的方式進行訪問。但是要注意BUILD_SLICE用於[x:y:z]這種類型的slice,結合BINARY_SUBSCR讀取slice的值,結合STORE_SUBSCR用於修改slice的值。

另外SLICE+n用於[a:b]類型的訪問,STORE_SLICE+n用於[a:b]類型的修改,其中n表示如下:

死磕python字節碼手工還原python源碼,牛皮!

下面看具體實例:

死磕python字節碼手工還原python源碼,牛皮!

四、循環

SETUP_LOOP用於開始一個循環。SETUP_LOOP 26 (to 35)中35表示循環退出點。

1.while循環

死磕python字節碼手工還原python源碼,牛皮!

對應python代碼是:

死磕python字節碼手工還原python源碼,牛皮!

2.for in結構

死磕python字節碼手工還原python源碼,牛皮!

這是典型的for+in結構,轉為python代碼就是:

死磕python字節碼手工還原python源碼,牛皮!

五、if

POP_JUMP_IF_FALSE和JUMP_FORWARD一般用於分支判斷跳轉。

POP_JUMP_IF_FALSE表示條件結果為FALSE就跳轉到目標偏移指令。

JUMP_FORWARD直接跳轉到目標偏移指令。

死磕python字節碼手工還原python源碼,牛皮!

轉為python代碼是:

死磕python字節碼手工還原python源碼,牛皮!

六、分辨函數

1.函數範圍

前面介紹第二列表示指令在函數中的偏移地址,所以看到0就是函數開始,下一個0前一條指令就是函數結束位置,當然也可以通過RETURN_VALUE來確定函數結尾

死磕python字節碼手工還原python源碼,牛皮!

2.函數調用

函數調用類似於push+call的彙編結構,壓棧參數從左到右依次壓入(當然不是push,而是讀取指令LOAD_xxxx來指定參數)。

函數名一般通過LOAD_GLOBAL指令指定,如果是模塊函數或者類成員函數通過LOAD_GLOBAL+LOAD_ATTR來指定。先指定要調用的函數,然後壓參數,最後通過CALL_FUNCTION調用。

CALL_FUNCTION後面的值表示有幾個參數。支持嵌套調用:

死磕python字節碼手工還原python源碼,牛皮!

這段bytecode轉換成python代碼就是

死磕python字節碼手工還原python源碼,牛皮!

七、其他指令

其他常見指令,一看就明白,就不具體分析了

死磕python字節碼手工還原python源碼,牛皮!

基礎運算還有一套對應的BINARY_xxxx指令,兩者區別很簡單。

死磕python字節碼手工還原python源碼,牛皮!

歡迎各位大佬指點!


分享到:


相關文章: