Python3 如何反编译EXE
1. 需求分析
只支持通過py2exe和pyinstaller 工具編譯生成的EXE文件
公司內(nèi)部使用Python編寫的代碼,最終需要在發(fā)布前編譯成windows執(zhí)行的.EXE文件,所以今天在網(wǎng)上看到有相關(guān)牛人,github開源寫了一個反編譯代碼程序,可以將Windows EXE文件反編譯處pyc文件,最終再將pyc文件轉(zhuǎn)換成可以編譯查看的py文件,覺得比較牛,今天測試一下,看看效果如何,已經(jīng)整個操作步驟是怎樣的,做一個留存。
2. 環(huán)境描述
兩個測試使用環(huán)境來完成反編譯:
本地主測試是Mac,Python版本3.7.5
一臺Windows10,主要用來安裝16進制編譯器,方便我們來可視化分析,目前這個16進制編譯器010Editor 只能安裝到windows系統(tǒng)下,所以使用了windows10系統(tǒng),主要的作用是在這。
3. 步驟分解
主要的步驟分為以下幾個步驟來完成整個反編譯過程。
這里再確認下我們的最終需求,將EXE可執(zhí)行文件反編譯成最終可視、可編輯的py結(jié)尾的Python代碼文件
有了目標之后,那我們整理具體的操作步驟,并進行細化分解
4. 操作步驟
4.1. 獲取可執(zhí)行EXE文件
我這里準備的EXE可執(zhí)行文件是通過pyinstaller打包工具完成的編譯,軟件包名稱如下:
4.2. 下載反編譯程序包
github下載連接地址:
countercept/python-exe-unpacker
# git clone https://github.com/countercept/python-exe-unpacker.git (venv_3.7.5) CarltonXu@CarltonXus-MacBook-Pro # cd python-exe-unpacker (venv_3.7.5) CarltonXu@CarltonXus-MacBook-Pro # ls -tlr total 160 -rw-r--r-- 1 CarltonXu wheel 35096 Apr 7 20:41 LICENSE -rw-r--r-- 1 CarltonXu wheel 4110 Apr 7 20:41 README.md -rw-r--r-- 1 CarltonXu wheel 12392 Apr 7 20:41 pyinstxtractor.py -rw-r--r-- 1 CarltonXu wheel 15377 Apr 7 20:41 python_exe_unpack.py -rw-r--r-- 1 CarltonXu wheel 97 Apr 7 20:41 requirements.txt drwxr-xr-x 3 CarltonXu wheel 96 Apr 7 20:42 __pycache__4.2.1. 安裝依賴包
在代碼下載后,需要安裝運行代碼所需要的依賴包,執(zhí)行下面指令即可完成安裝
(venv_3.7.5) CarltonXu@CarltonXus-MacBook-Pro # sudo pip3 install -r requirements.txt Password: WARNING: The directory '/Users/CarltonXu/Library/Caches/pip' or its parent directory is not owned or is not writable by the current user. The cache has been disabled. Check the permissions and owner of that directory. If executing pip with sudo, you may want sudo's -H flag. Looking in indexes: http://pypi.douban.com/simple/ Requirement already satisfied: pefile==2017.9.3 in /Users/CarltonXu/workspace/venv_3.7.5/lib/python3.7/site-packages (from -r requirements.txt (line 1)) (2017.9.3) Requirement already satisfied: unpy2exe==0.3 in /Users/CarltonXu/workspace/venv_3.7.5/lib/python3.7/site-packages (from -r requirements.txt (line 2)) (0.3) Collecting uncompyle6==2.11.5Downloading http://pypi.doubanio.com/packages/35/a5/f0b734adba414239e144007904207b2fa2ce3ac0b4c87f4a7b0edcf74c0b/uncompyle6-2.11.5.tar.gz (1.4 MB)|████████████████████████████████| 1.4 MB 2.2 MB/s Requirement already satisfied: xdis==3.5.5 in /Users/CarltonXu/workspace/venv_3.7.5/lib/python3.7/site-packages (from -r requirements.txt (line 4)) (3.5.5) Requirement already satisfied: pycrypto==2.6.1 in /Users/CarltonXu/workspace/venv_3.7.5/lib/python3.7/site-packages (from -r requirements.txt (line 5)) (2.6.1) Requirement already satisfied: configparser==3.5.0 in /Users/CarltonXu/workspace/venv_3.7.5/lib/python3.7/site-packages (from -r requirements.txt (line 6)) (3.5.0) Requirement already satisfied: future in /Users/CarltonXu/workspace/venv_3.7.5/lib/python3.7/site-packages (from pefile==2017.9.3->-r requirements.txt (line 1)) (0.18.2) Collecting spark-parser<1.7.0,>=1.6.1Downloading http://pypi.doubanio.com/packages/f3/4e/a95a1ff543744bfaa33449b301fe74272556db2e852c5c3852517a5024be/spark_parser-1.6.1-py3-none-any.whl (17 kB) Requirement already satisfied: six in /Users/CarltonXu/workspace/venv_3.7.5/lib/python3.7/site-packages (from uncompyle6==2.11.5->-r requirements.txt (line 3)) (1.13.0) Collecting argparseDownloading http://pypi.doubanio.com/packages/f2/94/3af39d34be01a24a6e65433d19e107099374224905f1e0cc6bbe1fd22a2f/argparse-1.4.0-py2.py3-none-any.whl (23 kB) Requirement already satisfied: click in /Users/CarltonXu/workspace/venv_3.7.5/lib/python3.7/site-packages (from spark-parser<1.7.0,>=1.6.1->uncompyle6==2.11.5->-r requirements.txt (line 3)) (7.0) Using legacy 'setup.py install' for uncompyle6, since package 'wheel' is not installed. Installing collected packages: spark-parser, argparse, uncompyle6Attempting uninstall: spark-parserFound existing installation: spark-parser 1.8.9Uninstalling spark-parser-1.8.9:Successfully uninstalled spark-parser-1.8.9Attempting uninstall: uncompyle6Found existing installation: uncompyle6 3.7.4Uninstalling uncompyle6-3.7.4:Successfully uninstalled uncompyle6-3.7.4Running setup.py install for uncompyle6 ... done Successfully installed argparse-1.4.0 spark-parser-1.6.1 uncompyle6-2.11.54.3. 分析EXE文件屬性
這里分析EXE文件屬性其實就是看EXE是否為py2exe及pyinstaller工具打包出來的,其實這個動作在執(zhí)行解包動作的時候會進行precheck動作,如果檢測失敗會終止并提示,我們看下代碼里面怎么做檢測的。
文件路徑:python-exe-unpacker/pyinstxtractor.py
檢測的邏輯,如果是通過pyinstaller打包的,會在EXE文件的添加一個Magic number,這個Magic number就是 b"MEI\014\013\012\013\016" 劃算成16進制就是 “4d45490c0b0a0b0e”
下面我們再通過010Edit工具打開EXE文件,找到最后位置的8個字節(jié)看看16進制顯示的值
最后我們看到代碼解析EXE文件和通過010Editor工具解析,EXE文件是屬于pyinstaller進行打包的,那我們就可以進行后續(xù)操作了
4.4. 執(zhí)行解包操作
解包操作通過下載的python-exe-unpacker說明,執(zhí)行以下指令即可。
# (venv_3.7.5) ? CarltonXu@CarltonXus-MacBook-Pro # python python_exe_unpack.py -i /Users/CarltonXu/Downloads/SMS-Agent.exe [*] On Python 3.7 [*] Processing /Users/CarltonXu/Downloads/SMS-Agent.exe [*] Pyinstaller version: 2.1+ [*] This exe is packed using pyinstaller [*] Unpacking the binary now [*] Python version: 37 [*] Length of package: 17411420 bytes [*] Found 1631 files in CArchive [*] Beginning extraction...please standby [*] Found 754 files in PYZ archive [*] Successfully extracted pyinstaller exe.最終執(zhí)行完成,看到成功后,如果沒有-o指定輸出目錄的話,默認會在當前目錄輸出unpacked/SMS-agent.exe/目錄,此目錄下便是解包后的代碼,有一堆的文件和一個目錄
下面輸出的目錄也就是源代碼目錄,但是目錄下面全是pyc文件,我們還注意到此目錄下還有一個PYZ-00.pyz_extracted文件夾,里面都是引入的依賴庫,當然程序的源代碼在下這個下面,當然也是我們需要反編譯的對象。
4.4. 執(zhí)行反編譯操作
前邊我們看到找到了pyc文件,下面自然就是對它進行解密了。pyc其實是python程序執(zhí)行過程中產(chǎn)生的緩存文件,我們直接運行python代碼時也會看到。對于這種格式的反編譯是比較簡單的,網(wǎng)上有許多工具,甚至還有很多在線工具及開源代碼,我們也可以使用最長用的uncompyle6工具來恢復(fù)py文件,操作試試。
# 現(xiàn)將解壓主目錄下的sms_agent主程序,后綴修改為sms_agent.pyc (venv_3.7.5) ? CarltonXu@CarltonXus-MacBook-Pro [ /tmp ] # cp /tmp/python-exe-unpacker/unpacked/SMS-Agent.exe/sms_agent /tmp/python-exe-unpacker/unpacked/SMS-Agent.exe/sms_agent.pyc # 執(zhí)行反編譯指令 (venv_3.7.5) ? CarltonXu@CarltonXus-MacBook-Pro [ /tmp ] # uncompyle6 /tmp/python-exe-unpacker/unpacked/SMS-Agent.exe/sms_agent.pyc > /tmp/python-exe-unpacker/unpacked/SMS-Agent.exe/sms_agent.py Traceback (most recent call last):File "/Users/CarltonXu/workspace/venv_3.7.5/lib/python3.7/site-packages/xdis/load.py", line 197, in load_module_from_file_objectfloat_version = magic_int2float(magic_int)File "/Users/CarltonXu/workspace/venv_3.7.5/lib/python3.7/site-packages/xdis/magics.py", line 426, in magic_int2floatreturn py_str2float(magicint2version[magic_int]) KeyError: 227During handling of the above exception, another exception occurred:Traceback (most recent call last):File "/Users/CarltonXu/workspace/venv_3.7.5/bin/uncompyle6", line 8, in <module>sys.exit(main_bin())File "/Users/CarltonXu/workspace/venv_3.7.5/lib/python3.7/site-packages/uncompyle6/bin/uncompile.py", line 194, in main_bin**options)File "/Users/CarltonXu/workspace/venv_3.7.5/lib/python3.7/site-packages/uncompyle6/main.py", line 324, in maindo_fragments,File "/Users/CarltonXu/workspace/venv_3.7.5/lib/python3.7/site-packages/uncompyle6/main.py", line 184, in decompile_filefilename, code_objectsFile "/Users/CarltonXu/workspace/venv_3.7.5/lib/python3.7/site-packages/xdis/load.py", line 170, in load_moduleget_code=get_code,File "/Users/CarltonXu/workspace/venv_3.7.5/lib/python3.7/site-packages/xdis/load.py", line 205, in load_module_from_file_object% (ord(magic[0:1]) + 256 * ord(magic[1:2]), filename) ImportError: Unknown magic number 227 in /tmp/python-exe-unpacker/unpacked/SMS-Agent.exe/sms_agent.pyc我們執(zhí)行上面的代碼將pyc轉(zhuǎn)換成py,發(fā)現(xiàn)失敗了,提示是Unknown magic number 227,這個失敗因為pyinstaller工具打包的時候,會將代碼文件的magic number(python的版本及編譯時間)給清除掉,所以反編譯時候需要將magic number添加回去才能識別,magic number我們可以通過解壓主目錄下的struct結(jié)構(gòu)體文件中提取出來(一般是前16個字節(jié),可以對比打包前的源文件),將struct文件體中的前16個字節(jié)提取出來,然后在添加到文件中,然后再執(zhí)行uncompyle6反編譯試試。那struct前16個字節(jié)值如何獲取,我們可以有兩種方式獲取
4.4.1. 獲取struct文件結(jié)構(gòu)體magic number
- 第一種:通過010Editor獲取
對比兩個文件,獲取struct的前16個字節(jié)內(nèi)容
第一張圖struct結(jié)構(gòu)體文件E3之前的16個字節(jié)便是magic number
第二張圖sms_agent主程序文件E3之前為空,所以缺少了16個字節(jié)magic number
第一張圖黃色框里面的內(nèi)容都是16進制的值
第一張圖:struct
第二張圖:sms_agent
- 第二種:通過python獲取
這時候,我們兩種方式都可以獲取struct前16個字節(jié)的magic number值,我們記錄下來,添加到pyc文件中,magic number 2進制值:b’B\r\r\n\x00\x00\x00\x00pyi0\x10\x01\x00\x00’
4.4.2. 將Magic number添加到pyc文件
In [1]: struct_path = "/tmp/python-exe-unpacker/unpacked/SMS-Agent.exe/sms_agent"In [2]: add_magic_num_file_name = "/tmp/python-exe-unpacker/unpacked/SMS-Agent.exe/sms_agent"In [3]: MAGIC_NUMBER = b'B\r\r\n\x00\x00\x00\x00pyi0\x10\x01\x00\x00'In [4]: f = open(add_magic_num_file_name, 'rb')In [5]: f.seek(0) Out[5]: 0In [6]: f.tell() Out[6]: 0In [7]: new_content = f.read()In [8]: new_add_magic_number_file_name = "/tmp/python-exe-unpacker/unpacked/SMS-Agent.exe/sms_agent.pyc"In [9]: n_f = open(new_add_magic_number_file_name, "wb")In [10]: n_f.seek(0) Out[10]: 0In [11]: n_f.tell() Out[11]: 0In [12]: n_f.write(MAGIC_NUMBER + new_content) Out[12]: 10972In [13]: f.close()In [14]: n_f.close()4.4.3. 反編譯新添加magic number的pyc文件
(venv_3.7.5) CarltonXu@CarltonXus-MacBook-Pro [ /tmp ] # python /Users/CarltonXu/workspace/venv_3.7.5/bin/uncompyle6 /tmp/python-exe-unpacker/unpacked/SMS-Agent.exe/sms_agent.pyc > /tmp/python-exe-unpacker/unpacked/SMS-Agent.exe/sms_agent.py執(zhí)行反編譯指令沒有出錯,證明一切正常,查看新生成的sms_agent.py,代碼一切正常。
通過反編譯出來的py文件,我們看到已經(jīng)可以正常看到代碼了,只不過有些中文字符被解析成了Unicode編碼,可以再使用相應(yīng)工具轉(zhuǎn)換即可。這個不影響正常查看,我們反編譯的sms_agent是主目錄的主程序文件,其實還有最重要的,我們需要將PYZ-00.pyz_extracted文件夾下的所有pyc文件都進行反編譯。
這里需要注意下PYZ-00.pyz_extracted目錄下的依賴庫的pyc文件缺少的字節(jié)數(shù)與主程序不同,通過010Editor查看,依賴庫下面的pyc文件不是缺少了16個字節(jié),而是中間少了4個字節(jié),那么,我們只需要把struct頭部的16個字節(jié)覆蓋掉PYZ-00.pyz_extracted目錄下的依賴庫的pyc的前12個字節(jié)。
4.4.5. 反編譯PYZ-00.pyz_extracted依賴庫下的pyc文件
由于依賴庫下的文件較多,這里我寫了一個腳本來自動完成轉(zhuǎn)換,僅供參
# vim: tabstop=4 shiftwidth=4 softtabstop=4 # # Copyright 2019 OneProCloud (Shanghai) Ltd # # Authors: XuXingZhuang xuxingzhuang@oneprocloud.com # # Copyright (c) 2019. This file is confidential and proprietary. # All Rights Reserved, OneProCloud (Shanghai) Ltd(http://www.oneprocloud.com).) #import osMAGIC_HEAD = b'B\r\r\n\x00\x00\x00\x00pyi0\x10\x01\x00\x00' FILES_DIRS = "/Users/CarltonXu/Downloads/SMS-Agent-unpacked/SMS-Agent.exe/PYZ-00.pyz_extracted/" CONVERT_CMD = "/Users/CarltonXu/workspace/venv_3.7.5/bin/python /Users/CarltonXu/workspace/venv_3.7.5/bin/uncompyle6"num = 0 for root, dirs, files in os.walk(FILES_DIRS):print(files)for f_name in files:num += 1print("Execute Number: %s" %num)if not f_name.endswith(".pyc"):continueold_file = os.path.join(root, f_name)new_file = os.path.join(root + "/pyz_workspace/" + f_name)with open(old_file, "rb") as o_f:o_f.seek(12)od_content = o_f.read()with open(new_file, "wb") as n_f:n_f.seek(0)new_content = MAGIC_HEAD + od_contentn_f.write(new_content)GENERA_PY_FILE = os.path.join(root + "/pyz_workspace/" + f_name[0:-4] + ".py")EXEC_CONVERT_CMD = CONVERT_CMD + " %s > %s" %(new_file, GENERA_PY_FILE)exec_ret = os.popen(EXEC_CONVERT_CMD).read()if exec_ret:print("INFO: Execute convert cmd successful.")else:print("[ERROR]: Execute convert cmd filed, please check it.")4.4.6. 整理PYZ-00.pyz_extracted依賴庫下的py文件
由于反編譯完成后的py文件較多,而且沒有相關(guān)目錄,所有的文件名稱都是xxx.xxxx.xxx.py
我們正常的代碼編寫會進行目錄劃分,用于不同的功能,以及程序調(diào)用,所以這里寫了一個腳本,來將py文件進行歸類創(chuàng)建目錄,后續(xù)好進行查看
4.4.7. 整體腳本
# vim: tabstop=4 shiftwidth=4 softtabstop=4 # # Copyright 2019 OneProCloud (Shanghai) Ltd # # Authors: XuXingZhuang xuxingzhuang@oneprocloud.com # # Copyright (c) 2019. This file is confidential and proprietary. # All Rights Reserved, OneProCloud (Shanghai) Ltd(http://www.oneprocloud.com).) #import os import shutilMAGIC_HEAD = b'B\r\r\n\x00\x00\x00\x00pyi0\x10\x01\x00\x00' FILES_DIRS = "/tmp/python-exe-unpacker/unpacked/SMS-Agent.exe/PYZ-00.pyz_extracted" CONVERT_CMD = "/Users/CarltonXu/workspace/venv_3.7.5/bin/python /Users/CarltonXu/workspace/venv_3.7.5/bin/uncompyle6"def order_dirs(dirs):for f in os.listdir(dirs):if f.split(".")[-1].endswith("pyc"):os.popen("rm -rf %s" %(dirs + f)).read()continuemk_dir = f.split(".")[0:-2]if mk_dir:mk_path = dirs + "/".join(mk_dir)mv_file = dirs + fmv_to_path = mk_path + "/"new_file_name = ".".join(f.split(".")[-2:])os.makedirs(mk_path, exist_ok=True)shutil.move(mv_file, mv_to_path)os.renames(mv_to_path + f, mv_to_path + new_file_name)else:continuenum = 0 for root, dirs, files in os.walk(FILES_DIRS):print(files)for f_name in files:num += 1print("Execute Number: %s" %num)if not f_name.endswith(".pyc"):continuenew_dirs = root + "/pyz_workspace/"os.makedirs(new_dirs, exist_ok=True)old_file = os.path.join(root, f_name)new_file = os.path.join(new_dirs + f_name)with open(old_file, "rb") as o_f:o_f.seek(12)od_content = o_f.read()with open(new_file, "wb") as n_f:n_f.seek(0)new_content = MAGIC_HEAD + od_contentn_f.write(new_content)GENERA_PY_FILE = os.path.join(new_dirs + f_name[0:-4] + ".py")EXEC_CONVERT_CMD = CONVERT_CMD + " %s > %s" %(new_file, GENERA_PY_FILE)exec_ret = os.popen(EXEC_CONVERT_CMD).read()if not exec_ret:print("INFO: Execute convert cmd successful.")order_dirs(new_dirs)print("INFO: Order directory successful.")else:print("[ERROR]: Execute convert cmd filed, please check it.")總結(jié)
以上是生活随笔為你收集整理的Python3 如何反编译EXE的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: AGC012 - E: Camel an
- 下一篇: translator什么意思中文_tra