一文让你完全弄懂Stegosaurus
國內(nèi)關(guān)于 Stegosaurus 的介紹少之又少,一般只是單純的工具使用的講解之類的,并且本人在學(xué)習(xí)過程中也是遇到了很多的問題,基于此種情況下寫下此文,也是為我逝去的青春時(shí)光留個(gè)念想吧~
Stegosaurus是什么?
在了解 Stegosaurus 是什么之前,我們首先需要弄清楚的一個(gè)問題是:什么是隱寫術(shù)?
隱寫術(shù),從字面上來理解,隱是隱藏,所以我們從字面上可以知道,隱寫術(shù)是一類可以隱藏自己寫的一些東西的方法,可能我們所寫的這些東西是一些比較重要的信息,不想讓別人看到,我們會(huì)考慮采取一些辦法去隱藏它,比如對(duì)所寫的文件加解密,用一些特殊的紙張(比如紙張遇到水后,上面的字才會(huì)顯示出來)之類的。隱寫術(shù)這種手段在日常生活中用的十分廣泛,我相信部分小伙伴們小時(shí)候曾經(jīng)有過寫日記的習(xí)慣,寫完的日記可能不想讓爸爸媽媽知道(青春期萌動(dòng)的內(nèi)心,咱們都是過來人,都懂這個(gè)2333),所以以前常常會(huì)買那種上了把鎖的那種日記本,這樣就不怕自己的小秘密被爸爸媽媽知道啦。
事實(shí)上,隱寫術(shù)是一門關(guān)于信息隱藏的技巧與科學(xué),專業(yè)一點(diǎn)的講,就是指的是采取一些不讓除預(yù)期的接收者之外的任何人知曉信息的傳遞事件或者信息的內(nèi)容的方法。隱寫術(shù)的英文叫做 Steganography ,根據(jù)維基百科的解釋,這個(gè)英文來源于特里特米烏斯的一本講述密碼學(xué)與隱寫術(shù)的著作 Steganographia ,該書書名源于希臘語,意為“隱秘書寫”。(這個(gè)不是重點(diǎn))
所以今天呢,我們要給大家介紹的是隱寫術(shù)的其中一個(gè)分支(也就是其中一種隱寫的方法),也就是 Stegosaurus , Stegosaurus 是一款隱寫工具,它允許我們?cè)?Python 字節(jié)碼文件( pyc 或 pyo )中嵌入任意 Payload 。由于編碼密度較低,因此我們嵌入 Payload 的過程既不會(huì)改變?cè)创a的運(yùn)行行為,也不會(huì)改變?cè)次募奈募笮 ?Payload 代碼會(huì)被分散嵌入到字節(jié)碼之中,所以類似 strings 這樣的代碼工具無法查找到實(shí)際的 Payload 。 Python 的 dis 模塊會(huì)返回源文件的字節(jié)碼,然后我們就可以使用 Stegosaurus 來嵌入 Payload 了。
為了方便維護(hù),我將此項(xiàng)目移至 Github 上:https://github.com/AngelKitty/stegosaurus
首先講到一個(gè)工具,不可避免的,我們需要講解它的用法,我并不會(huì)像文檔一樣工整的把用法羅列在一起,如果需要了解更加細(xì)節(jié)的部分請(qǐng)參考 Github上的詳細(xì)文檔,我會(huì)拿一些實(shí)際的案例去給大家講解一些常見命令的用法,在后續(xù)的文章中,我會(huì)大家深入理解 python 反編譯的一些東西。
Stegosaurus 僅支持 Python3.6 及其以下版本
拿到一個(gè)工具,我們一般會(huì)看看它的基本用法:
python3 stegosaurus.py -h $ python3 -m stegosaurus -h usage: stegosaurus.py [-h] [-p PAYLOAD] [-r] [-s] [-v] [-x] carrierpositional arguments:carrier Carrier py, pyc or pyo fileoptional arguments:-h, --help show this help message and exit-p PAYLOAD, --payload PAYLOADEmbed payload in carrier file-r, --report Report max available payload size carrier supports-s, --side-by-side Do not overwrite carrier file, install side by sideinstead.-v, --verbose Increase verbosity once per use-x, --extract Extract payload from carrier file我們可以看到有很多參數(shù)選項(xiàng),我們就以一道賽題來講解部分參數(shù)命令吧~
我們此次要講解的這道題是來自 Bugku 的 QAQ
賽題鏈接如下:
http://ctf.bugku.com/files/447e4b626f2d2481809b8690613c1613/QAQ http://ctf.bugku.com/files/5c02892cd05a9dcd1c5a34ef22dd9c5e/cipher.txt首先拿到這道題,用 010Editor 乍一眼看過去,我們可以看到一些特征信息:
可以判斷這是個(gè)跟 python 有關(guān)的東西,通過查閱相關(guān)資料可以判斷這是個(gè) python 經(jīng)編譯過后的 pyc 文件。這里可能很多小伙伴們可能不理解了,什么是 pyc 文件呢?為什么會(huì)生成 pyc 文件? pyc 文件又是何時(shí)生成的呢?下面我將一一解答這些問題。
簡單來說, pyc 文件就是 Python 的字節(jié)碼文件,是個(gè)二進(jìn)制文件。我們都知道 Python 是一種全平臺(tái)的解釋性語言,全平臺(tái)其實(shí)就是 Python 文件在經(jīng)過解釋器解釋之后(或者稱為編譯)生成的 pyc 文件可以在多個(gè)平臺(tái)下運(yùn)行,這樣同樣也可以隱藏源代碼。其實(shí), Python 是完全面向?qū)ο蟮恼Z言, Python 文件在經(jīng)過解釋器解釋后生成字節(jié)碼對(duì)象 PyCodeObject , pyc 文件可以理解為是 PyCodeObject 對(duì)象的持久化保存方式。而 pyc 文件只有在文件被當(dāng)成模塊導(dǎo)入時(shí)才會(huì)生成。也就是說, Python 解釋器認(rèn)為,只有 import 進(jìn)行的模塊才需要被重用。 生成 pyc 文件的好處顯而易見,當(dāng)我們多次運(yùn)行程序時(shí),不需要重新對(duì)該模塊進(jìn)行重新的解釋。主文件一般只需要加載一次,不會(huì)被其他模塊導(dǎo)入,所以一般主文件不會(huì)生成 pyc 文件。
我們舉個(gè)例子來說明這個(gè)問題:
為了方便起見,我們事先創(chuàng)建一個(gè)test文件夾作為此次實(shí)驗(yàn)的測(cè)試:
mkdir test && cd test/假設(shè)我們現(xiàn)在有個(gè) test.py 文件,文件內(nèi)容如下:
def print_test():print('Hello,Kitty!')print_test()我們執(zhí)行以下命令:
python3 test.py不用說,想必大家都知道打印出的結(jié)果是下面這個(gè):
Hello,Kitty!我們通過下面命令查看下當(dāng)前文件夾下有哪些文件:
ls -alh我們可以發(fā)現(xiàn),并沒有 pyc 文件生成。
‘我們?cè)偃?chuàng)建一個(gè)文件為 import_test.py 文件,文件內(nèi)容如下:
注: test.py 和 import_test.py 應(yīng)當(dāng)放在同一文件夾下
import testtest.print_test()我們執(zhí)行以下命令:
python3 import_test.py結(jié)果如下:
Hello,Kitty! Hello,Kitty!誒,為啥會(huì)打印出兩句相同的話呢?我們?cè)偻驴?#xff0c;我們通過下面命令查看下當(dāng)前文件夾下有哪些文件:
ls -alh結(jié)果如下:
總用量 20K drwxr-xr-x 3 python python 4.0K 11月 5 20:38 . drwxrwxr-x 4 python python 4.0K 11月 5 20:25 .. -rw-r--r-- 1 python python 31 11月 5 20:38 import_test.py drwxr-xr-x 2 python python 4.0K 11月 5 20:38 __pycache__ -rw-r--r-- 1 python python 58 11月 5 20:28 test.py誒,多了個(gè) __pycache__ 文件夾,我們進(jìn)入文件夾下看看有什么?
cd __pycache__ && ls我們可以看到生成了一個(gè) test.cpython-36.pyc 。為什么是這樣子呢?
我們可以看到,我們?cè)趫?zhí)行 python3 import_test.py 命令的時(shí)候,首先開始執(zhí)行的是 import test ,即導(dǎo)入 test 模塊,而一個(gè)模塊被導(dǎo)入時(shí), PVM(Python Virtual Machine) 會(huì)在后臺(tái)從一系列路徑中搜索該模塊,其搜索過程如下:
- 在當(dāng)前目錄下搜索該模塊
- 在環(huán)境變量 PYTHONPATH 中指定的路徑列表中依次搜索
- 在 python 安裝路徑中搜索
事實(shí)上, PVM 通過變量 sys.path 中包含的路徑來搜索,這個(gè)變量里面包含的路徑列表就是上面提到的這些路徑信息。
模塊的搜索路徑都放在了 sys.path 列表中,如果缺省的 sys.path 中沒有含有自己的模塊或包的路徑,可以動(dòng)態(tài)的加入 (sys.path.apend) 即可。
事實(shí)上, Python 中所有加載到內(nèi)存的模塊都放在 sys.modules 。當(dāng) import 一個(gè)模塊時(shí)首先會(huì)在這個(gè)列表中查找是否已經(jīng)加載了此模塊,如果加載了則只是將模塊的名字加入到正在調(diào)用 import 的模塊的 Local 名字空間中。如果沒有加載則從 sys.path 目錄中按照模塊名稱查找模塊文件,模塊文件可以是 py 、 pyc 、 pyd ,找到后將模塊載入內(nèi)存,并加入到 sys.modules 中,并將名稱導(dǎo)入到當(dāng)前的 Local 名字空間。
可以看出來,一個(gè)模塊不會(huì)重復(fù)載入。多個(gè)不同的模塊都可以用 import 引入同一個(gè)模塊到自己的 Local 名字空間,其實(shí)背后的 PyModuleObject 對(duì)象只有一個(gè)。
在這里,我還要說明一個(gè)問題,import 只能導(dǎo)入模塊,不能導(dǎo)入模塊中的對(duì)象(類、函數(shù)、變量等)。例如像上面這個(gè)例子,我在 test.py 里面定義了一個(gè)函數(shù) print_test() ,我在另外一個(gè)模塊文件 import_test.py不能直接通過 import test.print_test 將 print_test 導(dǎo)入到本模塊文件中,只能用 import test 進(jìn)行導(dǎo)入。如果我想只導(dǎo)入特定的類、函數(shù)、變量,用 from test import print_test 即可。
既然說到了 import 導(dǎo)入機(jī)制,再提一提嵌套導(dǎo)入和 Package 導(dǎo)入。
import 嵌套導(dǎo)入
嵌套,不難理解,就是一個(gè)套著一個(gè)。小時(shí)候我們都玩過俄羅斯套娃吧,俄羅斯套娃就是一個(gè)大娃娃里面套著一個(gè)小娃娃,小娃娃里面還有更小的娃娃,而這個(gè)嵌套導(dǎo)入也是同一個(gè)意思。假如我們現(xiàn)在有一個(gè)模塊,我們想要導(dǎo)入模塊 A ,而模塊 A 中有含有其他模塊需要導(dǎo)入,比如模塊 B ,模塊 B 中又含有模塊 C ,一直這樣延續(xù)下去,這種方式我們稱之為 import 嵌套導(dǎo)入。
對(duì)這種嵌套比較容易理解,我們需要注意的一點(diǎn)就是各個(gè)模塊的 Local 名字空間是獨(dú)立的,所以上面的例子,本模塊 import A 完了后,本模塊只能訪問模塊 A ,不能訪問 B 及其它模塊。雖然模塊 B 已經(jīng)加載到內(nèi)存了,如果要訪問,還必須明確在本模塊中導(dǎo)入 import B 。
那如果我們有以下嵌套這種情況,我們?cè)撛趺刺幚砟?#xff1f;
比如我們現(xiàn)在有個(gè)模塊 A :
# A.py from B import D class C:pass還有個(gè)模塊 B :
# B.py from A import C class D:pass我們簡單分析一下程序,如果程序運(yùn)行,應(yīng)該會(huì)去從模塊B中調(diào)用對(duì)象D。
我們嘗試執(zhí)行一下 python A.py :
報(bào) ImportError 的錯(cuò)誤,似乎是沒有加載到對(duì)象 D ,而我們將 from B import D 改成 import B ,我們似乎就能執(zhí)行成功了。
這是怎么回事呢?這其實(shí)是跟 Python 內(nèi)部 import 的機(jī)制是有關(guān)的,具體到 from B import D , Python 內(nèi)部會(huì)分成以下幾個(gè)步驟:
- 在 sys.modules 中查找符號(hào) B
- 如果符號(hào) B 存在,則獲得符號(hào) B 對(duì)應(yīng)的 module 對(duì)象 <module B> 。從 <module B> 的 __dict__ 中獲得符號(hào) D 對(duì)應(yīng)的對(duì)象,如果 D 不存在,則拋出異常
- 如果符號(hào) B 不存在,則創(chuàng)建一個(gè)新的 module 對(duì)象 <module B> ,注意,此時(shí) module 對(duì)象的 __dict__ 為空。執(zhí)行 B.py 中的表達(dá)式,填充 <module B> 的 __dict__ 。從 <module B> 的 __dict__ 中獲得 D 對(duì)應(yīng)的對(duì)象。如果 D 不存在,則拋出異常。
所以,這個(gè)例子的執(zhí)行順序如下:
1、執(zhí)行 A.py 中的 from B import D
注:由于是執(zhí)行的 python A.py ,所以在 sys.modules 中并沒有 <module B> 存在,首先為 B.py 創(chuàng)建一個(gè) module 對(duì)象( <module B> ),注意,這時(shí)創(chuàng)建的這個(gè) module 對(duì)象是空的,里邊啥也沒有,在 Python 內(nèi)部創(chuàng)建了這個(gè) module 對(duì)象之后,就會(huì)解析執(zhí)行 B.py ,其目的是填充 <module B> 這個(gè) dict 。
2、執(zhí)行 B.py 中的 from A import C
注:在執(zhí)行 B.py 的過程中,會(huì)碰到這一句,首先檢查 sys.modules 這個(gè) module 緩存中是否已經(jīng)存在 <module A> 了,由于這時(shí)緩存還沒有緩存 <module A> ,所以類似的, Python 內(nèi)部會(huì)為 A.py 創(chuàng)建一個(gè) module 對(duì)象( <module A> ),然后,同樣地,執(zhí)行 A.py 中的語句。
3、再次執(zhí)行 A.py 中的 from B import D
注:這時(shí),由于在第 1 步時(shí),創(chuàng)建的 <module B> 對(duì)象已經(jīng)緩存在了 sys.modules 中,所以直接就得到了 <module B> ,但是,注意,從整個(gè)過程來看,我們知道,這時(shí) <module B> 還是一個(gè)空的對(duì)象,里面啥也沒有,所以從這個(gè) module 中獲得符號(hào) D 的操作就會(huì)拋出異常。如果這里只是 import B ,由于 B 這個(gè)符號(hào)在 sys.modules 中已經(jīng)存在,所以是不會(huì)拋出異常的。
我們可以從下圖很清楚的看到 import 嵌套導(dǎo)入的過程:
Package 導(dǎo)入
包 (Package) 可以看成模塊的集合,只要一個(gè)文件夾下面有個(gè) __init__.py 文件,那么這個(gè)文件夾就可以看做是一個(gè)包。包下面的文件夾還可以成為包(子包)。更進(jìn)一步的講,多個(gè)較小的包可以聚合成一個(gè)較大的包。通過包這種結(jié)構(gòu),我們可以很方便的進(jìn)行類的管理和維護(hù),也方便了用戶的使用。比如 SQLAlchemy 等都是以包的形式發(fā)布給用戶的。
包和模塊其實(shí)是很類似的東西,如果查看包的類型: import SQLAlchemy type(SQLAlchemy) ,可以看到其實(shí)也是 <type 'module'> 。 import 包的時(shí)候查找的路徑也是 sys.path。
包導(dǎo)入的過程和模塊的基本一致,只是導(dǎo)入包的時(shí)候會(huì)執(zhí)行此包目錄下的 __init__.py ,而不是模塊里面的語句了。另外,如果只是單純的導(dǎo)入包,而包的 __init__.py 中又沒有明確的其他初始化操作,那么此包下面的模塊是不會(huì)自動(dòng)導(dǎo)入的。
假設(shè)我們有如下文件結(jié)構(gòu):
. └── PA├── __init__.py├── PB1│?? ├── __init__.py│?? └── pb1_m.py├── PB2│?? ├── __init__.py│?? └── pb2_m.py└── wave.pywave.py , pb1_m.py , pb2_m.py 文件中我們均定義了如下函數(shù):
def getName():pass__init__.py 文件內(nèi)容均為空。
我們新建一個(gè) test.py ,內(nèi)容如下:
import sys import PA.wave #1 import PA.PB1 #2 import PA.PB1.pb1_m as m1 #3 import PA.PB2.pb2_m #4 PA.wave.getName() #5 m1.getName() #6 PA.PB2.pb2_m.getName() #7我們運(yùn)行以后,可以看出是成功執(zhí)行成功了,我們?cè)倏纯茨夸浗Y(jié)構(gòu):
. ├── PA │?? ├── __init__.py │?? ├── __init__.pyc │?? ├── PB1 │?? │?? ├── __init__.py │?? │?? ├── __init__.pyc │?? │?? ├── pb1_m.py │?? │?? └── pb1_m.pyc │?? ├── PB2 │?? │?? ├── __init__.py │?? │?? ├── __init__.pyc │?? │?? ├── pb2_m.py │?? │?? └── pb2_m.pyc │?? ├── wave.py │?? └── wave.pyc └── test.py我們來分析一下這個(gè)過程:
- 當(dāng)執(zhí)行#1 后, sys.modules 會(huì)同時(shí)存在 PA 、 PA.wave 兩個(gè)模塊,此時(shí)可以調(diào)用 PA.wave 的任何類或函數(shù)了。但不能調(diào)用 PA.PB1(2) 下的任何模塊。當(dāng)前 Local 中有了 PA 名字。
- 當(dāng)執(zhí)行 #2 后,只是將 PA.PB1 載入內(nèi)存, sys.modules 中會(huì)有 PA 、 PA.wave 、 PA.PB1 三個(gè)模塊,但是 PA.PB1 下的任何模塊都沒有自動(dòng)載入內(nèi)存,此時(shí)如果直接執(zhí)行 PA.PB1.pb1_m.getName() 則會(huì)出錯(cuò),因?yàn)?PA.PB1 中并沒有 pb1_m 。當(dāng)前 Local 中還是只有 PA 名字,并沒有 PA.PB1 名字。
- 當(dāng)執(zhí)行 #3 后,會(huì)將 PA.PB1 下的 pb1_m 載入內(nèi)存, sys.modules 中會(huì)有 PA 、 PA.wave 、 PA.PB1 、 PA.PB1.pb1_m 四個(gè)模塊,此時(shí)可以執(zhí)行 PA.PB1.pb1_m.getName() 了。由于使用了 as ,當(dāng)前 Local 中除了 PA 名字,另外添加了 m1 作為 PA.PB1.pb1_m 的別名。
- 當(dāng)執(zhí)行 #4 后,會(huì)將 PA.PB2 、 PA.PB2.pb2_m 載入內(nèi)存, sys.modules 中會(huì)有 PA 、 PA.wave 、 PA.PB1 、 PA.PB1.pb1_m 、 PA.PB2 、 PA.PB2.pb2_m 六個(gè)模塊。當(dāng)前 Local 中還是只有 PA 、 m1 。
- 下面的 #5 ,#6 , #7 都是可以正確運(yùn)行的。
注:需要注意的問題是如果 PA.PB2.pb2_m 想導(dǎo)入 PA.PB1.pb1_m 、 PA.wave 是可以直接成功的。最好是采用明確的導(dǎo)入路徑,對(duì)于 ../.. 相對(duì)導(dǎo)入路徑還是不推薦使用。
既然我們已經(jīng)知道 pyc 文件的產(chǎn)生,再回到那道賽題,我們嘗試將 pyc 文件反編譯回 python 源碼。我們使用在線的開源工具進(jìn)行嘗試:
部分代碼沒有反編譯成功???我們可以嘗試分析一下,大概意思就是讀取 cipher.txt 那個(gè)文件,將那個(gè)文件內(nèi)容是通過 base64 編碼的,我們的目的是將文件內(nèi)容解碼,然后又已知 key ,通過 encryt 函數(shù)進(jìn)行加密的,我們可以嘗試將代碼補(bǔ)全:
def encryt(key, plain):cipher = ''for i in range(len(plain)):cipher += chr(ord(key[i % len(key)]) ^ ord(plain[i]))return cipherdef getPlainText():plain = ''with open('cipher.txt') as (f):while True:line = f.readline()if line:plain += lineelse:breakreturn plain.decode('base_64')def main():key = 'LordCasser'plain = getPlainText()cipher = encryt(key, plain)with open('xxx.txt', 'w') as (f):f.write(cipher)if __name__ == '__main__':main()結(jié)果如下:
YOU ARE FOOLED THIS IS NOT THAT YOU WANT GO ON DUDE CATCH THAT STEGOSAURUS提示告訴我們用 STEGOSAURUS 工具進(jìn)行隱寫的,我們直接將隱藏的payload分離出來即可。
python3 stegosaurus.py -x QAQ.pyc我們得到了最終的 flag 為:flag{fin4lly_z3r0_d34d}
既然都說到這個(gè)份子上了,我們就來分析一下我們是如何通過 Stegosaurus 來嵌入 Payload 。
我們?nèi)匀灰陨厦孢@個(gè)代碼為例子,我們?cè)O(shè)置腳本名稱為 encode.py 。
第一步,我們使用 Stegosaurus 來查看在不改變?cè)次募?(Carrier) 大小的情況下,我們的 Payload 能攜帶多少字節(jié)的數(shù)據(jù):
python3 -m stegosaurus encode.py -r現(xiàn)在,我們可以安全地嵌入最多24個(gè)字節(jié)的 Payload 了。如果不想覆蓋源文件的話,我們可以使用 -s 參數(shù)來單獨(dú)生成一個(gè)嵌入了 Payload 的 py 文件:
python3 -m stegosaurus encode.py -s --payload "flag{fin4lly_z3r0_d34d}"現(xiàn)在我們可以用 ls 命令查看磁盤目錄,嵌入了 Payload 的文件( carrier 文件)和原始的字節(jié)碼文件兩者大小是完全相同的:
注:如果沒有使用 -s 參數(shù),那么原始的字節(jié)碼文件將會(huì)被覆蓋。
我們可以通過向 Stegosaurus 傳遞 -x 參數(shù)來提取出 Payload :
python3 -m stegosaurus __pycache__/encode.cpython-36-stegosaurus.pyc -x我們構(gòu)造的 Payload 不一定要是一個(gè) ASCII 字符串, shellcode 也是可以的:
我們重新編寫一個(gè) example.py 模塊,代碼如下:
import sys import os import math def add(a,b):return int(a)+int(b) def sum1(result):return int(result)*3def sum2(result):return int(result)/3def sum3(result):return int(result)-3def main():a = 1b = 2result = add(a,b)print(sum1(result))print(sum2(result))print(sum3(result))if __name__ == "__main__":main()我們讓它攜帶 Payload 為 flag_is_here。
我們可以查看嵌入 Payload 之前和之后的 Python 代碼運(yùn)行情況:
通過 strings 查看 Stegosaurus 嵌入了 Payload 之后的文件輸出情況( payload 并沒有顯示出來):
接下來使用 Python 的 dis 模塊來查看 Stegosaurus 嵌入 Payload 之前和之后的文件字節(jié)碼變化情況:
嵌入payload之前:
#( 11/29/18@ 5:14下午 )( python@Sakura ):~/桌面python3 -m dis example.py 1 0 LOAD_CONST 0 (0)2 LOAD_CONST 1 (None)4 IMPORT_NAME 0 (sys)6 STORE_NAME 0 (sys)2 8 LOAD_CONST 0 (0)10 LOAD_CONST 1 (None)12 IMPORT_NAME 1 (os)14 STORE_NAME 1 (os)3 16 LOAD_CONST 0 (0)18 LOAD_CONST 1 (None)20 IMPORT_NAME 2 (math)22 STORE_NAME 2 (math)4 24 LOAD_CONST 2 (<code object add at 0x7f90479778a0, file "example.py", line 4>)26 LOAD_CONST 3 ('add')28 MAKE_FUNCTION 030 STORE_NAME 3 (add)6 32 LOAD_CONST 4 (<code object sum1 at 0x7f9047977810, file "example.py", line 6>)34 LOAD_CONST 5 ('sum1')36 MAKE_FUNCTION 038 STORE_NAME 4 (sum1)9 40 LOAD_CONST 6 (<code object sum2 at 0x7f9047977ae0, file "example.py", line 9>)42 LOAD_CONST 7 ('sum2')44 MAKE_FUNCTION 046 STORE_NAME 5 (sum2)12 48 LOAD_CONST 8 (<code object sum3 at 0x7f9047977f60, file "example.py", line 12>)50 LOAD_CONST 9 ('sum3')52 MAKE_FUNCTION 054 STORE_NAME 6 (sum3)15 56 LOAD_CONST 10 (<code object main at 0x7f904798c300, file "example.py", line 15>)58 LOAD_CONST 11 ('main')60 MAKE_FUNCTION 062 STORE_NAME 7 (main)23 64 LOAD_NAME 8 (__name__)66 LOAD_CONST 12 ('__main__')68 COMPARE_OP 2 (==)70 POP_JUMP_IF_FALSE 7824 72 LOAD_NAME 7 (main)74 CALL_FUNCTION 076 POP_TOP>> 78 LOAD_CONST 1 (None)80 RETURN_VALUE嵌入 payload 之后:
#( 11/29/18@ 5:31下午 )( python@Sakura ):~/桌面python3 -m dis example.py 1 0 LOAD_CONST 0 (0)2 LOAD_CONST 1 (None)4 IMPORT_NAME 0 (sys)6 STORE_NAME 0 (sys)2 8 LOAD_CONST 0 (0)10 LOAD_CONST 1 (None)12 IMPORT_NAME 1 (os)14 STORE_NAME 1 (os)3 16 LOAD_CONST 0 (0)18 LOAD_CONST 1 (None)20 IMPORT_NAME 2 (math)22 STORE_NAME 2 (math)4 24 LOAD_CONST 2 (<code object add at 0x7f146e7038a0, file "example.py", line 4>)26 LOAD_CONST 3 ('add')28 MAKE_FUNCTION 030 STORE_NAME 3 (add)6 32 LOAD_CONST 4 (<code object sum1 at 0x7f146e703810, file "example.py", line 6>)34 LOAD_CONST 5 ('sum1')36 MAKE_FUNCTION 038 STORE_NAME 4 (sum1)9 40 LOAD_CONST 6 (<code object sum2 at 0x7f146e703ae0, file "example.py", line 9>)42 LOAD_CONST 7 ('sum2')44 MAKE_FUNCTION 046 STORE_NAME 5 (sum2)12 48 LOAD_CONST 8 (<code object sum3 at 0x7f146e703f60, file "example.py", line 12>)50 LOAD_CONST 9 ('sum3')52 MAKE_FUNCTION 054 STORE_NAME 6 (sum3)15 56 LOAD_CONST 10 (<code object main at 0x7f146e718300, file "example.py", line 15>)58 LOAD_CONST 11 ('main')60 MAKE_FUNCTION 062 STORE_NAME 7 (main)23 64 LOAD_NAME 8 (__name__)66 LOAD_CONST 12 ('__main__')68 COMPARE_OP 2 (==)70 POP_JUMP_IF_FALSE 7824 72 LOAD_NAME 7 (main)74 CALL_FUNCTION 076 POP_TOP>> 78 LOAD_CONST 1 (None)80 RETURN_VALUE注: Payload 的發(fā)送和接受方法完全取決于用戶個(gè)人喜好, Stegosaurus 只提供了一種向 Python 字節(jié)碼文件嵌入或提取 Payload 的方法。但是為了保證嵌入之后的代碼文件大小不會(huì)發(fā)生變化,因此 Stegosaurus 所支持嵌入的 Payload 字節(jié)長度十分有限。因此 ,如果你需要嵌入一個(gè)很大的 Payload ,那么你可能要將其分散存儲(chǔ)于多個(gè)字節(jié)碼文件中了。
為了在不改變?cè)次募笮〉那闆r下向其嵌入 Payload ,我們需要識(shí)別出字節(jié)碼中的無效空間( Dead Zone )。這里所謂的無效空間指的是那些即使被修改也不會(huì)改變?cè)?Python 腳本正常行為的那些字節(jié)數(shù)據(jù)。
需要注意的是,我們可以輕而易舉地找出 Python3.6 代碼中的無效空間。 Python 的引用解釋器 CPython 有兩種類型的操作碼:即無參數(shù)的和有參數(shù)的。在版本號(hào)低于 3.5 的 Python 版本中,根據(jù)操作碼是否帶參,字節(jié)碼中的操作指令將需要占用 1 個(gè)字節(jié)或 3 個(gè)字節(jié)。在 Python3.6 中就不一樣了, Python3.6 中所有的指令都占用 2 個(gè)字節(jié),并會(huì)將無參數(shù)指令的第二個(gè)字節(jié)設(shè)置為 0 ,這個(gè)字節(jié)在其運(yùn)行過程中將會(huì)被解釋器忽略。這也就意味著,對(duì)于字節(jié)碼中每一個(gè)不帶參數(shù)的操作指令, Stegosaurus 都可以安全地嵌入長度為 1 個(gè)字節(jié)的 Payload 代碼。
我們可以通過 Stegosaurus 的 -vv 選項(xiàng)來查看 Payload 是如何嵌入到這些無效空間之中的:
#( 11/29/18@10:35下午 )( python@Sakura ):~/桌面python3 -m stegosaurus example.py -s -p "ABCDE" -vv 2018-11-29 22:36:26,795 - stegosaurus - DEBUG - Validated args 2018-11-29 22:36:26,797 - stegosaurus - INFO - Compiled example.py as __pycache__/example.cpython-36.pyc for use as carrier 2018-11-29 22:36:26,797 - stegosaurus - DEBUG - Read header and bytecode from carrier 2018-11-29 22:36:26,798 - stegosaurus - DEBUG - POP_TOP (0) 2018-11-29 22:36:26,798 - stegosaurus - DEBUG - POP_TOP (0) 2018-11-29 22:36:26,798 - stegosaurus - DEBUG - POP_TOP (0) 2018-11-29 22:36:26,798 - stegosaurus - DEBUG - RETURN_VALUE (0) 2018-11-29 22:36:26,798 - stegosaurus - DEBUG - BINARY_SUBTRACT (0) 2018-11-29 22:36:26,798 - stegosaurus - DEBUG - RETURN_VALUE (0) 2018-11-29 22:36:26,798 - stegosaurus - DEBUG - BINARY_TRUE_DIVIDE (0) 2018-11-29 22:36:26,798 - stegosaurus - DEBUG - RETURN_VALUE (0) 2018-11-29 22:36:26,798 - stegosaurus - DEBUG - BINARY_MULTIPLY (0) 2018-11-29 22:36:26,798 - stegosaurus - DEBUG - RETURN_VALUE (0) 2018-11-29 22:36:26,798 - stegosaurus - DEBUG - BINARY_ADD (0) 2018-11-29 22:36:26,798 - stegosaurus - DEBUG - RETURN_VALUE (0) 2018-11-29 22:36:26,798 - stegosaurus - DEBUG - POP_TOP (0) 2018-11-29 22:36:26,798 - stegosaurus - DEBUG - RETURN_VALUE (0) 2018-11-29 22:36:26,798 - stegosaurus - INFO - Found 14 bytes available for payload Payload embedded in carrier 2018-11-29 22:36:26,799 - stegosaurus - DEBUG - POP_TOP (65) ----A 2018-11-29 22:36:26,799 - stegosaurus - DEBUG - POP_TOP (66) ----B 2018-11-29 22:36:26,799 - stegosaurus - DEBUG - POP_TOP (67) ----C 2018-11-29 22:36:26,799 - stegosaurus - DEBUG - RETURN_VALUE (68) ----D 2018-11-29 22:36:26,799 - stegosaurus - DEBUG - BINARY_SUBTRACT (69) ----E 2018-11-29 22:36:26,799 - stegosaurus - DEBUG - RETURN_VALUE (0) 2018-11-29 22:36:26,799 - stegosaurus - DEBUG - BINARY_TRUE_DIVIDE (0) 2018-11-29 22:36:26,799 - stegosaurus - DEBUG - RETURN_VALUE (0) 2018-11-29 22:36:26,799 - stegosaurus - DEBUG - BINARY_MULTIPLY (0) 2018-11-29 22:36:26,799 - stegosaurus - DEBUG - RETURN_VALUE (0) 2018-11-29 22:36:26,799 - stegosaurus - DEBUG - BINARY_ADD (0) 2018-11-29 22:36:26,799 - stegosaurus - DEBUG - RETURN_VALUE (0) 2018-11-29 22:36:26,799 - stegosaurus - DEBUG - POP_TOP (0) 2018-11-29 22:36:26,799 - stegosaurus - DEBUG - RETURN_VALUE (0) 2018-11-29 22:36:26,799 - stegosaurus - DEBUG - Creating new carrier file name for side-by-side install 2018-11-29 22:36:26,799 - stegosaurus - INFO - Wrote carrier file as __pycache__/example.cpython-36-stegosaurus.pyc參考文獻(xiàn)
- https://bitbucket.org/jherron/stegosaurus/src
- https://github.com/AngelKitty/stegosaurus
- https://www.freebuf.com/sectool/129357.html
轉(zhuǎn)載于:https://www.cnblogs.com/ECJTUACM-873284962/p/10041534.html
總結(jié)
以上是生活随笔為你收集整理的一文让你完全弄懂Stegosaurus的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ASPxPopupControl出现前一
- 下一篇: 呼叫中心crm系统是什么(呼叫中心crm