简介

Pyhton是一个脚本语言,在运行Python代码时,最终由Python解释器来执行。解释器就是Python的运行环境,但是除了开发人员,大部分人在使用时并不会安装Python。所以官方就提供了一些打包程序,将代码与解释环境打包到二进制文件中,方便在各种操作系统中运行。

下面为一些文件格式:

.py: Python代码文件。对于一些开源项目,发布方也许会直接提供源码,但是使用时需要安装依赖库

.pyc: 源码编译后的中间式文件,其目的是为了加快下次运行时的速度。不能直接运行,需要python虚拟机的支持才可以运行,类似于java、.net平台的中的虚拟机,因此可以pyc文件可以跨平台执行。不同的python编译出的pyc文件是不同的,例如python2.4编译出的pyc文件,2.5不可以使用

可执行文件:针对得专业人员,直接提供可执行文件,只需要了解使用方法即可,缺点是可移植性差,需要针对不同的操作系统,生成可执行文件。

下面是一些打包为可执行文件的方法:

下面的实验,我主要使用pyinstaller打包文件。

Python文件打包

配置说明

下载方法:

pip install pyinstaller

python setup.py install

验证

pyinstaller -v

参数

-F 制作独立的可执行程序

-D 制作出的档案存放在同一个文件夹下(默认值)

-K 包含TCL/TK(对于使用了TK的,最好加上这个选项,否则在未安装TK的电脑上无法运行)

-w 制作窗口程序

-c 制作命令行程序(默认)

-X 制作使用UPX压缩过的可执行程序(推荐使用这个选项,需要下载UPX包,解压后upx.exe放在Python(非PyInstaller)安装目录下,下载upx308w.zip)

-o DIR 指定输出SPEC文件路径(这也决定了最后输出的exe文件路径)

--icon=[ICO文件路径] 指定程序图标

-v [指定文件] 指定程序版本信息

-n [指定程序名] 指定程序名称

Windows:生成exe文件

正常使用

pyinstaller -F test.py

可执行文件存储在dist/test文件夹中,不过我这环境出了点问题,使用

pyinstaller -D test.py

进入build/test文件夹,将Python环境安装处的Python37.dll拷贝到目录,再执行该目录下的test.exe

Linux:生成ELF

下载方法和windows平台相同。

打包生成ELF

pyinstaller -F test.py

pyinstaller打包程序识别

exe图标特征

exe字符串特征

和Python语句很类似__file__,__main___,此外也有很多Python的特征

ELF字符串特征

ELF主函数特征

在Linux上运行pyintaller打包的可执行文件时,它会将打包好的文件解压到临时文件夹(/tmp)中的_MEIxxxxxx 路径中暂时存放,执行完毕之后再删除。所以在主函数起始位置会有如下特征

反编译文件

我们要将Python打包成的可执行文件,首先要利用archive_viewer.py将exe/elf反编译为pyc字节码,再使用uncompyle6反编译为py文件。

反编译为pyc文件

archive_viewer.py就在我们的pyinstaller安装包文件夹中。例如我的Python环境中:

Win:D:\Anaconda\Lib\site-packages\PyInstaller\utils\cliutils

Linux:/home/ubuntu/.local/lib/python3.6/site-packages/PyInstaller/utils/cliutils

参数

U: go Up one level

O : open embedded archive name

X : extract name

Q: quit

首先,我们要解析EXE/ELF文件数据包,将可执行文件放置到与archive_viewer.py同目录下,使用命令:

python archive_viewer.py test.py

有很重要的两点:

反编译EXE/ELF文件的Python版本必须与打包时的版本一致

反编译的pyc文件命名必须遵照上面红框中的“test”

其次,提取test.pyc和struct.pyc文件(后面会说明该文件作用)

? x test

to filename? test.pyc

? x struct

to filename? struct.pyc

Linux端相同

下面Win和Linux端操作相同,只讲述Win端。

文件修补

使用pyinstaller打包的文件,文件头的数据会被抹消掉。再还原的过程中,我们需要手动进行修补。文件头的格式为:magic(4字节,编译器标志) + 时间戳(4字节)。在实际修补时,需要添加的数据可能不止是8个字节。

将test.pyc和struct.pyc对比

struct.pyc比test.pyc多出16字节,将这16字节插入test.pyc的头部

反编译字节码文件

这里需要使用到uncompyle6(如果是Python2.7需要使用uncompyle2)

安装

pip install uncompyle6

或者

git clone https://github.com/rocky/python-uncompyle6.git

cd python-uncompyle6

python setup.py install

反编译

uncompyle6 test.pyc > test.py

实战演练

准备

signin.exe:

我们的目标是获取程序登录的账户密码。

未加壳的64位可执行文件。

程序分析

可以判断出,这个程序实际上是由Python打包成的可执行文件,且在运行这个程序时,在同目录下产生了一个tmp.dll文件,猜测是程序调用某些函数的接口。

反编译

使用archive_viewer.py反编译为字节码文件

python archive_viewer.py signin.exe

修补文件

55 0D 0D 0A 00 00 00 00 70 79 69 30 10 01 00 00

程序是在Python3.8环境下打包,因此我们需要在Python3.8下使用uncompyle6

uncompyle6 main.pyc > main.py

得到py文件

1 #uncompyle6 version 3.7.2

2 #Python bytecode 3.8 (3413)

3 #Decompiled from: Python 3.8.0 (tags/v3.8.0:fa919fd, Oct 14 2019, 19:37:50) [MSC v.1916 64 bit (AMD64)]

4 #Embedded file name: main.py

5 #Compiled at: 1995-09-28 00:18:56

6 #Size of source mod 2**32: 272 bytes

7 importsys8 from PyQt5.QtCore import *

9 from PyQt5.QtWidgets import *

10 from signin import *

11 from mydata importstrBase6412 from ctypes import *

13 import_ctypes14 from base64 importb64decode15 importos16

17 classAccountChecker:18

19 def __init__(self):20 self.dllname = './tmp.dll'

21 self.dll =self._AccountChecker__release_dll()22 self.enc =self.dll.enc23 self.enc.argtypes =(c_char_p, c_char_p, c_char_p, c_int)24 self.enc.restype =c_int25 self.accounts = {b'SCTFer': b64decode(b'PLHCu+fujfZmMOMLGHCyWWOq5H5HDN2R5nHnlV30Q0EA')}26 self.try_times =027

28 def __release_dll(self):29 with open(self.dllname, 'wb') as (f):30 f.write(b64decode(strBase64.encode('ascii')))31 returnWinDLL(self.dllname)32

33 defclean(self):34 _ctypes.FreeLibrary(self.dll._handle)35 ifos.path.exists(self.dllname):36 os.remove(self.dllname)37

38 def_error(self, error_code):39 errormsg = {0:'Unknown Error',40 1:'Memory Error'}41 QMessageBox.information(None, 'Error', errormsg[error_code], QMessageBox.Abort, QMessageBox.Abort)42 sys.exit(1)43

44 def __safe(self, username: bytes, password: bytes):45 pwd_safe = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

46 status =self.enc(username, password, pwd_safe, len(pwd_safe))47 return(pwd_safe, status)48

49 defcheck(self, username, password):50 self.try_times += 1

51 if username not inself.accounts:52 returnFalse53 encrypted_pwd, status =self._AccountChecker__safe(username, password)54 if status == 1:55 self._AccountChecker__error(1)56 if encrypted_pwd !=self.accounts[username]:57 returnFalse58 self.try_times -= 1

59 returnTrue60

61

62 classSignInWnd(QMainWindow, Ui_QWidget):63

64 def __init__(self, checker, parent=None):65 super().__init__(parent)66 self.checker =checker67 self.setupUi(self)68 self.PB_signin.clicked.connect(self.on_confirm_button_clicked)69

70 @pyqtSlot()71 defon_confirm_button_clicked(self):72 username = bytes((self.LE_usrname.text()), encoding='ascii')73 password = bytes((self.LE_pwd.text()), encoding='ascii')74 if username == b'' or password == b'':75 self.check_input_msgbox()76 else:77 self.msgbox(self.checker.check(username, password))78

79 defcheck_input_msgbox(self):80 QMessageBox.information(None, 'Error', 'Check Your Input!', QMessageBox.Ok, QMessageBox.Ok)81

82 defmsgbox(self, status):83 msg_ex = {0:'',84 1:'',85 2:"It's no big deal, try again!",86 3:'Useful information is in the binary, guess what?'}87 msg = 'Succeeded! Flag is your password' if status else 'Failed to sign in\n' + msg_ex[(self.checker.try_times % 4)]88 QMessageBox.information(None, 'SCTF2020', msg, QMessageBox.Ok, QMessageBox.Ok)89

90

91 if __name__ == '__main__':92 app =QApplication(sys.argv)93 checker =AccountChecker()94 sign_in_wnd =SignInWnd(checker)95 sign_in_wnd.show()96 app.exec()97 checker.clean()98 sys.exit()99 #okay decompiling main.pyc

代码分析

通过代码我们能够了解到这些信息

1.

elf.dllname = './tmp.dll'

调用了tmp.dll文件作为接口。

2.

def __safe(self, username: bytes, password: bytes):

pwd_safe= b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'status=self.enc(username, password, pwd_safe, len(pwd_safe))return(pwd_safe, status)defcheck(self, username, password):

self.try_times+= 1

if username not inself.accounts:returnFalse

encrypted_pwd, status=self._AccountChecker__safe(username, password)if status == 1:

self._AccountChecker__error(1)if encrypted_pwd !=self.accounts[username]:returnFalse

self.try_times-= 1

return True

self.accounts = {b'SCTFer': b64decode(b'PLHCu+fujfZmMOMLGHCyWWOq5H5HDN2R5nHnlV30Q0EA')}

调用tmp.dll文件中的enc函数,传入username, password, pwd_safe, len(pwd_safe),实际就是将password加密后存储到pwd_safe字节码中。最后用pwd_safe与b64decode(b'PLHCu+fujfZmMOMLGHCyWWOq5H5HDN2R5nHnlV30Q0EA')比较,且我们能够了解到用户名应该是SCTFer,且最后返回的status一个为非1。

打开tmp.dll文件,找到enc函数

观察代码,实际操作可以分为两部分,逆向分析

异或操作

第47~54行代码实际上就是将Dst与用户名循环异或,最后得到b64decode(b'PLHCu+fujfZmMOMLGHCyWWOq5H5HDN2R5nHnlV30Q0EA'),因此我们只需要逆向异或就能得到加密后的Dst

from base64 import *username= "SCTFer"pwd_safe= b64decode('PLHCu+fujfZmMOMLGHCyWWOq5H5HDN2R5nHnlV30Q0EA')#print (len(pwd_safe))

num = ["%02x" % x for x inpwd_safe]

hex_num= [int(x, 16) for x innum]print(num)#print (len(num))

for i in range(32):

hex_num[i]^= ord(username[i %len(username)])#print (hex_num)

hex_nums = bytes.fromhex(''.join([hex(x)[2:].rjust(2, '0') for x inhex_num]))print (hex_nums)

得到

b'o\xf2\x96\xfd\x82\x9c\xde\xb52v\x86yK3\xe6\x1f\x06\xd8\xb7=\x13J\xb8\xe3\xb52\xb3\xd38\x86\x10\x02\x00'

加密操作

每次传入了8字节数据进行加密(总共64字节),打开sub_180011311函数

仔细观察代码,实际上这部分代码是使用CRC32的查表法,对数据进行加密。

加密原理实际上就是CRC32算法---输入一组长度48的字符串,每8个字节分为1组,共6组。对每一组取首位,判断正负。正值,左移一位;负值,左移一位,再异或0xB0004B7679FA26B3。重复判断操作64次,得到查表法所用的表。

因此我们只需要将整个加密过程逆向操作得到查表法的表,再进行CRC64计算,就能得到输入。

secret =[]#for i in range(4):#secret.append(int(hex_nums[i*8:(i + 1) * 8][::-1].hex(),16))

for i in range(4):

secret.append(int.from_bytes(hex_nums[i*8:(i + 1) * 8], byteorder="little"))print(secret)

key= 0xB0004B7679FA26B3flag= ""

for s insecret:for i in range(64):

sign= s & 1

if sign == 1:

s^=key

s//= 2

if sign == 1:

s|= 0x8000000000000000

print(hex(s))

j=0while j < 8:

flag+= chr(s&0xFF)

s>>= 8j+= 1

print(flag)

因为计算机中采用小端排序,因此需要注意分组倒序。得到

脚本

from base64 import *username= "SCTFer"pwd_safe= b64decode('PLHCu+fujfZmMOMLGHCyWWOq5H5HDN2R5nHnlV30Q0EA')#print (len(pwd_safe))

num = ["%02x" % x for x inpwd_safe]

hex_num= [int(x, 16) for x innum]print(num)#print (len(num))

for i in range(32):

hex_num[i]^= ord(username[i %len(username)])#print (hex_num)

hex_nums = bytes.fromhex(''.join([hex(x)[2:].rjust(2, '0') for x inhex_num]))print(hex_nums)

secret=[]#for i in range(4):#secret.append(int(hex_nums[i*8:(i + 1) * 8][::-1].hex(),16))

for i in range(4):

secret.append(int.from_bytes(hex_nums[i*8:(i + 1) * 8], byteorder="little"))print(secret)

key= 0xB0004B7679FA26B3flag= ""

for s insecret:for i in range(64):

sign= s & 1

if sign == 1:

s^=key

s//= 2

if sign == 1:

s|= 0x8000000000000000

print(hex(s))

j=0while j < 8:

flag+= chr(s&0xFF)

s>>= 8j+= 1

print(flag)

账户密码

username:SCTFer

password:SCTF{We1c0m3_To_Sctf_2020_re_!!}

python反编译加密文件_python打包的二进制文件反编译相关推荐

  1. apktool 反编译 java_APK文件使用ApkTool解包反编译和重新打包及签名

    前段使用一直使用一个手机APK软件,不过最近软件更新,出现了一个很讨厌的语音提示,于是想通过重新编译把语音提示去掉. [准备工作] 配置JAVA环境,到http://www.java.com/下载并进 ...

  2. python des加密文件_Python DES加密解密方法 pyDes库 兼容中文

    单纯记录一下Python中DES加密解密的使用方式直接看代码: 依赖pyDes库安装> pip install pyDes 约定秘钥 python和java 加密和解密联调,兼容中文字符串 Py ...

  3. python解压zip文件_python怎样压缩和解压缩ZIP文件(转)

    有时我们需要在 Python 中使用 zip 文件,而在1.6版中,Python 就已经提供了 zipfile 模块可以进行这样的操作.不过 Python 中的 zipfile 模块不能处理多卷的情况 ...

  4. 反编译apk文件查看源码,反编译apk获取资源

    之前做过反编译apk文件的,原本想的就是应该做下总结的,后来...不知道后来发生了什么,一定不是因为我懒了.总之,今天领导让我研究下一个口碑比较好的app,看下它某个功能怎么实现的,用的什么技术,又扒 ...

  5. python创建多个文件_Python创建文件夹与文件的快捷方法

    这篇文章主要给大家介绍了关于Python创建文件夹与文件的快捷方法以及批量创建文件夹的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学 ...

  6. python 下载qq群文件_python作业/练习/实战:下载QQ群所有人的头像

    步骤与提示: 1.在腾讯群网页中进入任意一个群,获取相关信息,可以用postman是试一下,可以看到我们要的是mems里面的数据,需要获取到QQ号和群名片,如果没有群名片的话取昵称 2.根据QQ号下载 ...

  7. python如何实现下载文件_python实现下载文件的三种方法

    python实现下载文件的三种方法 Python开发中时长遇到要下载文件的情况,最常用的方法就是通过Http利用urllib或者urllib2模块. 当然你也可以利用ftplib从ftp站点下载文件. ...

  8. python怎么发送代码文件_python 通过 socket 发送文件的实例代码

    目录结构: client: #!/usr/bin/env python # -*-coding:utf-8 -*- import socket, struct, json download_dir = ...

  9. python扫描目录下文件_Python扫描目录读取文件,不考虑子目录

    这篇文章主要为大家详细介绍了Python扫描目录读取文件,不考虑子目录,具有一定的参考价值,可以用来参考一下. 感兴趣Python扫描目录读取文件,不考虑子目录的小伙伴,下面一起跟随512笔记的小编罗 ...

最新文章

  1. 一文吃透JAVA定时器格式
  2. python中数据类型为list_python_数据类型_list
  3. 你不知道的JavaScript-0
  4. 第二十四期:揭秘:为什么电脑越用越卡 大型破案现场
  5. POJ 2255/递归:前序中序求后序
  6. 怎样学操作系统?一文带你掌握核心内容
  7. java写html的多选框,Selenium+java - 单选框及复选框处理
  8. 带有控制器,方法,标题,参数,@ RequestParam,@ PathVariable的Spring MVC @RequestMapping注释示例
  9. Connected to the target VM, address: '127.0.0.1:60885', transport: 'socket'
  10. 2017年二级计算机c真题语言,2017全国计算机二级C考试真题
  11. 计算机论文中期考核报告,硕士学位论文中期检查表范文_中期检查报告范文_中期考核 修改论文题目...
  12. 项目中常见的沟通方式
  13. hbase+phoenix开发预演小例子
  14. linux切换root 权限,【linux】sudo su切换到root权限
  15. 高山仰止,景行行止,虽不能至,心向往之!软工大二狗的回顾与展望。
  16. Java中基本数据类型和包装类型的区别
  17. BAT用户画像数据浅析
  18. python数据分析董付国ppt_Python数据分析、挖掘与可视化(慕课版) 董付国 著 / 人民邮电出版社...
  19. ResNet50是什么
  20. java设计程序实验报告,实验报告一

热门文章

  1. python之pymysql的使用
  2. Python之math库
  3. 逾期怎么处理_信用卡3万逾期三个月,催收说已经起诉生效,立案处理了,该怎么办?...
  4. linux文件系统管理知识导图,Linux磁盘和文件系统管理基础知识自测
  5. ansys怎么建立弯曲圆柱_ANSYS复合材料分析
  6. oracle adg 人工干预,Oracle DataGuard
  7. ShadeGraph教程之节点详解6:Procedural Nodes
  8. Linux命令完全指南route,Linux命令--route
  9. client中周期性边界_RVE周期性边界条件施加
  10. Blazor Hydra——在单个站点上托管多个Blazor SPA