您当前的位置:首页 > 知乎文章

关于Python病毒样本的分析方法(二)

时间:2022-02-28 10:47:08  知乎原文链接  作者:网盾网络安全培训

典型木马病毒分析

通过分析一个简单的样本来演示如何分析这一系列的样本。我们找到一个样本哈希287b67d9927b6a318de8c94ef525cc426f59dc2199ee0e0e1caf9ef0881e7555。分析第一步需要判断该样本是由什么工具打包的:

首先,我们可以看到有“_MEIPASS2=”字符串,从这可以看到该样本是由Python打包而来。从WinMain函数的代码来看,可以看出来该样本是PythonInstaller打包而来。

class="ztext-empty-paragraph">

class="ztext-empty-paragraph">

确定好是由什么工具打包后,可以使用之前提到的方式直接对其解包。使用pyinstxtractor.py脚本对其进行解包。解包后的文件列表大致如下:

class="ztext-empty-paragraph">

class="ztext-empty-paragraph">

其中我们可以看到解包后会生成python27.dll,从这可以看出来该样本是由Python2.7编写的。

class="ztext-empty-paragraph">

class="ztext-empty-paragraph">

我们大致可以看到,解包后的文件有很多。会有很多Python运行必要的组件和第三方组件,如:_socket.pyd、Crypto.Cipher._AES.pyd等。我们可以在文件列表内看到一个没有扩展名的文件:

class="ztext-empty-paragraph">

class="ztext-empty-paragraph">

该文件就是我们需要样本核心代码。该文件的文件名就是打包前py文件的文件名,该文件的文件格式很接近pyc的文件格式。之间的差别就是在于文件头缺少majic字段和时间戳。

class="ztext-empty-paragraph">

class="ztext-empty-paragraph">

我们添加一个Python2.7的majic字段和任意的时间戳。然后我们就可以使用uncompile.py脚本还原出该py文件。

class="ztext-empty-paragraph">

class="ztext-empty-paragraph">

对于这种常见工具打包的Python样本,我们通常处理的流程:(1) 判断样本是由什么工具打包而来的。这种工具很常见,它们打包出来的程序往往很容易判断出来。(2) 使用针对的破解工具或方法进行代码提取。(3) 提取的代码通常是pyc文件格式的。

(4) 使用uncompile.py脚本进行反编译就可得到原始的py文件。

其他Python打包分析

通常情况下,病毒样本不会乖乖的使用以上几种工具进行打包。很多黑客会使用自己定制的程序来对python脚本进行打包。我们以一个样本举例,通过该样本来演示如何分析。该样本是一个由pupy的py脚本打包而来的elf文件。1. 分析该样本,发现该样本会在内存中解密释放libpython2.7.so.1.0这个so文件。该文件是python2.7的核心文件,导出函数都是python重要的api函数。

class="ztext-empty-paragraph">

class="ztext-empty-paragraph">

2. 在将libpython2.7.so.1.0,就该函数sub_5637CE90DFD2负责获取libpython2.7.so.1.0的导出函数。

class="ztext-empty-paragraph">

class="ztext-empty-paragraph">

3. 将libpython2.7.so.1.0的导出函数地址初始化到imports全局变量内。通过使用dlsym函数,利用函数的名称来获取函数地址。

class="ztext-empty-paragraph">

class="ztext-empty-paragraph">

4. 之前已经将python api的地址存储在imports变量内,之后的调用也是通过imports变量来进行的,还原一下调用的python函数的符号,可以看到样本初始化python环境和执行的整个过程。

首先是初始化python运行环境。

class="ztext-empty-paragraph">

class="ztext-empty-paragraph">

随后初始化必要的python模块。

class="ztext-empty-paragraph">

class="ztext-empty-paragraph">

class="ztext-empty-paragraph">

class="ztext-empty-paragraph">

class="ztext-empty-paragraph">

在准备好python运行环境后,就该是加载pupy的代码了。

看到PyMarshal_ReadObjectFromString函数的时候,可以得知该样本使用脚本式来执行python的代码。

class="ztext-empty-paragraph">

class="ztext-empty-paragraph">

PyMarshal_ReadObjectFromString函数的主要功能是,读取一段数据,生成一个PyCodeObject的python对象。第一个参数是string的地址,第二个参数是数据的长度。

class="ztext-empty-paragraph">

5. PyMarshal_ReadObjectFromString的第一个参数就是pupy的字节码,这个字节码的实际格式是一个pyc文件。将这个string还原成pyc文件就可以恢复出pupy的py脚本。

class="ztext-empty-paragraph">

class="ztext-empty-paragraph">

class="ztext-empty-paragraph">

class="ztext-empty-paragraph">

总结

通过对多个工具分析可以看到,无论是通过什么工具进行打包,都需要一个关键的因素,那就是python.dll或libpython.so。那么我们先来介绍一下python.dll在python中起到了什么作用。

实质上,在整个Python的目录结构中,python.dll是最核心最基础的组件。Python的运行时环境就是在python.dll中实现的,这里包含了对象/类型系统、内存分配器和运行时状态信息。这里也就可以理解为什么任何方式进行打包都需要将对应的python.dll一同打包进去了。

也就是说,无论什么工具,都是要通过python.dll来建立python的运行环境。这个过程是可以通过调用python.dll的导出函数来实现的。在python.dll的一个导出函数中,有个函数Py_Initialize就是用来初始化Python的运行环境。

Python有两种主要的运行模式,一种是交互式模式,另一种是脚本运行方式。我们通过两个简单例子来演示一下:

1. 脚本运行方式

我们准备一个简单的py脚本,将其编译为pyc文件。

class="ztext-empty-paragraph">

class="ztext-empty-paragraph">

我们准备一个简单的C代码来调用此pyc文件。

class="ztext-empty-paragraph">

class="ztext-empty-paragraph">

(1) 加载对应版本的python.dll。

(2) 首先先调用Py_Initialize函数。(3) 随后调用PyRun_SimpleFile,来运行pyc文件。

class="ztext-empty-paragraph">

2. 输出结果

class="ztext-empty-paragraph">

class="ztext-empty-paragraph">

3. 交互式模式

简单的C代码的例子:

class="ztext-empty-paragraph">

class="ztext-empty-paragraph">

(1) 加载对应版本的python.dll。

(2) 首先先调用Py_Initialize函数。

(3) 接下来利用PyDict_New创建一个Dict。

(4) 然后调用PyEval_GetBuiltins获取解释器。

(5) 最后调用PyRun_String来执行各种代码。

class="ztext-empty-paragraph">

4. 运行结果

class="ztext-empty-paragraph">

class="ztext-empty-paragraph">

根据两个演示,可以很明确的知道Python的运行逻辑。在之后遇到的任何由Python打包的可执行文件时,可以通过对PyRun系列的函数进行检测。

class="ztext-empty-paragraph">

class="ztext-empty-paragraph">

通过这一系列的函数,我们可以获取到打包进可执行文件内的明文Python脚本或pyc的字节码。

5. 总结

处理python打包这一系列样本的过程主要如下:(1) 判断是否是已知工具打包。(2) 如果不是已知工具,可着手查找PyRun系列的函数的调用。(3) 在PyRun系列的函数的参数中可以获取到对应的样本代码。

(4) 使用uncompile.py脚本进行反编译就可得到原始的py文件。

上一篇      下一篇    删除文章    编辑文章
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表
推荐资讯
相关文章
    无相关信息
栏目更新
栏目热门