【转】PE文件的修改和感染
2008-04-29 08:20
既然已经能够搜索磁盘及网络共享文件中的所有文件,要实现寄生,那么自然下一步就是对搜索到的PE文件进行感染了。感染PE的很重要的一个考虑就是将病毒代码写入到PE 文件的哪个位置。读写文件一般利用Win32 API CreateFile、CreateFileMapping、MapViewOfFile等API以内存映射文件的方式进行,这样可以避免自己管理缓冲的麻烦,因而为较多病毒所采用。为了能够读写具有只读属性的文 件,病毒在操作前首先利用GetFileAttributes 获取其属性并保存,然后用SetFileAttributes将文件的属性修改为可写,在 感染完毕后再恢复其属性值。 一般说来,有如下几种感染PE文件的方案供选择:
a)添加一个新的节。将病毒代码写入到新的节中,相应修改节表,文件头中文件大小等属性值。由于在PE尾部增加了一个节,因此较容易被用户察觉。在某些情况下,由于原PE头部没有足够的空间存放新增节的节表信息,因此还要对其它数据进行搬移等操作。鉴于上述问 题,PE 病毒使用该方法的并不多。
b)附加在最后一个节上。修改最后一个节节表的大小和属性以及文件头中文件大小等属性值。由于越来越多的杀毒软件采用了一种尾部扫描的方式,因此很多病毒还要在病毒代码之后附加随机数据以逃避该种扫描。现代PE 病毒大量使用该种方式。
c)写入到PE文件头部未用空间各个节所保留的空隙之中。PE 头部大小一般为1024 字节,有5-6 个节的普通PE文件实际被占用部分一般仅为600 字节左右,尚有400 多个字节的剩余空间可以利用。PE文件各个节之间一般都是按照512 字节对齐的,但节中的实际数据常常未完全使用全部的512字节,PE文件的对齐设计本来是出于效率的考虑,但其留下的空隙却给病毒留下了栖身之地。这种感染方式感染后原PE 文的总长度可能并不会增加,因此自CIH 病毒首次使用该技术以来,备受病毒作者的青睐。
d)覆盖某些非常用数据。如一般exe文件的重定位表,由于exe一般不需要重定位,因此可以覆盖重定位数据而不会造成问题,为保险起见可将文件头中指示重定位项的DataDirectory 数组中的相应项清空,这种方式一般也不会造成被感染文件长度的增加。因此很多病毒也广泛使用该种方法。
e)压缩某些数据或代码以节约出存放病毒代码的空间,然后将病毒代码写入这些空间,在程序代码运行前病毒首先解压缩相应的数据或代码,然后再将控制权交给原程序。该种方式一般不会增加被感染文件的大小,但需考虑的因素较多,实现起来难度也比较大。用的还不多。 不论何种方式,都涉及到对PE头部相关信息以及节表的相关操作,我们首先研究一下PE的修改,即如何在添加了病毒代码后使得PE文件仍然是合法的PE文件,仍然能够被系统加载器加载执行。
PE文件的每个节的属性都是由节表中的一个表项描述的,节表紧跟在IMAGE_NT_HEADERS后面,因此从文件偏移0x3C 处的双字找到IMAGE_NT_HEADERS 的起始偏移,再加上IMAGE_NT_HEADERS的大小(248字节)就定位了节表的起始位置,每个表项是一个IMAGE_SECTION_HEADER结构:
typedef struct _IMAGE_SECTION_HEADER { BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; // 节的名字 union { DWORD PhysicalAddress; DWORD VirtualSize; // 字节计算的实际大小 } Misc; DWORD VirtualAddress; // 节的起始虚拟地址 DWORD SizeOfRawData; // 按照文件头FileAlignment // 对齐后的大小 DWORD PointerToRawData; // 文件中指向该节起始的偏移 DWORD PointerToRelocations; DWORD PointerToLinenumbers; WORD NumberOfRelocations; WORD NumberOfLinenumbers; DWORD Characteristics; // 节的属性 } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER; |
节表项的数目由IMAGE_NT_HEADERS的NumberOfSections成员确定。由节表中的起始虚拟地址以及该节在文件中的位置就可以换算加载后内存虚拟地址和文件中地址之间的映射关系。添加一个节则需要修改该节表数组, 在其中增加一个表项, 然后相应修改 NumberOfSections 的数目。值得注意的是,某些PE文件现存节表后面可能紧跟着其它数据,如bound import 数据,这时就不能简单地增加一个节表项,需要先移动这些数据并修改相应的结构后才能增加节,否则PE文件将不能正常执行。由于很多病毒是自我修改的,因此节属性通常设置为E000XXXX,表示该节可读写执行,否则就需要在病毒的开始处调用VirtualProtect之类的API动态修改内存页的属性了。
由上述节表的定义还可以看到每个节的实际数据都是按照文件头中FileAlignment 对齐的,这个大小一般是512,因此每个节可能有不超过512字节的未用空间(SizeOfRawData-VirtualSize),这恰好给病毒以可乘之机,著名的CIH病毒首先采用了这种技术,不过问题是每个节的空隙大小是不定的,因此就需要将病毒代码分成若干部分存放,运行时再通过一段代码组合起来,优点是如果病毒代码较小则无需增加PE的大小,隐蔽性较强。如果所有节的未用空间仍不足以容纳病毒代码,则可新增节或附加到最后一个节上。
附加到最后一个节上是比较简单的,只要修改节表中最后一个节的VirtualSize 以及按FileAlignment 对齐后的SizeOfRawData成员即可。当然在上述所有修改节的情况中,如果改变了文件的大小,都要修正文件头中SizeOfImage这个值的大小,该值是所有节和头按照SectionAlignment 对齐后的大小。
这里有两个问题值得注意,第一问题就是对WFP(Windows File Protection)文件的处理,WFP机制是从Windows 2000 开始新增的保护系统文件的机制,若系统发现重要的系统文件被改变,则弹出一个对话框警告用户该文件已被替换。当然有多种方法绕过WFP 保护,但对病毒而言,更简单的方法就是不感染在WFP 列表中的系统文件。
可使用sfc.dll的导出函数SfcIsFileProtected判断一个文件是否在该列表中,该API 的第一个问 匦胛?,第二个参数是要判断的文件名,若在列表中返回非0值,否则返回0。
另外一个问题就是关于PE文件的校验。大部分PE文件都不使用文件头中的CheckSum域的校验和值,不过有些PE文件,如关键的系统服务程序文件以及驱动程序文件则该值必须正确,否则系统加载器将拒绝加载。PE 头部的CheckSum 可以使用Imagehlp.dll的导出函数 CheckSumMappedFile计算,也可以在将该域清0后按照如下简单的等价算法计算:
如果PE文件大小是奇数字节,则以0补足,使之按偶数字节。将PE文件头的CheckSum 域清0,然后以两个字节为单位进行adc运算,最后和将该累加和同文件实际大小进行adc运算即得到校验和的值。下面的cal_checksum过程假设esi 已经指向PE文件头,文件头部CheckSum域已经被清0,CF 标志位已经被复位:
;调用示例: ;clc ;push pe_fileseize ;call cal_checksum cal_checksum: adc bp,word [esi] ;初始esi指向文件头,ebx 中保 存的是文件大小 inc esi inc esi loop cal_checksum mov ebx,[esp+4] adc ebp,ebx ;ebp 中存放的就是PE 的校验和 ret 4 |
除了PE头部的校验和之外,很多程序自身也有校验模块,如Winzip 和Winrar 的自解压文件,如果被感染,将造成无法正常解压缩。因此对于类似的PE文件,病毒应尽量不予感染。
|
来源:oschina
链接:https://my.oschina.net/u/1777508/blog/603580