python逆向之pyc反編譯的使用教程
前言:
今天碰到個(gè)程序是用python編寫,然后編譯成exe程序,有點(diǎn)興趣就拿來研究了一下,下面記錄下分析的過程。
python代碼的運(yùn)行是靠python解析器將源代碼轉(zhuǎn)換為字節(jié)碼(.pyc),然后把編譯好的字節(jié)碼轉(zhuǎn)發(fā)到Python虛擬機(jī)(PVM)中進(jìn)行執(zhí)行,那么python程序是如何打包成為exe程序來執(zhí)行的那,這里面有二種方法可以將python轉(zhuǎn)換為exe程序執(zhí)行。
第一種就是將python程序轉(zhuǎn)換為c/c++代碼后,然后編譯成為exe程序,但是這種方法會(huì)有很多限制且容易出bug,并不好用;
第二種就是首先將python所需要的所有庫代碼編碼為pyd文件并拷貝到對(duì)應(yīng)的目錄中,然后按照PE格式制造一個(gè)可執(zhí)行文件,包含了windows自帶的運(yùn)行dll庫和PythonXX.dll(解析器庫),入口點(diǎn)為python解析器,就是將整個(gè)python所需要的全部壓縮進(jìn)了一個(gè)exe程序中來進(jìn)行執(zhí)行,需要的庫通過pyd調(diào)用。
python編譯:
為了兼容性或者代碼的保護(hù),我們會(huì)采用將python程序編碼為exe程序來進(jìn)行運(yùn)行,使用工具一般采用PyInstaller工具進(jìn)行轉(zhuǎn)換。這里先介紹PyInstaller是如何將python程序轉(zhuǎn)換為exe程序的:
安裝:
下載地址: https://github.com/pyinstaller/pyinstaller 或者直接執(zhí)行命令: pip install PyInstaller
支持版本為 Python version 3.7-3.11 ,并且支持PyQt5, PySide2, PyQt6, PySide6, wxPython, matplotlib and others out-of-the-box的捆綁:
注意:Python 3.10.0 包含一個(gè)錯(cuò)誤,使得 PyInstaller 不支持它。PyInstaller也無法與Python 3.12的beta版本一起使用
支持操作系統(tǒng)為win7以及以上版本。
打包:
首先看下PyInstaller的常用命令:
picture.ico為圖標(biāo): PyInstaller -F -i picture.ico -n noPac.exe noPac.py 打包成獨(dú)立exe: PyInstaller -F --version-file ver.txt noPac.py # 多文件 pyinstaller -D noPac.py # 單個(gè)可執(zhí)行文件 pyinstaller -F noPac.py 加密打包exe(加密只針對(duì)依賴庫): 但是要安裝tinyaes:pip install tinyaes pyinstaller -F --key 123456 xxx.py
我這里使用一個(gè)連接mysql的python代碼進(jìn)行測(cè)試:
包含兩個(gè)py文件:demo.py和mysql_client_tools.py
執(zhí)行:PyInstaller -F -i icon.ico -n demo.exe demo.py
執(zhí)行成功后會(huì)在dist目錄下生成exe文件,并在build目錄生成build文件
當(dāng)提示缺少庫的時(shí)候可以在PyCharm中查看:
然后使用命令:
pyinstaller -F -p D:\code\Work_Scan\venv\Lib\site-packages main.py
加密exe可以使用:pyinstaller -F --key 123456 demo.py
python逆向:
前期分析:
拿到一個(gè)程序我們首先要去分析這個(gè)程序到底是用什么語言寫的,然后才能對(duì)癥下藥,既然是exe程序,我們使用die進(jìn)行分析:
我這里測(cè)試的就是上面生成的加密后的exe,可以看到分析結(jié)果,使用的是PyInstaller進(jìn)行的打包,且語言為python。
解包:
之后我們要對(duì)exe進(jìn)行解包處理,這里我們要使用工具pyinstxtractor:
pyinstxtractor下載地址:
https://github.com/extremecoders-re/pyinstxtractor
執(zhí)行:
python pyinstxtractor.py demo_key.exe
這里需要注意使用的什么版本的python編譯的exe,就要用什么版本的python進(jìn)行解包,不然會(huì)報(bào)錯(cuò)
解包完成后可以看到生成了demo_key.exe_extracted文件夾,里面就是解包后的文件,需要特別注意的幾個(gè)文件:
main.pyc 主函數(shù)
pyimod00_crypto_key.pyc 這里面保存有加密key
PYZ-00.pyz_extracted 文件夾里面為依賴庫
如果PYZ-00.pyz_extracted里面的文件是pyc.encrypted,則證明為加密文件,如果不是則為加密。
解密:
當(dāng)發(fā)現(xiàn)存在pyc.encrypted,則進(jìn)行解密,如果不是則跳過這一步:
使用010editor打開pyimod00_crypto_key.pyc,注意看我們解包的python的dll版本,這里就要注意header頭的版本一定要一致,不然會(huì)報(bào)錯(cuò)
這里列出各個(gè)版本的python頭:
Python 2.7: \x03\xf3\x0d\x0a\0\0\0\0 Python 3.0: \x3b\x0c\x0d\x0a\0\0\0\0 Python 3.1: \x4f\x0c\x0d\x0a\0\0\0\0 Python 3.2: \x6c\x0c\x0d\x0a\0\0\0\0 Python 3.3: \x9e\x0c\x0d\x0a\0\0\0\0\0\0\0\0 Python 3.4: \xee\x0c\x0d\x0a\0\0\0\0\0\0\0\0 Python 3.5: \x17\x0d\x0d\x0a\0\0\0\0\0\0\0\0 Python 3.6: \x33\x0d\x0d\x0a\0\0\0\0\0\0\0\0 Python 3.7: \x42\x0d\x0d\x0a\0\0\0\0\0\0\0\0\0\0\0\0 Python 3.8: \x55\x0d\x0d\x0a\0\0\0\0\0\0\0\0\0\0\0\0 Python 3.9: \x61\x0d\x0d\x0a\0\0\0\0\0\0\0\0\0\0\0\0 Python 3.10: \x6f\x0d\x0d\x0a\0\0\0\0\0\0\0\0\0\0\0\0
使用uncompyle6反編譯:
pip install uncompyle6 注意uncompyle6有版本限制為1.4, 2.1-2.7, and 3.0-3.8
執(zhí)行如下命令:
uncompyle6 -o pyimod00_crypto_key.py pyimod00_crypto_key.pyc uncompyle6 -o demo.py demo.pyc cat pyimod00_crypto_key.py
可以看到解密出來的key為0000000000123456
因?yàn)槭褂玫氖茿ES加密,使用如下代碼進(jìn)行解密,這里要注意看PyInstaller用的是什么版本,如果是>=4.0使用的是tinyaes,而且使用的算法也不一樣
Pyinstaller < 4.0 => PyCrypto and CFB
Pyinstaller >= 4.0 => tinyaes and CTR
當(dāng)pyinstaller < 4.0 使用如下:
# For pyinstaller < 4.0 import glob import zlib from Crypto.Cipher import AES from pathlib import Path CRYPT_BLOCK_SIZE = 16 # key obtained from pyimod00_crypto_key key = bytes('MySup3rS3cr3tK3y', 'utf-8') for p in Path("PYZ-00.pyz_extracted").glob("**/*.pyc.encrypted"): inf = open(p, 'rb') # encrypted file input outf = open(p.with_name(p.stem), 'wb') # output file # Initialization vector iv = inf.read(CRYPT_BLOCK_SIZE) cipher = AES.new(key, AES.MODE_CFB, iv) # Decrypt and decompress plaintext = zlib.decompress(cipher.decrypt(inf.read())) # Write pyc header # The header below is for Python 3.8 outf.write(b'\x55\x0d\x0d\x0a\0\0\0\0\0\0\0\0\0\0\0\0') # Write decrypted data outf.write(plaintext) inf.close() outf.close() # Delete .pyc.encrypted file p.unlink()
當(dāng)使用版本>= 4.0使用如下代碼:
# For pyinstaller >=4.0 import glob import zlib import tinyaes from pathlib import Path CRYPT_BLOCK_SIZE = 16 # key obtained from pyimod00_crypto_key key = bytes('MySup3rS3cr3tK3y', 'utf-8') for p in Path("PYZ-00.pyz_extracted").glob("**/*.pyc.encrypted"): inf = open(p, 'rb') # encrypted file input outf = open(p.with_name(p.stem), 'wb') # output file # Initialization vector iv = inf.read(CRYPT_BLOCK_SIZE) cipher = tinyaes.AES(key, iv) # Decrypt and decompress plaintext = zlib.decompress(cipher.CTR_xcrypt_buffer(inf.read())) # Write pyc header # The header below is for Python 3.8 outf.write(b'\x55\x0d\x0d\x0a\0\0\0\0\0\0\0\0\0\0\0\0') # Write decrypted data outf.write(plaintext) inf.close() outf.close() # Delete .pyc.encrypted file p.unlink()
但是要如何判單使用的是什么版本,我們可以反編譯 pyimod01_archive.pyc,看里面是否引用了tinyaes,這里可以看到,引用了tinyaes
則使用下面的版本。
反編譯:
其實(shí)上面也講過了,使用uncompyle6進(jìn)行反編譯,這里可以使用我上面的腳本進(jìn)行批量的反編譯,但是當(dāng)反匯編的為公共庫的時(shí)候會(huì)失敗,所以也可以選擇uncompyle6進(jìn)行指定反匯編:
我這里直接全部反編譯,可以看到可以將源代碼反編譯出來
但是可以看看其他反編譯的文件可以看到有些沒成功會(huì)報(bào)錯(cuò)比如如下的
這就是加了混淆,具體的怎么解混淆就要看情況了,后面有時(shí)間在寫
總結(jié):
過程其實(shí)很簡(jiǎn)單,主要使用pyinstxtractor.py和uncompyle6兩個(gè)工具,一個(gè)是解包,一個(gè)是反編譯,中間有當(dāng)文件加密,可以使用解密工具進(jìn)行解密,之后使用uncompyle6反編譯,過程不難,用這樣的方法可以很簡(jiǎn)單的反編譯出源代碼,所以為了對(duì)抗破解,會(huì)進(jìn)行混淆等操作,這個(gè)后續(xù)進(jìn)行介紹。
到此這篇關(guān)于python逆向之pyc反編譯的使用教程的文章就介紹到這了,更多相關(guān)python pyc反編譯內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python numpy 點(diǎn)數(shù)組去重的實(shí)例
下面小編就為大家分享一篇Python numpy 點(diǎn)數(shù)組去重的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-04-04pytorch如何定義新的自動(dòng)求導(dǎo)函數(shù)
這篇文章主要介紹了pytorch如何定義新的自動(dòng)求導(dǎo)函數(shù)問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。2022-12-12python生成器generator:深度學(xué)習(xí)讀取batch圖片的操作
這篇文章主要介紹了python生成器generator:深度學(xué)習(xí)讀取batch圖片的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-05-05簡(jiǎn)單了解django索引的相關(guān)知識(shí)
這篇文章主要介紹了簡(jiǎn)單了解django索引的相關(guān)知識(shí),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-07-07linux centos 7.x 安裝 python3.x 替換 python2.x的過程解析
這篇文章主要介紹了linux centos 7.x 安裝 python3.x 替換 python2.x的過程解析,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12Python函數(shù)的作用域及內(nèi)置函數(shù)詳解
這篇文章主要介紹了python函數(shù)的作用域及內(nèi)置函數(shù)詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2021-09-09flask之郵件發(fā)送的實(shí)現(xiàn)示例
Flask-Mail是一個(gè)處理電子郵件發(fā)送的擴(kuò)展,它提供了簡(jiǎn)單且易于使用的API,可以方便地發(fā)送電子郵件,本文就來介紹一下flask之郵件發(fā)送的實(shí)現(xiàn)示例,感興趣的可以了解一下2023-12-12通過python調(diào)用adb命令對(duì)App進(jìn)行性能測(cè)試方式
這篇文章主要介紹了通過python調(diào)用adb命令對(duì)App進(jìn)行性能測(cè)試方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-04-04