Windows PE病毒分类及感染方式

以后我们很长时间都要与Windows PE病毒打交道,所以在这里就先了解下它的分类及感染方式吧。

1. PE病毒的概念

PE病毒又称为Win32 PE病毒,或称为Win32病毒,它指所有Windows下PE文件格式文件的感染病毒。因为它通常采用Win32汇编编写,而且格式为PE文件,因此得名。

PE病毒是以Windows PE程序为载体,能寄生于PE文件或Windows系统的病毒程序。

感染是指在尽量不影响目标程序(系统)正常功能的前提下,而使其具有病毒自身的功能。一个病毒通常包括如下模块:

  • 感染模块:被感染程序同样具备感染能力
  • 触发模块:在特定条件下实施相应的病毒功能,比如日期、键盘输入等
  • 破坏模块:网络攻击行为,推荐攻击链或ATT&CK
  • 其他模块

如果我们要编写或分析PE病毒,则需要掌握以下关键信息:

  • 病毒的重定位
  • 获取API函数地址
  • 文件搜索
  • 内存映射文件
  • 病毒如何感染其它文件
  • 病毒如何返回到Host程序

2. PE病毒分类

以感染目标进行分类,包括文件感染和系统感染。

2.1 文件感染

将代码寄生在PE文件,病毒本身只是PE文件的一部分,依赖于感染目标,通常也叫Host文件,控制权获得也是以目标程序运行来获得的。分为:

  • 传统感染型:以Win32汇编程序编写为主
  • 捆绑释放型:编写难度较低,通过高级语言均可编写,将目标程序和病毒程序捆在一起,和捆绑器有相似之处

2.2 系统感染

将代码或程序寄生在Windows操作系统,该类病毒越来越多,它不感染具体文件,但是它会在操作系统中保存自己的实体,同时也可以通过系统启动的方法来获得控制权。传播途径包括即时通信软件、U盘、光盘、电子邮件、网络共享等。

3. 传统文件感染型

3.1 感染思路

如下图所示,左边是一个正常的PE文件,右边是PE病毒感染该程序时的修改,病毒代码以新节的形式附在程序最后面。我们知道PE文件是由多个节组成的,病毒代码为了融入目标程序会以节的形式,同时修改PE文件头。

对感染来说,它一方面需要使得宿主具备自己的功能,另一方面也不破坏宿主程序的功能。所以病毒代码执行完毕之后,它必须将控制权交还给宿主,以免自己被发现。

  • 优点:被感染后的程序主体依然是目标程序,不影响目标程序图标,隐蔽性稍好。
  • 缺点:对病毒代码的编写要求较高,通常是汇编语言编写;难以成功感染自校验程序。

3.2 感染的基本流程

比如某个Windows PE病毒只感染当前目录下的test.exe文件,它没有破坏性。test.exe被感染后,首先执行病毒代码,然后执行自身代码。假设在当前目录下存在4个文件:

  • calc.exe:计算器
  • notepad.exe:记事本
  • test.exe:测试PE文件
  • main.exe:PE病毒程序

test.exe文件原始大小为2.5KB,程序入口地址为00401000。运行main.exe文件后,test.exe文件大小变为6.5KB,入口地址变为004042DC,也就是病毒代码的地址。病毒将被感染的test.exe重命名为test-ok.exe,然后将当前目录下某一个.exe文件(比如记事本)重命名为test.exe,如果我们运行测试PE文件,也就是test-ok.exe,它也能像main.exe一样去感染记事本,感染完后将记事本重命名为test-oo.exe,计算器也是如此被感染。

3.3 关键技术

3.3.1 重定位

重定位在DLL文件尤其常见,因为DLL文件会加载到不同的位置,如果再按照VA定位会出错。对于病毒程序也是一样,它有相应的代码区感染目标程序,而目标程序有很多,病毒代码写在目标程序的什么位置呢?这就需要病毒代码去定位目标程序的位置,就要利用重定位技术。

  • 关键点:病毒代码寄生在目标程序的位置不固定
  • shellcode类似:通常需要注入远程系统,但这段代码在远程系统什么位置有时不能确定,另外远程系统的环境有时也不能准确感知,故需要使用重定位和API函数自动获取

如下图所示,左边是病毒的RVA,其地址为004010xx;右图是当这段代码插入到另一个Host文件后,变量的实际位置和预期位置出现了差异,而重定位的关键是知道这个差异值是多少,后续遇到的各种变量或地址都可以通过这种差异方式校正。

但是根据Host特征逐一硬编码这种方式并不可取,繁琐且未必准确,所以采用另一种方法,那就是病毒代码运行过程中自我重定位。

假设一个变量相对于病毒起始位置的偏移是9h,那么只要通过求得病毒插入到被感染对象后的病毒起始位置 + 这个变量相对于病毒起始位置的偏移 = 变量在被感染对象中的位置。

那么如何求得插入感染对象后的病毒起始位置呢?由于每个被感染对象的大小都不一样,所以病毒插入到被感染对象后的起始位置也是不一样的。

首先我们要知道一些前置知识。call指令的作用是将下一条指令的地址压入堆栈,然后设置eip寄存器指向要跳转的地址。比如下面这两条指令:

1
2
00C9AF58   .  E8 93020000   call Crackme_.00C9B1F0
00C9AF5D . 6A 01 push 0x1

F7进入函数,此时堆栈窗口栈顶数据为00C9AF5D,也就是call指令的下一条指令的地址;eip寄存器显示00C9B1F0,也就是指向要跳转的地址。

ebp是基址指针寄存器,总是指向系统栈最上面栈帧的底部。eip是指令指针寄存器,存放要执行的下一条指令的地址。

我们已经知道了call指令的功能,接下来就是利用call指令求得插入感染对象后的病毒起始位置。所以怎么利用??变量delta也是在病毒里面的啊,与Var1有什么区别

3.3.2 API函数自获取

PE文件函数节的功能:当调用外部DLL中的API函数时,通过导入函数节这种关系定义出来,系统加载时就能加载对应的DLL文件并找到相应的API函数,再将地址写入到PE文件的导入函数表中,程序运行时就知道从导入函数表中取地址进行调用,这是正常的PE文件运行过程。通常函数节是由目标程序作者编写。

但对于病毒程序来说,它是一段代码,当它感染另外一个程序时,它是否能修改目标程序的导入函数节,而使得其可以服务病毒代码呢?理论上是可以的,但非常复杂,因为要在导入函数节添加东西一定会导致其它结构的变化,需要做很多的修正工作,这也可能破坏原有功能。所以对于病毒来说,它需要自己去获取API函数地址,并且没有导入函数节的支撑,但它又必须要使用很多API函数来实现病毒功能。

  • 关键点:需要使用API函数,但没有导入函数节支撑
  • shellcode类似:需要使用API函数自获取技术来确定注入远程系统的位置

如何获取API函数地址呢?有以下几种方法:

  • 搜寻宿主的导入表获得GetModuleHandleA()函数和GetProcAddress()函数的地址,然后通过它返回系统DLL的基址。因为很多程序都要使用这两个函数,因此在某些情况下是可行的。如果宿主没有使用GetProcAddress(),那就不得不搜寻导出表了

  • 直接获得kernel32.dll的基址,然后再搜寻其导出表获得GetProcAddress()LoadLibraryA()的地址,就能得到任何想调用的函数地址

  • 硬编码调用函数,比如在9X下GetModuleHandleA()的地址一般是BFF7xxxx

第一种和第三种方法存在兼容性问题,第一种方法局限于目标程序使用GetProcAddress()函数;第三种方法存在硬编码的问题,操作系统不同就不能运行了。

  • GetProcAddress()函数:包括两个参数,模块基地址和想要获取的API函数名称,它将动态获得DLL函数的入口地址。
  • LoadLibraryA()函数:将指定的DLL加载到内存中,返回值为DLL文件加载到内存中的基地址。

明确采用第二种方法后,接下来的步骤就是获取kernel32.dll的基地址,通过kernel32.dll模块中的相应结构和特征定位。典型方法:定位kernel32.dll模块中任意一个地址,然后按照模块首地址特征(对齐于1000H,PE文件开始标识MZ)向低地址遍历定位PE文件头。

kernel32.dll中任意一个地址从哪里获得?

  • 程序入口代码执行时栈顶存储的地址
  • SEH(结构化异常处理)链末端
  • PEB(进程环境块)相关数据结构指向了各模块的地址
  • 栈中特定高端地址的数据

注意:不同操作系统存在差别。

这个还是没说明白,看有没有合适的例子吧

3.3.3 目标程序遍历搜索

通常以PE文件格式的文件(如EXE、DLL、SCR等)作为感染目标,其关键点为全盘查找或者部分盘符查找,遍历算法包括递归或非递归。在对目标进行搜索时,通常调用两个API函数:FindFirstFile()FindNextFile()

搜索目标进行感染算法如下:

  1. 指定查找的目录为当前工作目录
  2. 开始搜索文件(*.*)
  3. 该目录搜索完毕?是则返回,否则继续
  4. 找到文件还是目录?目录则调用自身函数(也就是递归),否则继续
  5. 是文件,如符合感染条件,则调用感染模块,否则继续
  6. 搜索下一个文件(FindNextFile()),转到3继续

3.3.4 文件感染

感染的关键是病毒代码能够得到运行,常用方法包括:

  • 选择合适的位置放入病毒代码(已有节、新增节)
  • 将控制权交给病毒代码,如修改程序入口点AddressofEntryPonit,或者在原目标代码执行过程中运行病毒代码(EPO(EntryPoint Obscuring)技术)

同时,病毒代码执行时,程序的正常功能不能被破坏,即控制权的交换。

  • 感染时,记录原始“程序控制点位置”
  • 病毒代码执行完毕之后,返回控制权
  • 避免重复感染,感染标记

3.3.4.1 文件感染分类

  • 插入式感染:将病毒代码插入到Host文件的代码节的中间或前后。这种感染方式会增加代码节的大小,并且可能修改Host程序中的一些参数实际位置,导致Host程序运行失败。
  • 伴随式感染:备份Host程序,用自身替换Host程序。当病毒执行完毕后,再将控制权交给备份程序。

3.3.4.2 感染文件基本步骤

  1. 判断目标文件开始2字节是否为“MZ”

  2. 判断PE文件标记“PE”

  3. 判断感染标记,如果已被感染过则跳出继续执行Host程序,否则继续

  4. 获得数据目录的个数(每个数据目录信息占8个字节)

  5. 得到节表起始位置(数据目录的偏移地址 + 数据目录占用的字节数 = 节表起始地址)

  6. 得到目前最后节表的末尾偏移(紧接其后用于写入一个新的病毒节)

    节表起始位置 + 节的个数 = 目前最后节表的末尾偏移

    每个节表占用的字节数为0x28

  7. 写入节表和病毒节

  8. 修正文件头信息

4. 捆绑释放型

捆绑释放型感染实现起来比较简单,目前很大一部分病毒程序都采用这种方法。捆绑释放型病毒感染时将目标Host程序作为数据存储在病毒体内,当执行病毒程序时,它先执行病毒程序,然后还原并执行Host文件,从而保证被感染的程序本身能正常运行,不会引起异样。

熊猫烧香就属于这一类病毒。如下图,左边是一个正常程序,感染后(右边)会将病毒放在前面,正常程序放在后面。程序运行时,病毒会拿到控制权,但是程序图标会显示前面的病毒程序,这也是一个明显的被感染特征。

  • 优点:编写简单、效率高,可感染自校验程序
  • 缺点:被感染后的程序主体是病毒程序,易被发现(程序叠加+释放执行),程序图标问题

5. 系统感染型

系统感染型本身不对PE文件做任何感染操作,但它感染的目标是操作系统,也是一种寄生类的方式,只是寄生目标有所不同。这类病毒通常为独立个体,不感染系统内的其它文件。

系统感染型病毒存在两个两个关键问题:

  • 如何再次获得控制权——自启动

    由于该程序不感染PE文件,它没有Host文件,所以如何再次获得控制权是一个关键性问题,也是目标很多病毒程序设计时不得不考虑的问题。此时和操作系统自启动相关,病毒必须依赖于该机制再次获得控制权。

  • 如何传播:可移动存储介质(U盘、移动硬盘、刻录光盘等)、网络共享、电子邮件或其它应用。

5.1 控制权再次获取

操作系统启动流程:BIOS -> 硬盘MBR -> 活动分区DBR -> 系统内部

操作系统整个启动流程也是控制权传递的过程,包括现在提出的可信计算,也是对控制权一步一步地校验,控制流程的数据完整性或行为的校验。对于操作系统本身,它的启动方式很多,系统内部包括:

  • 注册表中的键值
  • 系统中的特定位置
  • 配置文件
  • 特定路径的特定文件,如Explorer.exe(显示桌面)

如果病毒本身能很好地结合这套机制,它可以做的事情非常多,并且具有很好的隐蔽性。

其它启动方式:

  • 利用系统自动播放机制Autorun.inf

    比如U盘病毒或光盘病毒就是利用U盘或光盘的自动播放功能。目前,也有一些U盘插入后,不需要用户双击U盘,里面的程序也会启动。

  • 在其它可执行文件嵌入少量触发代码

    • 修改导入函数节启动DLL病毒文件(添加相应结构,初始化代码触发)
    • 在特定PE文件代码段插入触发代码等(只需定位可执行程序并运行)
  • DLL劫持:替换已有DLL文件

    很多应用程序或操作系统执行时,都会去执行DLL文件,如果病毒将自身做成一个DLL文件,同时将系统DLL文件替换。可想而知,系统启动时,它会根据文件名启动的,此时病毒DLL文件就会拿到控制权,如果拿到控制权之后再进一步装载原始DLL文件,这样系统的本身机制也不会受到影响,隐蔽性更强。该方法非常常见,甚至有一些病毒程序将反病毒软件可依赖的DLL文件替换。

5.2 病毒的传播方式

一切可对外交互的渠道都可传播,包括:

  • 各类存储设备(软盘、光盘、U盘、移动硬盘、智能设备)
  • 各类网络通信方式(QQ、MSN、Email、淘宝旺旺、微信、微博等)
  • 各类网络连接方式(有线、WIFI、蓝牙等)
  • 各类网络应用(迅雷、BT等)

邮件蠕虫越来越常见,其中以邮件附件的形式进行传播较多。附件中可能包含病毒包括exe文件、rar文件、pdf文件、doc文件、xls文件、jpg文件、chm文件等。下图是一个包含病毒的邮件附件,显示为一个word文档,后缀名doc,图标显示也是word。但它的真实后缀是scr(屏保),它其实是利用了一种技术,在文件名里插入翻转字符,然后将翻转字符之后的其它字符进行了翻转,它的完整文件名应该是“5月TW行lmcod.scr”。这也是一种欺骗性很强的攻击手法。

再补充一个通过可移动存储设备传播的非感染式病毒,即Autorun.inf。下图显示了Autorun.inf文件,如果文件存在U盘根目录,当我们双击这个U盘时,它就会触发对应的病毒,如果选择U盘盘符右键打开或打开资源管理器,这时进入的也是病毒程序。当然下面的演示是计算器程序。

1
2
3
4
5
6
7
[AutoRun]
open=mspaint.exe
shell\open=打开(&O)
shell\open\Command=calc.exe
shell\open\Default=1
shell\explore=资源管理器(&X)
shell\explore\Command=calc.exe

还有一类是伪装的文件夹,如下图所示的photo.exe文件,当Windows操作系统默认不显示.exe时,它就能伪装成文件夹,当我们双击之后就会运行病毒,同时可以打开某个文件夹进行隐蔽。

最后,补充“摆渡”知识点,这种攻击行为经常发生在一些具有特殊目的病毒程序身上。期望通过可移动的媒介来渗透一些平时不联网的电脑中,并从中获取数据,利用摆渡的方式植入病毒或木马到内网,比较典型的案例就是Stuxnet。

下图展示了Stuxnet震网事件的漏洞利用过程和启动方式,传统的Autorun方式很容易被禁止掉,而Stuxnet利用的是lnk漏洞(MS10-046),它会在目标U盘下放入lnk快捷方式及病毒程序(如DLL文件)。不管通过什么方式进入U盘,lnk文件都会被解析从而触发漏洞,导致U盘中的病毒程序被执行。