PE型病毒编写总结

时间:2024-02-22 18:10:25
关键词(预备知识): 
PE文件 
PE文件头 
PE文件Import table(引入表) 
PE文件Section table (节表) 
PE文件入口地址 
RVA 地址 
Win32 宏汇编语言 
内存映射文件 

病毒功能: 
自动搜索硬盘上的PE文件(包括exe和dll),在文件中插入病毒体,使该PE文件在执行时先执行病毒体后执行原有程序。 

病毒流程(原始文件): 
1.查找LoadLibrary和GetProcAddress函数的地址,将其地址保存在数据段两个变量中。 
2.将数据段的那两个保存函数地址的变量的地址保存在代码段的存储区中(以下病毒体开始) 
3.根据代码段存储区中保存的值调用LoadLibrary和GetProcAddress得到病毒所需要的函数的入口地址,并保存在代码段存储区中 
4.搜索硬盘上的所有文件 
5.检查该文件是否是合法的PE文件,如果不是,转1 
6.检查该文件是否已经被感染,如果是,转1 
7.根据节表计算病毒体大小和该PE文件代码段的空隙,如果病毒体不可以完全插入空隙则转1 
8.寻找PE文件的引入表,查找LoadLibrary和GetProcAddress函数如果没有找到,转1 
9.记录这两个函数的地址在PE文件装载时被存放的位置 
10.更新代码段存储区中原来用来存放这两个函数的地址的空间的地址的单元 
11.保存旧的入口地址 
12.根据代码段的开始偏移和代码段大小计算新的入口地址 
13.更新PE文件的入口地址 
14.修改代码段的属性成为可读可写可执行 
15.写入标志(PUKE) 
16.计算病毒代码写入的偏移 
17.将病毒体写入PE文件 
18.计算从病毒代码跳回源代码的偏移 
19.在病毒的末尾写入跳转指令 
20.转1 
编写过程中遇到的一些主要问题: 
数据的存储 
一 般情况下一个程序中需要用到的数据(比如一些常量,字符串,缓冲区)需要存放在数据段中。但是由于该病毒采用了插入技术,不能够修改源PE文件的段结构和 数据区,所以不方便保存自己的数据段。希望数据能够紧凑的和代码连接在一起,但是如果在代码段中定义数据,代码段又是不可写的,如果不加修改的使用,肯定 会出现Exception导致病毒体无法正常运行下去。PE文件的每一个段都在PE的段表中占有一项数据结构,其中有一个元素规定了该段的一些属性,比如 可读,可写,可执行等等,经过试验,只要修改代码段的属性到可写,我们就能够像操作数据段一样操作代码段,即可以任意读写代码段。(相关的知识请查阅PE 文件的格式的文档),所以我们在感染每一个PE文件之后,为了确保植入的病毒体能够正确的运行,我们要修改其代码段的属性为可写。该问题解决。 
动态获取函数地址 
在病毒的执行过程中,需要调用一些I/O的API来进行对文件的操作,如果在编写病毒源文件的时候直接使用函数名来调用,比如 
Invoke CreateFile, …… 
或者 
Call CreateFile 
那 么由病毒源文件编译出来的二进制文件使用的将是相对于病毒源可知性文件的相对地址和绝对地址,比如Call CreateFile变成了 call dword ptr[4xxxxx],4xxxxx是用来存放该函数真实调用地址的地址的地址,在401xxx处是一条 jmp dword ptr[yyyyyy]的指令,yyyyy是存放该函数入口地址的地址,这条指令就直接跳转到要调用的函数的入口处。但是植入其他的 文件之后,存放该函数入口地址的地址发生了改变,并且存放该函数真实调用地址的地址的地址也发生了改变,病毒体不能正确的调用到函数,导致程序崩溃。解决 这个问题经过了两个阶段。 
第一个阶段,我设想在使用LoadLibrary和GetProcAddress两个函数将所有需要的函数的地址全部 在程序中动态拿到并保存在代码段的某一处,每一次掉用的时候都用call dword ptr[esi+xxx]来调用,其中esi指向病毒体头,xxx 是存储单元相对于病毒体头的偏移。这种方法使用函数的绝对加载地址来调用函数,避免了上述问题,并在win2kServer上测试通过。但是这种方法无法 动态得到LoadLibrary和GetProcAddress两个函数,也就是说这调用两个函数动态获得其它函数地址的这段代码是不能写入病毒体的,所 以产生的病毒代码只能使用病毒第一次运行时获得的函数的地址,并在传播中一直使用,但是windows的不同版本中,函数的加载地址是不同的,通过收集两 个mm的机器上的函数加载地址(win2kPro)证实了我的担心,所以使用这种方法编写出来的病毒是无法在Win2kPro/Win98/Winxp或 者Win的其他版本上传播的,除非他们在他们的平台上运行病毒的源可执行文件。 
第二个阶段,实际上从第一阶段可以看出,我们现在所面临的问题只 是怎么在程序中动态获得LoadLibrary和GetProcAddress两个函数的加载地址,呵呵,当然必须使用LoadLibrary和 GetProcAddress两个函数来获得了,我们似乎陷入了一个死循环之中,但是病毒还是要写的,跨平台传播的特性还是要有的否则就太没有意思了。解 决这个问题的关键在于PE文件的另一个重要的数据结构—引入表(import table)引入表中记录了该PE文件所使用的动态连接库的信息,当然包括 kernel32.dll以及它里面的函数LoadLibrary和GetProcAddress(由于这两个函数和这个动态库的重要性,大部分的PE文 件都要用到它)。在病毒的源文件中我们可以使用函数名来调用LoadLibrary和GetProcAddress,用来获得他们自身的地址,将其存放在 数据段(其实放在哪里都可以)的两个单元A,B中在代码段的某处C,D中存放数据段A,B的地址: 
mov eax, offset A 
mov C, eax 
由于我们从引入表中只能拿到存放这两个函数的地址的存储单元的RVA地址,在感染的时候我们将用那个RVA地址填入C,D,然后使用 
mov eax, dword ptr[C];eax=A的地址 
call dword ptr[eax];即调用eax所指的地址的里存放的地址 
这 样我们就调到了LoadLibrary和GetProcAddress来动态获取其他的函数加载地址。用LoadLibrary和 GetProcAddress获取LoadLibrary和GetProcAddress函数的地址的那部分代码是不放入病毒体的,而调用这两个函数获得 其它函数的地址是在病毒体内的,所以为了保持病毒体的正确运行,我们在编写病毒源文件的时候要让这一段代码和病毒传播后的行为一致,所以我们才把获得地址 存在AB中再在CD中存AB的地址。如果这个文件没有用到kernel32.dll或者没有用到LoadLibrary和GetProcAddress这 两个函数的话,我们就放过吧(呵呵,不要太贪),当我们找到在PE加载的时候存放这两个函数的绝对地址的存储单元的地址(RVA)之后,我们就把这两个存 储单元的RVA地址存入C,D中,使用上面的那段代码我们就可以得到或者加载任何我们想使用的API。 
动态定位各存储单元 
在编写源文 件的时候,我们用到了许多标号来表示存储单元,比如handle_来保存FindfirstA的返回值,还有很多用于跳转的标号比如start: 等等, 如果源文件使用了这些标号来读写某个存储单元的值,在编译后对这些标号的调用自动被转换成为了绝对地址(也是RVA地址,但是是绝对的RVA 地址,和当 前的地址无关),这样,如何在别人的进程空间里准确定位自己的加载基址并使用这个基址来访问自己的存储单元就成了一个问题。但是这个问题很简单: 
call start 
……;一些数据定义,就是将数据段里的数据定义移到代码段里的那一部分,执行 
……;的时候直接跳过 
…… 
start: 
pop esi 
这样esi中就有了我们的病毒的数据定义的第一个字节的地址,在使用的时候我们使用esi+偏移的方式来使用这些数据单元,保证了不论在何处都能够准确定位。 
其他问题 
解决了以上的问题之后,写一个PE型病毒应该是轻而易举的事情了。但是还有一些问题解决了之后能够大大增强病毒的活性,列举如下: 
1.无法在除了代码段的其他各段插入病毒体并运行 
2.可以修改只读文件的属性 
3.病毒体太大