技术标签: python
大家好,我是小小明,今天我将教大家如何反编译exe文件。
这次以最近写的一篇gui《Python一键自动整理归类文件,GUI窗口程序拿来即用》为例进行演示。
地址:https://blog.csdn.net/as604049322/article/details/119619221
打包成单文件所使用的命令为:
pyinstaller -Fw --icon=h.ico auto_organize_gui.py --add-data="h.ico;/"
打包成文件夹所使用的命令为:
pyinstaller -w --icon=h.ico auto_organize_gui.py --add-data="h.ico;."
不管是哪种打包方式都会留下一个exe文件。
提取pyc文件有两种方法:
pyinstxtractor.py 脚本可以在github项目python-exe-unpacker中下载,地址:
https://github.com/countercept/python-exe-unpacker
下载该项目后把其中的pyinstxtractor.py
脚本文件复制到与exe同级的目录。
然后进入exe所在目录的cmd执行:
python pyinstxtractor.py auto_organize_gui.exe
执行后便得到exe文件名加上_extracted
后缀的文件夹:
对两种打包方式产生的exe提取出的文件结构稍有区别:
pyi-archive_viewer是PyInstaller自己提供的工具,它可以直接提取打包结果exe中的pyc文件。
详细介绍可参考官方文档:
https://pyinstaller.readthedocs.io/en/stable/advanced-topics.html#using-pyi-archive-viewer
执行pyi-archive_viewer [filename]
即可查看 exe 内部的文件结构:
pyi-archive_viewer auto_organize.exe
操作命令:
U: go Up one level
O <name>: open embedded archive name
X <name>: extract name
Q: quit
然后可以提取出指定需要提取的文件:
要提取其他被导入的pyc文件,则需要先打开PYZ-00.pyz
:
很显然,使用PyInstaller的pyi-archive_viewer 工具操作起来比较麻烦,一次只能提取一个文件,遇到子模块还需执行一次打开操作。所以后面我也只使用pyinstxtractor.py 脚本来提取pyc文件。
有很多对pyc文件进行解密的网站,例如:
不过我们直接使用 uncompyle6
库进行解码,使用pip可以直接安装:
pip install uncompyle6
uncompyle6可以反编译.pyc后缀结尾的文件,两种命令形式:
uncompyle6 xxx.pyc>xxx.py
uncompyle6 -o xxx.py xxx.pyc
以前面编码过程中生成的缓存为例进行演示:
uncompyle6 auto_organize.cpython-37.pyc>auto_organize.py
执行后便直接将.pyc文件反编译成python脚本了:
从编译结果看注释也被保留了下来:
对于不是pyc后缀结尾的文件,使用uncompyle6反编译时会报出must point to a Python source that can be compiled, or Python bytecode (.pyc, .pyo)
的错误。
所以我们需要先对提取出的内容人工修改后缀:
对于从pyinstaller提取出来的pyc文件并不能直接反编译,入口运行类共16字节的 magic
和 时间戳
被去掉了。
如果直接进行反编译,例如执行uncompyle6 auto_organize_gui.exe_extracted/auto_organize_gui.pyc
会报出如下错误:ImportError: Unknown magic number 227 in auto_organize_gui.exe_extracted\auto_organize_gui.pyc
使用支持16进制编辑的文本编辑器查看一探究竟,这里我使用UltraEdit32
:
分别打开正常情况下编译出的pyc和从pyinstaller提取出来的pyc文件进行对比:
可以看到前16个字节都被去掉了,其中前四个字节是magic
,这四个字节会随着系统和python版本发生变化,必须一致。后四个字节包括时间戳和一些其他的信息,都可以随意填写。
我们先通过UltraEdit32
向pyinstaller提取的文件添加头信息:
选择开头插入16个字节后,只需要替换前4个字节为当前环境下的magic:
然后执行:
uncompyle6 auto_organize_gui.exe_extracted/auto_organize_gui.pyc>auto_organize_gui.py
执行后可以看到文件已经顺利的被反编译:
考虑再反编译导入的其他依赖文件:
先用UltraEdit32
打开查看一下:
可以看到对于非入口运行的pyc文件是从12字节开始缺4个字节。
这里我们选择第13个字节再插入四个字节即可:
然后再执行:
uncompyle6 auto_organize_gui.exe_extracted/PYZ-00.pyz_extracted/auto_organize.pyc > auto_organize.py
然后成功的反编译出依赖的文件:
代码与原文件几乎完全一致:
如果一个exe需要被反编译的python脚本只有3个以内的文件,我们都完全可以人工来操作。但是假如一个exe涉及几十个甚至上百个python脚本需要反编译的时候,人工操作未免工作量过于巨大,我们考虑将以上过程用python实现,从而达到批量反编译的效果。
import os
import sys
import pyinstxtractor
exe_file = r"D:/PycharmProjects/gui_project/dist/auto_organize_gui.exe"
sys.argv = ['pyinstxtractor', exe_file]
pyinstxtractor.main()
# 恢复当前目录位置
os.chdir("..")
[*] Processing D:/PycharmProjects/gui_project/dist/auto_organize_gui.exe
[*] Pyinstaller version: 2.1+
[*] Python version: 37
[*] Length of package: 9491710 bytes
[*] Found 984 files in CArchive
[*] Beginning extraction...please standby
[*] Found 157 files in PYZ archive
[*] Successfully extracted pyinstaller archive: D:/PycharmProjects/gui_project/dist/auto_organize_gui.exe
You can now use a python decompiler on the pyc files within the extracted directory
def find_main(pyc_dir):
for pyc_file in os.listdir(pyc_dir):
if not pyc_file.startswith("pyi-") and pyc_file.endswith("manifest"):
main_file = pyc_file.replace(".exe.manifest", "")
result = f"{
pyc_dir}/{
main_file}"
if os.path.exists(result):
return main_file
pyc_dir = os.path.basename(exe_file)+"_extracted"
main_file = find_main(pyc_dir)
main_file
读取从pyz目录抽取的pyc文件的前4个字节作基准:
pyz_dir = f"{
pyc_dir}/PYZ-00.pyz_extracted"
for pyc_file in os.listdir(pyz_dir):
if pyc_file.endswith(".pyc"):
file = f"{
pyz_dir}/{
pyc_file}"
break
with open(file, "rb") as f:
head = f.read(4)
list(map(hex, head))
['0x42', '0xd', '0xd', '0xa']
校准入口类:
import shutil
if os.path.exists("pycfile_tmp"):
shutil.rmtree("pycfile_tmp")
os.mkdir("pycfile_tmp")
main_file_result = f"pycfile_tmp/{
main_file}.pyc"
with open(f"{
pyc_dir}/{
main_file}", "rb") as read, open(main_file_result, "wb") as write:
write.write(head)
write.write(b"\0"*12)
write.write(read.read())
校准子类:
pyz_dir = f"{
pyc_dir}/PYZ-00.pyz_extracted"
for pyc_file in os.listdir(pyz_dir):
pyc_file_src = f"{
pyz_dir}/{
pyc_file}"
pyc_file_dest = f"pycfile_tmp/{
pyc_file}"
print(pyc_file_src, pyc_file_dest)
with open(pyc_file_src, "rb") as read, open(pyc_file_dest, "wb") as write:
write.write(read.read(12))
write.write(b"\0"*4)
write.write(read.read())
from uncompyle6.bin import uncompile
if not os.path.exists("py_result"):
os.mkdir("py_result")
for pyc_file in os.listdir("pycfile_tmp"):
sys.argv = ['uncompyle6', '-o',
f'py_result/{
pyc_file[:-1]}', f'pycfile_tmp/{
pyc_file}']
uncompile.main_bin()
#!/usr/bin/env python
# coding: utf-8
# 提取exe中的pyc
import os
import sys
import pyinstxtractor
from uncompyle6.bin import uncompile
import shutil
# 预处理pyc文件修护校验头
def find_main(pyc_dir):
for pyc_file in os.listdir(pyc_dir):
if not pyc_file.startswith("pyi-") and pyc_file.endswith("manifest"):
main_file = pyc_file.replace(".exe.manifest", "")
result = f"{
pyc_dir}/{
main_file}"
if os.path.exists(result):
return main_file
def uncompyle_exe(exe_file, complie_child=False):
sys.argv = ['pyinstxtractor', exe_file]
pyinstxtractor.main()
# 恢复当前目录位置
os.chdir("..")
pyc_dir = os.path.basename(exe_file)+"_extracted"
main_file = find_main(pyc_dir)
pyz_dir = f"{
pyc_dir}/PYZ-00.pyz_extracted"
for pyc_file in os.listdir(pyz_dir):
if pyc_file.endswith(".pyc"):
file = f"{
pyz_dir}/{
pyc_file}"
break
else:
print("子文件中没有找到pyc文件,无法反编译!")
return
with open(file, "rb") as f:
head = f.read(4)
if os.path.exists("pycfile_tmp"):
shutil.rmtree("pycfile_tmp")
os.mkdir("pycfile_tmp")
main_file_result = f"pycfile_tmp/{
main_file}.pyc"
with open(f"{
pyc_dir}/{
main_file}", "rb") as read, open(main_file_result, "wb") as write:
write.write(head)
write.write(b"\0"*12)
write.write(read.read())
if os.path.exists("py_result"):
shutil.rmtree("py_result")
os.mkdir("py_result")
sys.argv = ['uncompyle6', '-o',
f'py_result/{
main_file}.py', main_file_result]
uncompile.main_bin()
if not complie_child:
return
for pyc_file in os.listdir(pyz_dir):
if not pyc_file.endswith(".pyc"):
continue
pyc_file_src = f"{
pyz_dir}/{
pyc_file}"
pyc_file_dest = f"pycfile_tmp/{
pyc_file}"
print(pyc_file_src, pyc_file_dest)
with open(pyc_file_src, "rb") as read, open(pyc_file_dest, "wb") as write:
write.write(read.read(12))
write.write(b"\0"*4)
write.write(read.read())
os.mkdir("py_result/other")
for pyc_file in os.listdir("pycfile_tmp"):
if pyc_file==main_file+".pyc":
continue
sys.argv = ['uncompyle6', '-o',
f'py_result/other/{
pyc_file[:-1]}', f'pycfile_tmp/{
pyc_file}']
uncompile.main_bin()
调用:
exe_file = r"D:/PycharmProjects/gui_project/dist/auto_organize_gui.exe"
uncompyle_exe(exe_file, True)
可以看到已经完美的反编译出exe其中的python脚本:
只需在打包命令后面加上--key
命令即可,例如文章开头的命令可以更换为:
pyinstaller -Fw --icon=h.ico auto_organize_gui.py --add-data="h.ico;/" --key 123456
123456
是你用来加密的密钥,可以随意更换。
该加密参数依赖tinyaes,可以通过以下命令安装:
pip install tinyaes
打包后再次执行反编译:
exe_file = r"D:/PycharmProjects/gui_project/dist/auto_organize_gui.exe"
uncompyle_exe(exe_file, True)
结果只有入口脚本反编译成功,被依赖的脚本均被加密,无法直接被反编译:
可以看到抽取的中间结果变成了.pyc.encrypted
格式,无法直接被反编译:
可以看到,常规手段就无法直接反编译了。这个时候还想反编译就需要底层的逆向分析研究了,或者pyinstaller的源码完整研究一遍,了解其加密处理的机制,看看有没有破解的可能。
文章浏览阅读2.1k次。QMetaObject类包含有关Qt对象的元信息。头文件:#include <QMetaObject>cmake:find_package(Qt6 COMPONENTS Core REQUIRED)target_link_libraries(mytarget PRIVATE Qt6::Core)qmake:QT += core详细说明Qt中的元对象系统负责信号和对象之间的通信机制、运行时类型信息和Qt属性系统。为应用程序中使用的每个QObject子类创建一个QMeat
文章浏览阅读1.7k次。线程与网络IO实现简单客户端与服务端聊天交互首先在学习了线程和网络IO后,做了一个练习.客户端给服务器端发消息.服务器端回消息给客户端.问题由此而生!服务器类:public class ThreadServer { /** * 在服务器端也写两个内部类实现读与写的分离 * @author 旖析木 * */ private class THDL extends Threa..._编程实现,客户端输入两个数,服务端计算结果,并返回。要求服务端用多线程实现
文章浏览阅读2.2w次,点赞3次,收藏29次。全局 traceId关于链路追踪,在微服务的趋势下,一次调用的日志信息分布在不同的机器上或目录下,当需要看一条链路调用所有的日志信息时,这是个比较困难的地方,我们虽然有ELK , Sentry等日志异常收集分析工具, 但是如何把信息串起来也是一个关键的问题。 我们一般的做法是在系统调用开始时生成一个traceId , 并且它伴随着一次调用的整个生命周期 。 当一个服务调用另外一个服务的时候,t..._全局id号段链模式
文章浏览阅读221次。https://xz.aliyun.com/t/4640_awd自动攻击3389
文章浏览阅读544次。jQuery天猫商品分类导航菜单一、HTML模块相关源码HTML文件:index.html<!DOCTYPE html><html><head> <meta charset="utf-8" /> <title>jQuery天猫分类导航菜单</title> <link rel="stylesheet" href="style.css"> <script type="text/_网页设计天猫商品分类代码
文章浏览阅读1.1k次。1, 可达500MA充电电流,SOT23-5,单LED指示灯,5V输入线性降压,HU40542, 可达1000MA充电电流,SOP8-EP,双LED指示灯,5V输入线性降压,HU40563, 可达600MA充电电流,SOT23-6,单LED指示灯,5V输入线性降压,HU40577,LDO稳压芯片(2V-80V),DC-DC降压芯片,DC-DC升压芯片选型表HU4054 是一款性能优异的单节锂离子电池恒流/恒压线性充电器。HU4054 适合给 USB 电源以及适配器电源供电。基于特殊的内._高输入的单节锂电池充电芯片
文章浏览阅读290次。phpQuery解析HTML( $dom = new DOMDocument(); )在有html头部时会去识别查询<metacharset=“字符编码”>,对按charset编码去解析,但部分html的编码声明是使用<metahttp-equiv=“content-type” content=“text/html; charset=字符编码” />这个时候只需要对要解析的内容拼接“<meta charset=“字符编码”>”即可;<?phpdat._phpquery 标签解析bug
文章浏览阅读3.1k次,点赞3次,收藏14次。Windows 7操作系统一、操作系统概述1. 操作系统的概念2.操作系统的功能3.操作系统的主要特征4.操作系统的分类5.常用操作系统简介二、Windows 7基础1. Windows 7的配置2. 键盘、鼠标的基本操作3.窗口4.对话框5.剪切板6.启动和退出应用程序三、整理Windows 7 的桌面1.桌面上的主要元素。2. 个性化桌面设置3. 任务栏与“开始”菜单四、Windows 7 的文件和文件夹管理1.文件2.资源管理器3.文件或文件夹的选定4.复制文件或文件夹★5.移动文件或文件夹★6.删除_③windows操作系统的基本概念和常用术语,文件、文件夹等。
文章浏览阅读1k次。1. 前言大家都知道,Postman是一个非常受欢迎的API接口调试工具,提供有Chrome扩展插件版和独立的APP,不过它的很多高级功能都需要付费才能使用。如果你连Postman都还没有用过,不妨可以先体验一番。Postman官网:https://www.getpostman.com/PS: 由于2018年初Chrome停止对Chrome应用程序的支持,你的Postman插件可能无法正常使用了,在这里建议大家直接下载它的应用程序进行使用。虽然Postman作为一款接口调试工具,算是非常优秀的了,_postman postwoman
文章浏览阅读949次。gid+实现多种统计图 ,支持负坐标 using System;using System.Web.UI;using System.Data;using System.Web.UI.WebControls;using System.Drawing;using System.Drawing.Imaging;using System.IO;using System.Web;using System._gid+
文章浏览阅读1.8w次,点赞7次,收藏12次。Android 自定义视频录制翻转问题终极解决方案自定义视频录制使用系统可用播放器前后摄像和视频反转问题总结自定义视频录制mediarecorder = new MediaRecorder();// 创建mediarecorder对象 mCamera = getCameraInstance(); // 获取camera if (null == mCamera)_android 录视频左右翻转
文章浏览阅读7.6k次,点赞103次,收藏249次。半小时实现Java网络爬虫,附完整源码,冰河强烈建议收藏!!