社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  Python

python编译后的pyd爆破

马哥Linux运维 • 4 年前 • 453 次点击  

最近接触一个国外某app的协议软件,是python3.8写的,它把关键模块都编译成了pyd,然后使用pyinstaller打包发布给用户。软件启动后检查机器码,然后需要输入授权码才可使用,看着很是恼火,所以想尝试破解。其中关键应该是需要爆破pyd里的逻辑,修改汇编代码来实现绕过授权。

前提知识

1. py、pyc、pyo、pyd

py: python 脚本文件(source code)

pyc: 脚本文件编译得到的字节码, 二进制文件,python文件经过编译器编译之后的文件。可以提高文件加载速度。

pyo: 脚本文件开启优化编译选项(-O)编译得到的字节码,二进制文件,优化编译后的文件。可以通过python -O file.py生成。

pyd: 基本的Windows DLL文件,python的动态链接库。

2. 编译pyd

要编译的脚本:uitl1.py

def fun_hello(s):
    if s == 1:
        return 'hello world'
    elif s == 2:
        return '222222222'

提供编译脚本:setup.py

from setuptools import setup
from Cython.Build import cythonize
 
setup(
    name='test',
    ext_modules=cythonize('util1.py')
)

在setup.py文件所在目录下进行如下命令:

python setup.py build_ext --inplace

这样就能看到同级目录下生成pyd文件了。32位的python生成pyd文件是32位的,64位的python生成的是64位的。

3. 使用pyd

test.py

import util1
 
if __name__ == '__main__':
    print(util1.fun_hello(2))

4. pyinstaller打包py到exe

pip install pyinstaller
pyinstaller test.py

5. 解包pyinstaller打包的exe

pyinstxtractor.py即可。这个代码不长,可以调试看看,熟悉下打包的exe组成。需要注意的是,被打包的文件都是zlib.compress压缩过后,再按照固定格式组成exe的,所以直接修改打包后的exe的16进制码来爆破貌似不好操作。只能解包后修改pyd,然后找齐依赖的库,重新pyinstaller打包,实现爆破。

https://github.com/countercept/python-exe-unpacker

6. pyc反编译

uncompyle6支持python3.8的pyc的反编译。

需要注意的是,如果是pyinstaller解包后取到的pyc文件,文件头部的magic被抹除过了,所以需要把对应版本python的magic加上来,可以装对应版本python,然后到安装目录下随便找个pyc文件,看一下头部,然后用010Editor复制到解包后的pyc,就可以正常反编译了。

下图是python3.8_32位的magic头:

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

pyd文件汇编代码和python脚本的对应关系分析

前面的前提知识,随便搜搜都能找到。但是如何才能直接修改pyd的汇编代码,实现python脚本流程的更改呢?

我百度谷歌搜了半天也没找到合适的资料,也许很少有人破解python编译打包的exe吧。

那么下面就是我做的工作了,也是本帖的价值所在了。

我自己写了一个python小脚本,然后编译成了pyd,它会生成一个中间的util1.c文件,代码大概有3000多行。只要花时间精力熟悉这个c文件,然后对照着ida就可以了解python脚本转成C然后编译成汇编指令,它们3者之间大概对应关系了。

下面略过大概1天的工作量,直接给出我们拿到一个pyd后,怎么快速找到我们要找的关键python代码。然后直接爆破。

把要分析的pyd文件拖到对应32位或64位的IDA:

大概所有的pyd都只有这个一个导出函数,当这个pyd模块被其他py脚本import时会调用这个导出函数进行模块初始化。

跳转到dword_1000634C可以看到一个结构体,里面有一个关键的成员__pyx_moduledef_slots。

这个成员是一个结构体数组。

里面有个关键函数__pyx_pymod_exec_util1负责初始化python脚本里的所有变量,函数,常量等等,把他们都对应到pyobject,然后就只使用这些pyobject了。所以汇编里看流程就很难,因为没有明显的明文了。

定位到 __pyx_pymod_exec_util1后,我们主要的目的是找常量和pyobject的对照表,python脚本里的函数名和汇编函数的对照表,有这2个表,python脚本和汇编的对应关系就明朗了。这里就只能手动往下翻了。

翻到类似调用 PyUnicode_InternFromString 的地方,大概就是我们要找的常量对照表了。

也就是C文件里的这个表。

其中offset dword_10006DFC就是代表字符串"222222222"的pyobject,直接找它的交叉引用就可以定位一些关键代码了。

我们继续在 __pyx_pymod_exec_util1 里找python脚本函数对应汇编函数的那个表。

跳过去:

aFunHello指向python脚本里的函数名。

__pyx_pf_5util1_fun_hello就是对应的汇编函数。

可以看到,只要找到这个表,就很容易定位我们要找的python脚本函数对应的汇编实现了。

其实我们也可以不必如上这么麻烦。只要在.data段里翻一翻。或者string窗口找到感兴趣的字符串交叉引用也能很快找到这个表。

需要知道的就是, aFunHello下面就是对应的汇编实现函数。

现在终于可以去分析fun_hello这个python脚本函数对应的汇编函数了。

可以看到脚本里的 s == 1 对应的汇编就是 __Pyx_PyInt_EqObjC 然后下面会使用PyObject_IsTrue判断这个函数的返回值。

那么爆破点就找到了。把 jz short loc_10004753 改成jnz short loc_10004753即可。

IDA-》edit-》Patch program-》Assemble修改,然后 IDA-》edit-》Patch program-》Apply patches to input file即可得到修改后的pyd文件。

这样就实现了修改python脚本的执行逻辑了。

正常脚本应该是输出22222222才对,因为我们的爆破,输出了hello world!

这里我只是简单分析了if语句的修改,可以多写几个例子。实现修改其他流程。

在此只是抛转引玉,给大家一点点参考。省一点点时间。

另:大家看了半天以为我是分析那个国外app协议软件,其实我还没有搞定那个破解,所以只把自己这段时间的分析工作贴了上来,仅供参考。

Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/107176
 
453 次点击