國內關於 Stegosaurus
的介紹少之又少,一般只是單純的工具使用的講解之類的,並且本人在學習過程中也是遇到了很多的問題,基於此種情況下寫下此文,也是爲我逝去的青春時光留個念想吧~
Stegosaurus是什麼?
在瞭解 Stegosaurus
是什麼之前,我們首先需要弄清楚的一個問題是:什麼是隱寫術?
隱寫術,從字面上來理解,隱是隱藏,所以我們從字面上可以知道,隱寫術是一類可以隱藏自己寫的一些東西的方法,可能我們所寫的這些東西是一些比較重要的信息,不想讓別人看到,我們會考慮採取一些辦法去隱藏它,比如對所寫的文件加解密,用一些特殊的紙張(比如紙張遇到水後,上面的字纔會顯示出來)之類的。隱寫術這種手段在日常生活中用的十分廣泛,我相信部分小夥伴們小時候曾經有過寫日記的習慣,寫完的日記可能不想讓爸爸媽媽知道(青春期萌動的內心,咱們都是過來人,都懂這個2333),所以以前常常會買那種上了把鎖的那種日記本,這樣就不怕自己的小祕密被爸爸媽媽知道啦。
事實上,隱寫術是一門關於信息隱藏的技巧與科學,專業一點的講,就是指的是採取一些不讓除預期的接收者之外的任何人知曉信息的傳遞事件或者信息的內容的方法。隱寫術的英文叫做 Steganography
,根據維基百科的解釋,這個英文來源於特里特米烏斯的一本講述密碼學與隱寫術的著作 Steganographia
,該書書名源於希臘語,意爲“隱祕書寫”。(這個不是重點)
所以今天呢,我們要給大家介紹的是隱寫術的其中一個分支(也就是其中一種隱寫的方法),也就是 Stegosaurus
, Stegosaurus
是一款隱寫工具,它允許我們在 Python
字節碼文件( pyc
或 pyo
)中嵌入任意 Payload
。由於編碼密度較低,因此我們嵌入 Payload
的過程既不會改變源代碼的運行行爲,也不會改變源文件的文件大小。 Payload
代碼會被分散嵌入到字節碼之中,所以類似 strings
這樣的代碼工具無法查找到實際的 Payload
。 Python
的 dis
模塊會返回源文件的字節碼,然後我們就可以使用 Stegosaurus
來嵌入 Payload
了。
爲了方便維護,我將此項目移至 Github
上:https://github.com/AngelKitty/stegosaurus
首先講到一個工具,不可避免的,我們需要講解它的用法,我並不會像文檔一樣工整的把用法羅列在一起,如果需要了解更加細節的部分請參考 Github
上的詳細文檔,我會拿一些實際的案例去給大家講解一些常見命令的用法,在後續的文章中,我會大家深入理解 python
反編譯的一些東西。
Stegosaurus
僅支持Python3.6
及其以下版本
拿到一個工具,我們一般會看看它的基本用法:
python3 stegosaurus.py -h
$ python3 -m stegosaurus -h
usage: stegosaurus.py [-h] [-p PAYLOAD] [-r] [-s] [-v] [-x] carrier
positional arguments:
carrier Carrier py, pyc or pyo file
optional arguments:
-h, --help show this help message and exit
-p PAYLOAD, --payload PAYLOAD
Embed 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 side
instead.
-v, --verbose Increase verbosity once per use
-x, --extract Extract payload from carrier file
我們可以看到有很多參數選項,我們就以一道賽題來講解部分參數命令吧~
我們此次要講解的這道題是來自 Bugku
的 QAQ
賽題鏈接如下:
http://ctf.bugku.com/files/447e4b626f2d2481809b8690613c1613/QAQ
http://ctf.bugku.com/files/5c02892cd05a9dcd1c5a34ef22dd9c5e/cipher.txt
首先拿到這道題,用 010Editor
乍一眼看過去,我們可以看到一些特徵信息:
可以判斷這是個跟 python
有關的東西,通過查閱相關資料可以判斷這是個 python
經編譯過後的 pyc
文件。這裏可能很多小夥伴們可能不理解了,什麼是 pyc
文件呢?爲什麼會生成 pyc
文件? pyc
文件又是何時生成的呢?下面我將一一解答這些問題。
簡單來說, pyc
文件就是 Python
的字節碼文件,是個二進制文件。我們都知道 Python
是一種全平臺的解釋性語言,全平臺其實就是 Python
文件在經過解釋器解釋之後(或者稱爲編譯)生成的 pyc
文件可以在多個平臺下運行,這樣同樣也可以隱藏源代碼。其實, Python
是完全面向對象的語言, Python
文件在經過解釋器解釋後生成字節碼對象 PyCodeObject
, pyc
文件可以理解爲是 PyCodeObject
對象的持久化保存方式。而 pyc
文件只有在文件被當成模塊導入時纔會生成。也就是說, Python
解釋器認爲,只有 import
進行的模塊才需要被重用。 生成 pyc
文件的好處顯而易見,當我們多次運行程序時,不需要重新對該模塊進行重新的解釋。主文件一般只需要加載一次,不會被其他模塊導入,所以一般主文件不會生成 pyc
文件。
我們舉個例子來說明這個問題:
爲了方便起見,我們事先創建一個test文件夾作爲此次實驗的測試:
mkdir test && cd test/
假設我們現在有個 test.py
文件,文件內容如下:
def print_test():
print('Hello,Kitty!')
print_test()
我們執行以下命令:
python3 test.py
不用說,想必大家都知道打印出的結果是下面這個:
Hello,Kitty!
我們通過下面命令查看下當前文件夾下有哪些文件:
ls -alh
我們可以發現,並沒有 pyc
文件生成。
‘我們再去創建一個文件爲 import_test.py
文件,文件內容如下:
注:
test.py
和import_test.py
應當放在同一文件夾下
import test
test.print_test()
我們執行以下命令:
python3 import_test.py
結果如下:
Hello,Kitty!
Hello,Kitty!
誒,爲啥會打印出兩句相同的話呢?我們再往下看,我們通過下面命令查看下當前文件夾下有哪些文件:
ls -alh
結果如下:
總用量 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
誒,多了個 __pycache__
文件夾,我們進入文件夾下看看有什麼?
cd __pycache__ && ls
我們可以看到生成了一個 test.cpython-36.pyc
。爲什麼是這樣子呢?
我們可以看到,我們在執行 python3 import_test.py
命令的時候,首先開始執行的是 import test
,即導入 test
模塊,而一個模塊被導入時, PVM(Python Virtual Machine)
會在後臺從一系列路徑中搜索該模塊,其搜索過程如下:
- 在當前目錄下搜索該模塊
- 在環境變量
PYTHONPATH
中指定的路徑列表中依次搜索 - 在
python
安裝路徑中搜索
事實上, PVM
通過變量 sys.path
中包含的路徑來搜索,這個變量裏面包含的路徑列表就是上面提到的這些路徑信息。
模塊的搜索路徑都放在了 sys.path
列表中,如果缺省的 sys.path
中沒有含有自己的模塊或包的路徑,可以動態的加入 (sys.path.apend)
即可。
事實上, Python
中所有加載到內存的模塊都放在 sys.modules
。當 import
一個模塊時首先會在這個列表中查找是否已經加載了此模塊,如果加載了則只是將模塊的名字加入到正在調用 import
的模塊的 Local
名字空間中。如果沒有加載則從 sys.path
目錄中按照模塊名稱查找模塊文件,模塊文件可以是 py
、 pyc
、 pyd
,找到後將模塊載入內存,並加入到 sys.modules
中,並將名稱導入到當前的 Local
名字空間。
可以看出來,一個模塊不會重複載入。多個不同的模塊都可以用 import
引入同一個模塊到自己的 Local
名字空間,其實背後的 PyModuleObject
對象只有一個。
在這裏,我還要說明一個問題,import
只能導入模塊,不能導入模塊中的對象(類、函數、變量等)。例如像上面這個例子,我在 test.py
裏面定義了一個函數 print_test()
,我在另外一個模塊文件 import_test.py
不能直接通過 import test.print_test
將 print_test
導入到本模塊文件中,只能用 import test
進行導入。如果我想只導入特定的類、函數、變量,用 from test import print_test
即可。
既然說到了 import
導入機制,再提一提嵌套導入和 Package
導入。
import
嵌套導入
嵌套,不難理解,就是一個套着一個。小時候我們都玩過俄羅斯套娃吧,俄羅斯套娃就是一個大娃娃裏面套着一個小娃娃,小娃娃裏面還有更小的娃娃,而這個嵌套導入也是同一個意思。假如我們現在有一個模塊,我們想要導入模塊 A
,而模塊 A
中有含有其他模塊需要導入,比如模塊 B
,模塊 B
中又含有模塊 C
,一直這樣延續下去,這種方式我們稱之爲 import
嵌套導入。
對這種嵌套比較容易理解,我們需要注意的一點就是各個模塊的 Local
名字空間是獨立的,所以上面的例子,本模塊 import A
完了後,本模塊只能訪問模塊 A
,不能訪問 B
及其它模塊。雖然模塊 B
已經加載到內存了,如果要訪問,還必須明確在本模塊中導入 import B
。
那如果我們有以下嵌套這種情況,我們該怎麼處理呢?
比如我們現在有個模塊 A
:
# A.py
from B import D
class C:
pass
還有個模塊 B
:
# B.py
from A import C
class D:
pass
我們簡單分析一下程序,如果程序運行,應該會去從模塊B中調用對象D。
我們嘗試執行一下 python A.py
:
報 ImportError
的錯誤,似乎是沒有加載到對象 D
,而我們將 from B import D
改成 import B
,我們似乎就能執行成功了。
這是怎麼回事呢?這其實是跟 Python
內部 import
的機制是有關的,具體到 from B import D
, Python
內部會分成以下幾個步驟:
- 在
sys.modules
中查找符號B
- 如果符號
B
存在,則獲得符號B
對應的module
對象<module B>
。從<module B>
的__dict__
中獲得符號D
對應的對象,如果D
不存在,則拋出異常 - 如果符號
B
不存在,則創建一個新的module
對象<module B>
,注意,此時module
對象的__dict__
爲空。執行B.py
中的表達式,填充<module B>
的__dict__
。從<module B>
的__dict__
中獲得D
對應的對象。如果D
不存在,則拋出異常。
所以,這個例子的執行順序如下:
1、執行 A.py
中的 from B import D
注:由於是執行的
python A.py
,所以在sys.modules
中並沒有<module B>
存在,首先爲B.py
創建一個module
對象(<module B>
),注意,這時創建的這個module
對象是空的,裏邊啥也沒有,在Python
內部創建了這個module
對象之後,就會解析執行B.py
,其目的是填充<module B>
這個dict
。
2、執行 B.py
中的 from A import C
注:在執行
B.py
的過程中,會碰到這一句,首先檢查sys.modules
這個module
緩存中是否已經存在<module A>
了,由於這時緩存還沒有緩存<module A>
,所以類似的,Python
內部會爲A.py
創建一個module
對象(<module A>
),然後,同樣地,執行A.py
中的語句。
3、再次執行 A.py
中的 from B import D
注:這時,由於在第
1
步時,創建的<module B>
對象已經緩存在了sys.modules
中,所以直接就得到了<module B>
,但是,注意,從整個過程來看,我們知道,這時<module B>
還是一個空的對象,裏面啥也沒有,所以從這個module
中獲得符號D
的操作就會拋出異常。如果這裏只是import B
,由於B
這個符號在sys.modules
中已經存在,所以是不會拋出異常的。
我們可以從下圖很清楚的看到 import
嵌套導入的過程:
Package
導入
包 (Package)
可以看成模塊的集合,只要一個文件夾下面有個 __init__.py
文件,那麼這個文件夾就可以看做是一個包。包下面的文件夾還可以成爲包(子包)。更進一步的講,多個較小的包可以聚合成一個較大的包。通過包這種結構,我們可以很方便的進行類的管理和維護,也方便了用戶的使用。比如 SQLAlchemy
等都是以包的形式發佈給用戶的。
包和模塊其實是很類似的東西,如果查看包的類型: import SQLAlchemy type(SQLAlchemy)
,可以看到其實也是 <type 'module'>
。 import
包的時候查找的路徑也是 sys.path
。
包導入的過程和模塊的基本一致,只是導入包的時候會執行此包目錄下的 __init__.py
,而不是模塊裏面的語句了。另外,如果只是單純的導入包,而包的 __init__.py
中又沒有明確的其他初始化操作,那麼此包下面的模塊是不會自動導入的。
假設我們有如下文件結構:
.
└── PA
├── __init__.py
├── PB1
│ ├── __init__.py
│ └── pb1_m.py
├── PB2
│ ├── __init__.py
│ └── pb2_m.py
└── wave.py
wave.py
, pb1_m.py
, pb2_m.py
文件中我們均定義瞭如下函數:
def getName():
pass
__init__.py
文件內容均爲空。
我們新建一個 test.py
,內容如下:
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
我們運行以後,可以看出是成功執行成功了,我們再看看目錄結構:
.
├── 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
我們來分析一下這個過程:
- 當執行
#1
後,sys.modules
會同時存在PA
、PA.wave
兩個模塊,此時可以調用PA.wave
的任何類或函數了。但不能調用PA.PB1(2)
下的任何模塊。當前Local
中有了PA
名字。 - 當執行
#2
後,只是將PA.PB1
載入內存,sys.modules
中會有PA
、PA.wave
、PA.PB1
三個模塊,但是PA.PB1
下的任何模塊都沒有自動載入內存,此時如果直接執行PA.PB1.pb1_m.getName()
則會出錯,因爲PA.PB1
中並沒有pb1_m
。當前Local
中還是隻有PA
名字,並沒有PA.PB1
名字。 - 當執行
#3
後,會將PA.PB1
下的pb1_m
載入內存,sys.modules
中會有PA
、PA.wave
、PA.PB1
、PA.PB1.pb1_m
四個模塊,此時可以執行PA.PB1.pb1_m.getName()
了。由於使用了as
,當前Local
中除了PA
名字,另外添加了m1
作爲PA.PB1.pb1_m
的別名。 - 當執行
#4
後,會將PA.PB2
、PA.PB2.pb2_m
載入內存,sys.modules
中會有PA
、PA.wave
、PA.PB1
、PA.PB1.pb1_m
、PA.PB2
、PA.PB2.pb2_m
六個模塊。當前Local
中還是隻有PA
、m1
。 - 下面的
#5
,#6
,#7
都是可以正確運行的。
注:需要注意的問題是如果
PA.PB2.pb2_m
想導入PA.PB1.pb1_m
、PA.wave
是可以直接成功的。最好是採用明確的導入路徑,對於../..
相對導入路徑還是不推薦使用。
既然我們已經知道 pyc
文件的產生,再回到那道賽題,我們嘗試將 pyc
文件反編譯回 python
源碼。我們使用在線的開源工具進行嘗試:
部分代碼沒有反編譯成功???我們可以嘗試分析一下,大概意思就是讀取 cipher.txt
那個文件,將那個文件內容是通過 base64
編碼的,我們的目的是將文件內容解碼,然後又已知 key
,通過 encryt
函數進行加密的,我們可以嘗試將代碼補全:
def encryt(key, plain):
cipher = ''
for i in range(len(plain)):
cipher += chr(ord(key[i % len(key)]) ^ ord(plain[i]))
return cipher
def getPlainText():
plain = ''
with open('cipher.txt') as (f):
while True:
line = f.readline()
if line:
plain += line
else:
break
return 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()
結果如下:
YOU ARE FOOLED
THIS IS NOT THAT YOU WANT
GO ON DUDE
CATCH THAT STEGOSAURUS
提示告訴我們用 STEGOSAURUS
工具進行隱寫的,我們直接將隱藏的payload分離出來即可。
python3 stegosaurus.py -x QAQ.pyc
我們得到了最終的 flag
爲:flag{fin4lly_z3r0_d34d}
既然都說到這個份子上了,我們就來分析一下我們是如何通過 Stegosaurus
來嵌入 Payload
。
我們仍然以上面這個代碼爲例子,我們設置腳本名稱爲 encode.py
。
第一步,我們使用 Stegosaurus
來查看在不改變源文件 (Carrier)
大小的情況下,我們的 Payload
能攜帶多少字節的數據:
python3 -m stegosaurus encode.py -r
現在,我們可以安全地嵌入最多24個字節的 Payload
了。如果不想覆蓋源文件的話,我們可以使用 -s
參數來單獨生成一個嵌入了 Payload
的 py
文件:
python3 -m stegosaurus encode.py -s --payload "flag{fin4lly_z3r0_d34d}"
現在我們可以用 ls
命令查看磁盤目錄,嵌入了 Payload
的文件( carrier
文件)和原始的字節碼文件兩者大小是完全相同的:
注:如果沒有使用
-s
參數,那麼原始的字節碼文件將會被覆蓋。
我們可以通過向 Stegosaurus
傳遞 -x
參數來提取出 Payload
:
python3 -m stegosaurus __pycache__/encode.cpython-36-stegosaurus.pyc -x
我們構造的 Payload
不一定要是一個 ASCII
字符串, shellcode
也是可以的:
我們重新編寫一個 example.py
模塊,代碼如下:
import sys
import os
import math
def add(a,b):
return int(a)+int(b)
def sum1(result):
return int(result)*3
def sum2(result):
return int(result)/3
def sum3(result):
return int(result)-3
def main():
a = 1
b = 2
result = add(a,b)
print(sum1(result))
print(sum2(result))
print(sum3(result))
if __name__ == "__main__":
main()
我們讓它攜帶 Payload
爲 flag_is_here
。
我們可以查看嵌入 Payload
之前和之後的 Python
代碼運行情況:
通過 strings
查看 Stegosaurus
嵌入了 Payload
之後的文件輸出情況( payload
並沒有顯示出來):
接下來使用 Python
的 dis
模塊來查看 Stegosaurus
嵌入 Payload
之前和之後的文件字節碼變化情況:
嵌入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 0
30 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 0
38 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 0
46 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 0
54 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 0
62 STORE_NAME 7 (main)
23 64 LOAD_NAME 8 (__name__)
66 LOAD_CONST 12 ('__main__')
68 COMPARE_OP 2 (==)
70 POP_JUMP_IF_FALSE 78
24 72 LOAD_NAME 7 (main)
74 CALL_FUNCTION 0
76 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 0
30 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 0
38 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 0
46 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 0
54 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 0
62 STORE_NAME 7 (main)
23 64 LOAD_NAME 8 (__name__)
66 LOAD_CONST 12 ('__main__')
68 COMPARE_OP 2 (==)
70 POP_JUMP_IF_FALSE 78
24 72 LOAD_NAME 7 (main)
74 CALL_FUNCTION 0
76 POP_TOP
>> 78 LOAD_CONST 1 (None)
80 RETURN_VALUE
注:
Payload
的發送和接受方法完全取決於用戶個人喜好,Stegosaurus
只提供了一種向Python
字節碼文件嵌入或提取Payload
的方法。但是爲了保證嵌入之後的代碼文件大小不會發生變化,因此Stegosaurus
所支持嵌入的Payload
字節長度十分有限。因此 ,如果你需要嵌入一個很大的Payload
,那麼你可能要將其分散存儲於多個字節碼文件中了。
爲了在不改變源文件大小的情況下向其嵌入 Payload
,我們需要識別出字節碼中的無效空間( Dead Zone
)。這裏所謂的無效空間指的是那些即使被修改也不會改變原 Python
腳本正常行爲的那些字節數據。
需要注意的是,我們可以輕而易舉地找出 Python3.6
代碼中的無效空間。 Python
的引用解釋器 CPython
有兩種類型的操作碼:即無參數的和有參數的。在版本號低於 3.5
的 Python
版本中,根據操作碼是否帶參,字節碼中的操作指令將需要佔用 1
個字節或 3
個字節。在 Python3.6
中就不一樣了, Python3.6
中所有的指令都佔用 2
個字節,並會將無參數指令的第二個字節設置爲 0
,這個字節在其運行過程中將會被解釋器忽略。這也就意味着,對於字節碼中每一個不帶參數的操作指令, Stegosaurus
都可以安全地嵌入長度爲 1
個字節的 Payload
代碼。
我們可以通過 Stegosaurus
的 -vv
選項來查看 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
參考文獻
- https://bitbucket.org/jherron/stegosaurus/src
- https://github.com/AngelKitty/stegosaurus
- https://www.freebuf.com/sectool/129357.html