如何解包 Python 惡意可執(zhí)行文件
使用 Python 編寫的程序通常以源碼的形式發(fā)布,也可以將所有依賴都打包到一個可執(zhí)行文件中。那么如何解包 Python 惡意可執(zhí)行文件呢?
打包
打包與加殼不同,打包 Python 程序的目的是創(chuàng)建一個可以在操作系統(tǒng)上獨立運行的可執(zhí)行文件。使用例如 PyInstaller 這類打包工具時,會執(zhí)行以下操作:
- 將所有.py 源文件編譯為 python 字節(jié)碼文件(.pyc文件)
- 整合所有 python 編譯的源代碼和 python 依賴,包括依賴于操作系統(tǒng)的 python 解釋器(Linux 上的 libpython3.9.so.1.0 或 Windows 上的 python37.dll)
- 將所有資源都打包在一起,執(zhí)行時將這些資源都解壓到內(nèi)存中,然后使用解釋器解釋執(zhí)行原始 python 代碼
以 Windows 平臺為例,將示例代碼使用 PyInstaller 進行打包:
示例代碼
直接運行腳本文件是很容易的:
運行情況
打包如下所示:
打包情況
打包得到的 evil_program.exe可以直接運行,可得到與腳本相同的運行結(jié)果。此時,程序可以移動到另一個完全沒有 Python 環(huán)境的機器上一樣可以執(zhí)行。
運行情況
在 Windows 上解包 Python<3.9
如果樣本中可以看到很多以 py 開頭的字符串,基本可以斷定是一個打包的 Python 程序。
字符串
對于 PyInstaller 來說,還有獨特字符串 MEIPASS。
監(jiān)控運行程序時在臨時文件夾中創(chuàng)建了哪些文件,就能夠知道使用的 Python 版本。例如 python38.dll 意味著使用的是 Python 3.8 版本。
監(jiān)控文件系統(tǒng)
想要恢復源代碼,必須要保證:
- 解壓得到所有文件,特別是編譯后的字節(jié)碼文件(.pyc)
- 反編譯感興趣的 .pyc 文件
解壓可以使用 pyinstxtractor,使用相同版本的 Python 即可解壓得到文件:
解壓文件
pyinstxtractor 還額外提供了哪些是 Python 程序文件的信息,盡管存在誤報但是也有很大的幫助??梢钥吹街魑募? evil_program.pyc:
提取文件
接著使用 uncompyle6進行反編譯,值得注意的是 uncompyle6 只支持到 Python 3.8。
反編譯
在 Linux 上解包 Python>=3.9
再次使用 pyinstxtractor 解壓樣本文件。由于樣本是 64 位 ELF 文件,不能直接使用 pyinstxtractor 需要將文件的 pydata 轉(zhuǎn)存到一個單獨的文件再執(zhí)行 pyinstxtractor。
轉(zhuǎn)存文件
解包成功如下所示,也需要配合相應的 Python 版本:
解壓文件
解壓后的數(shù)據(jù)中有一個 RansomWare.pyc,就是需要關注的內(nèi)容。由于使用 Python 3.9,需要 Decompyle++進行反編譯。該工具美中不足的就是沒有文檔講解如何進行構(gòu)建,如下所示:
構(gòu)建反編譯工具
如果想要從任何地方調(diào)用 pycdc 命令,也可以運行 sudo make install。
pycdc 是反編譯命令,使用它來恢復 RansomWare.pyc 的源代碼:
反編譯
當然,某些情況下也會遇到反編譯失敗的問題。但大部分代碼還是可以正常進行反編譯的:
反編譯失敗
反編譯失敗時,可以使用 pycdas 命令來恢復“反匯編”的字節(jié)碼。
write_key() 函數(shù)的字節(jié)碼反匯編
結(jié)論
本文介紹了如何解壓和反編譯使用 PyInstaller 打包的 Python 程序,在應對使用 Python 編寫的惡意軟件時可以在源碼級進行分析,可以大大提高效率。