0x00 概述
之前分享了关于病毒的的一些小知识,有坛友对PE文件病毒比较感兴趣,于是,把之前学过的PE文件型病毒知识整理分享一下。
0x10 PE文件病毒
是Windows系统病毒之一,在安全模式下可以删除。
0x11 病毒特点
该病毒是将可执行文件的代码中程序入口地址改为病毒的程序入口,这样就会导致用户在运行的时候执行病毒文件,这是黑客比较常用的方式。
0x20 如何判定PE文件
:如何判断一个文件是否为PE文件?
1、检验文件头部第一个字的值是否等于MZ,如果是,则DOS头有效。
2、用DOS头的字段e_lfanew来定位PE头。
3、比较PE透的第一个双字的值是否等于45500000H(PE\0\0)。
0x21PE文件结构
PE文件结构图如下:
0x22 判断是否为PE文件
汇编代码实现
#定义局部变量
IMAGE_DOS_HEADER dos_header;
IMAGE_NT_HEADER nt_header;
CFile fp;
fp.Read(&dos_header,sizeof(dos_header));
If(dos_header.e_magic!=0x5A4D) #用DOS头的字段e_lfanew来定位PE头。
{…..}
fp.Seek(dos_header.e_lfanew,CFile::begin);
if(nt_header.Signature!=0x00004550) #比较PE透的第一个双字的值是否等于45500000H(PE\0\0)。
0x30 PE病毒编写的关键
定位、获取API函数、搜索目标文件、感染、破坏。
0x31 病毒编写的难点
病毒是在宿主的运行环境下运行,所以无法像在自己本身的运行环境下一样访问自己的静态(全局)变量的数据和直接调用系统API。
0x32 病毒感染PE文件的基本方法
(1)判断目标文件开始的两个字节是否为“MZ”;
(2)判断PE文件标记“PE”;
(3)判断感染标记,如果已被感染过则跳过这个文件,否则继续;
(4)获得Directory(数据目录)的个数,每个数据目录信息占8个字节;
(5)得到节表起始位置:Directory的地址+数据目录占用的字节数=节表起始位置;
(6)得到目前最后节表的末尾偏移(紧接其后用于写入一个新的病毒节):
节表起始位置+节的个数×(每个节表占用的字节数28H)=目前最后节表的末尾偏移
(7)开始写入新的节表项
①写入节名(8字节);
②写入节的实际字节数(4字节);
③写入新节在内存中的开始偏移地址(4字节),同时可以计算出病毒入口位置:
上节在内存中的开始偏移地址+(上节大小/节对齐+1)×节对齐
④写入新节(即病毒节)在文件中对齐后的大小;
⑤写入新节在文件中的开始位置:
上节在文件中的开始位置+上节对齐后的大小(8) 修改映像文件头中的节表数目
(9)修改AddressOfEntryPoint(即程序入口点指向病毒入口位置),同时保存旧的AddressOfEntryPoint,以便返回HOST继续执行。
(10)更新SizeOfImage(内存中整个PE映像尺寸=原SizeOfImage+病毒节经过内存节对齐后的大小);
(11)写入感染标记(可以放在PE头中);
(12)写入病毒代码到新节指向的文件偏移中。
0x40 重定位
0x41 病毒感染重定位原因?
病毒要用到变量,在病毒感染host程序后,由于它依附到host程序中的位置各不
相同,因此病毒随着host载入内存后,病毒中的各个变量在内存中的位置会随着host程序的位置而发生变
化,因此,病毒程序要能正常使用变量。必须采用重定位技术。
0x42 重定位步骤
1.call指令跳转到下一条指令,使下一条指令感染后在内存中的实际地址进栈。
2.用pop EXX,[ESP]
指令取出栈顶内容,得到感染后下一条指令内存中的实际地址Base。
3.varstart为感染前call指令的下一条指令地址,varlable为感染前变量地址,则感染后var实际地址为(Base-Offset varstart)(基
地址)+Offset varlable
0x50 API函数获取
0x51 为什么要获取API函数地址
Win32下的系统功能调用一般通过调用动态连接库中的API函数实现。
API函数调用的实质是找到函数地址,然后
call MessageBox(0,”123”,0,0);
Call 0x7c91001c; (WIN XP SP3)`
0x60 病毒获取API函数地址
0x61 静态方式
调用时,根据函数名查引入表,就可以获取该函数的地址。
0x62 动态方式
使用函数LoadLibrary装载需要调用的函数所在的dll文件,获取模块句柄。然后调用GetProcAddress获取需要调用的函数地址。这种方式是在需要调用函数时才将函数所在的模块调入到内存中,同时也不需要编译器为函数在引入表中建立相应的项。
这里主要注意下面2个特殊的API
LoadLibrary加载一个DLL,返回DLL地址
GetProcAddress通过DLL地址和API函数名获得API函数的地址
0x63 得到模块kernel32的地址方法
由于程序入口点是被kernel32某个函数调用的,所以这个调用函数肯定在kernel32的地址空间上。那么我们只要取得这个返回地址,就得到了一个kernel32空间中的一个地址。
通过这个地址,我们可以从高地址向低地址方向进行搜索,通过PE标志的判断,搜索到kernel32模块的基地址!
大致流程如下:
取栈顶值到寄存器A(KERNEL32中的一个地址)
A = A 与 0FFFFF000h(分配粒度是1000h,基地址必然在xxxx000h处)
循环:
如果[A] == IMAGE_DOS_SIGNATURE(判断DOS头标志)
{B = A;B =B+e_lfanew; 指向PE标志
如果[B] ==IMAGE_NT_SIGNATURE (判断“PE\0\0”标志)
{ 跳出循环;(找到,退出!)}
A = A - 01000h;
循环结束
代码实现:
GetKBase:
mov edi ,[esp] ;取栈顶值,就是KERNEL32中的一个地址
and edi,0FFFFF0000h ;分配粒度是10000h,基地址必然在xxxx0000h处
.while TRUE
.if WORD ptr [edi] == IMAGE_DOS_SIGNATURE ;判断DOS头标志
mov esi,edi
add esi,DWORD ptr [esi+03Ch] ;esi指向PE标志
.if DWORD ptr [esi] ==IMAGE_NT_SIGNATURE ;判断PE标志
.break ;若正确,则跳出循环
.endif
.endif
sub edi, 010000h
.endw
mov hKernel32,edi ;把找到的KERNEL32.DLL的基地址保存起来
在得到了Kernel32的模块地址以后,就可以搜索他的导出表得到GetProcAddress和LoadLibrary两个API函数的地址。
对这两个API函数的联合调用就可以得到WIN32 应用层上任何所需要的API函数地址了。
0x64 函数名地址查找
通过函数名称查找函数名地址,具体如下:
1、定位到PE文件头。
2、从PE文件头中的可选文件头中取出数据目录表的第一个数据目录,得到导出表的地址。
3、从导出表的NumberOfNames字段得到以命名函数的总数,并以这个数字做微循环的次数来构造一个循环。
4、从AddressOfNames字段指向的函数名称地址表的第一项开始,在循环中将每一项定义的函数名与要查找的函数名比较,如果没有任何一个函数名符合,说明文件中没有指定名称的函数。
如果某一项定义的函数名与要查找的函数名符合,那么记住这个函数名在字符串地址表中的索引值(如x),然后在AddressOfNameOrdinals指向的数组中以同样的索引值x去找数组项中的值,假如该值为y。
5、以y值作为索引值,在AddressOfFunctions字段指向的函数入口地址表中获取的RVA就是函数的入口地址,当函数被装入内存后,这个RVA值加上模块实际装入的基址(ImageBase),就得到了函数真正的入口地址。
0x65 导出表中获取API函数地址
exportD=hKernel32.OptionalHeader.DataDirectory.VirtualAddress + hKernel32;
int i,iA;
for (i=0;i<exportD.NumberOfNames;i++)
{
p=hKernel32+exportD.AddressOfNames[i]; If (!strcmp(p,name)) break;
}
WORD *pw = (WORD*)(hKernel32 + exportD.AddressOfNamesOrdinals);
iA = pw[i];
DWORD *pA = (DWORD*)(hKernel32 + exportD.AddressOfFunctions);
DWORD address = pA[iA];
0x70 目标文件搜索
PE病毒通常以PE文件格式的文件(如EXE、SCR、DLL等)作为感染目标。在对目标进行搜索时一般采用两个关键的API函数:
FindFirstFile
FindNextFile
其一般搜索“.exe”、“.scr”等文件进行感染。在算法上可以采用递归或者非递归算法对所有盘符进行搜索。
0x71 文件感染
一个被病毒感染的HOST程序通常首先执行病毒代码,然后执行HOST程序的正常代码。这既保证病毒首先获得控制权,同时也不影响HOST程序的正常执行。
另外也可能在HOST程序执行的过程中调用病毒代码,例如病毒的EPO技术中就采用这种方式。
0x80 病毒对PE文件的修改方式
主要有这三种方式:添加节;扩展节;插入节。
0x81 添加节方式修改PE
在文件的最后建立一个新节,在节表结构的后面建立一个节表,用以表述该节。入口地址修改为病毒所在节.
1、把病毒代码追加到文件尾部。
2、在节表中增加一个section header各项数据填写正确(VirtualSize,VirtualAddress,PointerToRawData…..)。
3、在FILEHEADER中修改节表项数目: +1。
4、重新计算SizeofHeaders,并替换原值。
5、重新计算SizeofImage,并替换原值。
6、记录未感染时的AOEP(入口地址),因为在病毒代码结束时要让宿主程序正常执行。所以要先记录AOEP,在病毒程序结束后JMP跳到宿主程序的AOEP。
7、修改OptionalHEADER中的AddressOfEntryPoint,让它指向新加节的入口代码
添加节修改PE 结构图如下
0x82 插入节方式修改PE
这种方式不增加节的个数和文件长度,病毒搜寻到一个可执行文件后,分析每个节,查询节的空白空间是否可以容纳病毒代码,若可以,则感染之。CIH病毒就是采用这种方法感染可执行文件的。
SizeOfRawData - VirtualSize
插入节方式修改PE结构图如下
0x83 扩展节修改PE
先把病毒代码追加到最后一个节的尾部。修改节表中最后一项section header并增加 SizeOfRawData 的大小和内存布局大小。
扩展节修改PE结构图如下:
0x90 Show me the code
最后分享2个实验,感兴趣的坛友可以动手实践下
(1)打造最小的PE文件。
(2)动态获取API函数地址及观察被PE文件型病毒感染的文件
(3)建议在吾爱虚拟机中实验。
链接:https://pan.baidu.com/s/1sna9Wdv 密码:ilgd
压缩包提取密码:52pojie