Win32病毒入门(一)

 

【pker / CVC.GB】

1、声明
-------

本文仅仅是一篇讲述病毒原理的理论性文章,任何人如果通过本文中讲述的技术或利用本文
中的代码写出恶性病毒,造成的任何影响均与作者无关。

2、前言
-------

病毒是什么?病毒就是一个具有一定生物病毒特性,可以进行传播、感染的程序。病毒同样
是一个程序,只不过它经常做着一些正常程序不常做的事情而已,仅此而已。在这篇文章中
我们将揭开病毒的神秘面纱,动手写一个病毒(当然这个病毒是不具有破坏力的,仅仅是一
个良性病毒)。

在网上有很多病毒方面的入门文章,但大部分都很泛泛,并不适合真正的初学者。真正的高
手没有时间也不屑于写这样一篇详细的入门文章,所以我便萌发了写这样一篇文章的冲动,
一来是对自己的学习进行一下总结,二来也是想让像我一样的初学者能少走一些弯路。如果
你有一定的病毒编写基础,那么就此打住,这是一篇为对病毒编程完全没有概念的读者编写
的,是一篇超级入门的文章 :P

3、对读者的假设
---------------

没错,这是一篇完整、详细的入门文章,但是如果读者对编程还没有什么认识我想也不可能
顺利地读下去。本文要求读者:

1)  有基本的C/C++语言知识。因为文章中的很多结构的定义我使用的是C/C++的语法。

2)  有一定的汇编基础。在这篇文章中我们将使用FASM编译器,这个编译器对很多读者来说
    可能很陌生,不过没关系,让我们一起来熟悉它 :P

3)  有文件格式的概念,知道一个可执行文件可以有ELF、MZ、LE、PE之分。

好了,让我们开始我们的病毒之旅吧!!!

4、PE文件结构
-------------

DOS下,可执行文件分为两种,一种是从CP/M继承来的COM小程序,另一种是EXE可执行文件,
我们称之为MZ文件。而Win32下,一种新的可执行文件可是取代了MZ文件,就是我们这一节
的主角 -- PE文件。

PE(Portable Executable File Format)称为可移植执行文件格式,我们可以用如下的表
来描述一个PE文件:

+-----------------------------+     --------------------------------------------
|         DOS MZ文件头        |                                         ^
+-----------------------------+                                      DOS部分
|            DOS块            |                                         v
+-----------------------------+     --------------------------------------------
|           PE/0/0            |                                         ^
+-----------------------------+                                         |
|    IMAGE_FILE_HEADER结构    |                                      PE文件头
+-----------------------------+                                         |
| IMAGE_OPTIONAL_HEADER32结构 |                                         v
+-----------------------------+     --------------------------------------------
|                             |-----+                                   ^
|                             |-----+-----+                             |
|  n*IMAGE_SECTION_HEADER结构 |-----+-----+-----+                     节表
|                             |-----+-----+-----+-----+                 |
|                             |-----+-----+-----+-----+-----+           v
+-----------------------------+     |     |     |     |     |     --------------
|           .text节           |<----+     |     |     |     |           ^
+-----------------------------+           |     |     |     |           |
|           .data节           |<----------+     |     |     |           |
+-----------------------------+                 |     |     |           |
|           .idata节          |<----------------+     |     |        节数据
+-----------------------------+                       |     |           |
|           .reloc节          |<----------------------+     |           |
+-----------------------------+                             |           |
|             ...             |<----------------------------+           v
+-----------------------------+     --------------------------------------------

好了,各位读者请准备好,我们要对PE格式进行一次超高速洗礼,嘿嘿。

PE文件的头部是一个DOS MZ文件头,这是为了可执行文件的向下兼容性设计的。PE文件的DOS
部分分为两部分,一个是MZ文件头,另一部分是DOS块,这里面存放的是可执行代码部分。还
记得在DOS下运行一个PE文件时的情景么:“This program cannot be run in DOS mode.”。
没错,这就是DOS块(DOS Stub)完成的工作。下面我们先来看看MZ文件头的定义:

typedef struct _IMAGE_DOS_HEADER  {      // DOS .EXE header
    WORD   e_magic;                     // Magic number
    WORD   e_cblp;                      // Bytes on last page of file
    WORD   e_cp;                        // Pages in file
    WORD   e_crlc;                      // Relocations
    WORD   e_cparhdr;                   // Size of header in paragraphs
    WORD   e_minalloc;                  // Minimum extra paragraphs needed
    WORD   e_maxalloc;                  // Maximum extra paragraphs needed
    WORD   e_ss;                        // Initial (relative) SS value
    WORD   e_sp;                        // Initial SP value
    WORD   e_csum;                      // Checksum
    WORD   e_ip;                        // Initial IP value
    WORD   e_cs;                        // Initial (relative) CS value
    WORD   e_lfarlc;                    // File address of relocation table
    WORD   e_ovno;                      // Overlay number
    WORD   e_res[4];                    // Reserved words
    WORD   e_oemid;                     // OEM identifier (for e_oeminfo)
    WORD   e_oeminfo;                   // OEM information; e_oemid specific
    WORD   e_res2[10];                  // Reserved words
    LONG   e_lfanew;                    // File address of new exe header
}
 IMAGE_DOS_HEADER,  * PIMAGE_DOS_HEADER;

其中e_magic就是鼎鼎大名的‘MZ’,这个我们并不陌生。后面的字段指明了入口地址、堆
栈位置和重定位表位置等。我们还要关心的一个字段是e_lfanew字段,它指定了真正的PE文
件头,这个地址总是经过8字节对齐的。

下面让我们来真正地走进PE文件,下面是PE文件头的定义:

typedef struct _IMAGE_NT_HEADERS  {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;
}
 IMAGE_NT_HEADERS32,  * PIMAGE_NT_HEADERS32;

 PE文件头的第一个双字是00004550h,即字符P、E和两个0。后面还有两个结构:

 

typedef struct _IMAGE_FILE_HEADER  {
    WORD    Machine;
    WORD    NumberOfSections;
    DWORD   TimeDateStamp;
    DWORD   PointerToSymbolTable;
    DWORD   NumberOfSymbols;
    WORD    SizeOfOptionalHeader;
    WORD    Characteristics;
}
 IMAGE_FILE_HEADER,  * PIMAGE_FILE_HEADER;

typedef struct _IMAGE_OPTIONAL_HEADER 
{
    
//
    
// Standard fields.
    
//

    WORD    Magic;
    BYTE    MajorLinkerVersion;
    BYTE    MinorLinkerVersion;
    DWORD   SizeOfCode;
    DWORD   SizeOfInitializedData;
    DWORD   SizeOfUninitializedData;
    DWORD   AddressOfEntryPoint;
    DWORD   BaseOfCode;
    DWORD   BaseOfData;

    
//
    
// NT additional fields.
    
//

    DWORD   ImageBase;
    DWORD   SectionAlignment;
    DWORD   FileAlignment;
    WORD    MajorOperatingSystemVersion;
    WORD    MinorOperatingSystemVersion;
    WORD    MajorImageVersion;
    WORD    MinorImageVersion;
    WORD    MajorSubsystemVersion;
    WORD    MinorSubsystemVersion;
    DWORD   Win32VersionValue;
    DWORD   SizeOfImage;
    DWORD   SizeOfHeaders;
    DWORD   CheckSum;
    WORD    Subsystem;
    WORD    DllCharacteristics;
    DWORD   SizeOfStackReserve;
    DWORD   SizeOfStackCommit;
    DWORD   SizeOfHeapReserve;
    DWORD   SizeOfHeapCommit;
    DWORD   LoaderFlags;
    DWORD   NumberOfRvaAndSizes;
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
}
 IMAGE_OPTIONAL_HEADER32,  * PIMAGE_OPTIONAL_HEADER32;

我们先来看看IMAGE_FILE_HEADER。Machine字段指定了程序的运行平台。

NumberOfSections指定了文件中节(有关节的概念后面会有介绍)的数量。

TimeDataStamp是编译次文件的时间,它是从1969年12月31日下午4:00开始到创建为止的总
秒数。

PointerToSymbolTable指向调试符号表。NumberOfSymbols是调试符号的个数。这两个字段
我们不需要关心。

SizeOfOptionalHeader指定了紧跟在后面的IMAGE_OPTIONAL_HEADER结构的大小,它总等于
0e0h。

Characteristics是一个很重要的字段,它描述了文件的属性,它决定了系统对这个文件的
装载方式。下面是这个字段每个位的含义(略去了一些我们不需要关心的字段):

#define  IMAGE_FILE_RELOCS_STRIPPED           0x0001   //  文件中不存在重定位信息
#define  IMAGE_FILE_EXECUTABLE_IMAGE          0x0002   //  文件是可执行的
#define  IMAGE_FILE_LARGE_ADDRESS_AWARE       0x0020   //  程序可以触及大于2G的地址
#define  IMAGE_FILE_BYTES_REVERSED_LO         0x0080   //  小尾方式
#define  IMAGE_FILE_32BIT_MACHINE             0x0100   //  32位机器
#define  IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP   0x0400   //  不可在可移动介质上运行
#define  IMAGE_FILE_NET_RUN_FROM_SWAP         0x0800   //  不可在网络上运行
#define  IMAGE_FILE_SYSTEM                    0x1000   //  系统文件
#define  IMAGE_FILE_DLL                       0x2000   //  文件是一个DLL
#define  IMAGE_FILE_UP_SYSTEM_ONLY            0x4000   //  只能在单处理器计算机上运行
#define  IMAGE_FILE_BYTES_REVERSED_HI         0x8000   //  大尾方式

下面我们再来看一下IMAGE_OPTIONAL_HEADER32结构,从字面上看好象这个结构是可选的,
其实则不然,它是每个PE文件不可缺少的部分。我们分别对每个字段进行讲解,同样我们仍
省略了一些我们不太关心的字段。

Magic字段可能是两个值:107h表示是一个ROM映像,10bh表示是一个EXE映像。

SizeOfCode表示代码节的总大小。

SizeOfInitializedData指定了已初始化数据节的大小,SizeOfUninitializedData包含未初
始化数据节的大小。

AddressOfEntryPoint是程序入口的RVA(关于RVA的概念将在后面介绍,这是PE文件中的一个
非常重要又非常容易混淆的概念)。如果我们要改变程序的执行入口则可以改变这个值 :P

BaseOfCode和BaseOfData分别是代码节和数据节的起始RVA。

ImageBase是程序建议的装载地址。如果可能的话系统将文件加载到ImageBase指定的地址,
如果这个地址被占用文件才被加载到其他地址上。由于每个程序的虚拟地址空间是独立的,
所以对于优先装入的EXE文件而言,其地址空间不可能被占用;而对于DLL,其装入的地址空
间要依具体程序的地址空间的使用状态而定,所以可能每次装载的地址是不同的。这还引出
了另一个问题就是,一般的EXE文件不需要定位表,而DLL文件必须要有一个重定位表。

SectionAligment和FileAligment分别是内存中和文件中的对齐粒度,正是由于程序在内存
中和文件中的对齐粒度不同才产生了RVA概念,后面提到。

SizeOfImage是内存中整个PE的大小。

SizeOfHeaders是所有头加节表的大小。

CheckSum是文件的校验和,对于一般的PE文件系统并不检查这个值。而对于系统文件,如驱
动等,系统会严格检查这个值,如果这个值不正确系统则不予以加载。

Subsystem指定文件的子系统。关于各个取值的定义如下:

# define IMAGE_SUBSYSTEM_UNKNOWN              0   // 未知子系统
#define IMAGE_SUBSYSTEM_NATIVE               1   // 不需要子系统
#define IMAGE_SUBSYSTEM_WINDOWS_GUI          2   // Windows图形界面
#define IMAGE_SUBSYSTEM_WINDOWS_CUI          3   // Windows控制台界面
#define IMAGE_SUBSYSTEM_OS2_CUI              5   // OS/2控制台界面
#define IMAGE_SUBSYSTEM_POSIX_CUI            7   // Posiz控制台界面
#define IMAGE_SUBSYSTEM_NATIVE_WINDOWS       8   // Win9x驱动程序,不需要子系统
#define IMAGE_SUBSYSTEM_WINDOWS_CE_GUI       9   // Windows CE子系统


NumberOfRvaAndSizes指定了数据目录结构的数量,这个数量一般总为16。

DataDirectory为数据目录。

下面是数据目录的定义:

typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;
    DWORD   Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

VirtualAddress为数据的起始RVA,Size为数据块的长度。下面是数据目录列表的含义:

 

#define  IMAGE_DIRECTORY_ENTRY_EXPORT          0    //  导出表
#define  IMAGE_DIRECTORY_ENTRY_IMPORT          1    //  引入表
#define  IMAGE_DIRECTORY_ENTRY_RESOURCE        2    //  资源
#define  IMAGE_DIRECTORY_ENTRY_EXCEPTION       3    //  异常
#define  IMAGE_DIRECTORY_ENTRY_SECURITY        4    //  安全
#define  IMAGE_DIRECTORY_ENTRY_BASERELOC       5    //  重定位表
#define  IMAGE_DIRECTORY_ENTRY_DEBUG           6    //  调试信息
#define  IMAGE_DIRECTORY_ENTRY_ARCHITECTURE    7    //  版权信息

看到这里大家是不是很混乱呢?没办法,只能硬着头皮“啃”下去,把上面的内容再重新读
一遍... 下面我们继续,做好准备了么?我们开始啦!!

紧接着IMAGE_NT_HEADERS结构的是节表。什么是节表呢?别着急,我们先要清楚一下什么是
节。PE文件是按照节的方式组织的,比如:数据节、代码节、重定位节等。每个节有着自己
的属性,如:只读、只写、可读可写、可执行、可丢弃等。其实在执行一个PE文件的时候,
Windows并不是把整个PE文件一下读入内存,而是采用内存映射的机制。当程序执行到某个
内存页中的指令或者访问到某个内存页中的数据时,如果这个页在内存中那么就执行或访问,
如果这个页不在内存中而是在磁盘中,这时会引发一个缺页故障,系统会自动把这个页从交
换文件中提交的物理内存并重新执行故障指令。由于这时这个内存页已经提交到了物理内存
则程序可以继续执行。这样的机制使得文件装入的速度和文件的大小不成比例关系。

节表就是描述每个节属性的表,文件中有多少个节就有多少个节表。下面我们来看一下节表
的结构:

 

#define  IMAGE_SIZEOF_SHORT_NAME              8

typedef 
struct  _IMAGE_SECTION_HEADER  {
    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];
    union 
{
            DWORD   PhysicalAddress;
            DWORD   VirtualSize;
    }
 Misc;
    DWORD   VirtualAddress;
    DWORD   SizeOfRawData;
    DWORD   PointerToRawData;
    DWORD   PointerToRelocations;
    DWORD   PointerToLinenumbers;
    WORD    NumberOfRelocations;
    WORD    NumberOfLinenumbers;
    DWORD   Characteristics;
}
 IMAGE_SECTION_HEADER,  * PIMAGE_SECTION_HEADER;

Name为一个8个字节的数组。定义了节的名字,如:.text等。习惯上我们把代码节称为.text,
把数据节称为.data,把重定位节称为.reloc,把资源节称为.rsrc等。但注意:这些名字不
是一定的,可一任意命名,千万不要通过节的名字来定位一个节。

Misc是一个联合。通常是VirtualSize有效。它指定了节的大小。这是节在没有进行对齐前的
大小。

VirtualAddress指定了这个节在被映射到内存中后的偏移地址,是一个RVA地址。这个地址是
经过对齐的,以SectionAlignment为对齐粒度。

PointerToRawData指定了节在磁盘文件中的偏移,注意不要与RVA混淆。

SizeOfRawData指定了节在文件中对齐后的大小,即VirtualSize的值根据FileAlignment粒度
对齐后的大小。

Characteristics同样又是一个很重要的字段。它指定了节的属性。下面是部分属性的定义:

 

# define IMAGE_SCN_CNT_CODE                   0x00000020  // 节中包含代码
#
define IMAGE_SCN_CNT_INITIALIZED_DATA       0x00000040  // 节中包含已初始化数据
#
define IMAGE_SCN_CNT_UNINITIALIZED_DATA     0x00000080  // 节中包含未初始化数据
#
define IMAGE_SCN_MEM_DISCARDABLE            0x02000000  // 是一个可丢弃的节,即
                                                         // 节中的数据在进程开始
                                                         // 后将被丢弃

#
define IMAGE_SCN_MEM_NOT_CACHED             0x04000000  // 节中数据不经过缓存
#
define IMAGE_SCN_MEM_NOT_PAGED              0x08000000  // 节中数据不被交换出内存
#
define IMAGE_SCN_MEM_SHARED                 0x10000000  // 节中数据可共享
#
define IMAGE_SCN_MEM_EXECUTE                0x20000000  // 可执行节
#
define IMAGE_SCN_MEM_READ                   0x40000000  // 可读节
#
define IMAGE_SCN_MEM_WRITE                  0x80000000  // 可写节

好了,是时候跟大家介绍RVA的概念了。这是一个大多数初学者经常搞不清楚的容易混淆的概
念。RVA是Relative Virtual Address的缩写,即相对虚拟地址。那么RVA到底代表什么呢?
简单的说就是,RVA是内存中相对装载基址的偏移。假设一个进程的装载地址为00400000h,
一个数据的地址为00401234h,那么这个数据的RVA为00401234h-00400000h=1234h。

好累啊... 不知道我的描述是否清楚呢?我想多数读者读到这里一定又是一头雾水吧?为什
么要将这么多关于PE文件的知识呢?(废什么话?这样的问题也拿出来问。呵呵,我好象听
到有人这么说了 :P)因为Win32下的可执行文件、DLL和驱动等都是PE格式的,我们的病毒
要感染它们,所以必须要把整个PE格式烂熟于心。

其实关于PE文件我们还有导入表、导出表、重定位表、资源等很多内容没有讲。但是为了让
读者能够减轻一些负担,所以把这些内容穿插在后面的小节中,直到涉及到相关知识时我们
再进行讲解。

下面我们准备进入下一节,在进入下一节之前我建议读者把前面的内容再巩固一遍,在后面
的一节中我们要向大家介绍一款相当优秀的编译器 ---- FASM(Flat Assembler)。为什么
我要推荐它呢?一会儿你就会知道 :P

首先关于 [评价可免费] 的严重声明: 一、评价=评论加评价(评星星); 二、评价必须是下载完了该资源后的评价,没下载就评论无效; 三、如果正确评价了,返还积分可能需要等等,系统需要反应下。呵呵 评论时记得要评分。然后会返回给你花费的分再加1分.理论上有十分就可以下载完所有的资源了。一般人我不告诉他。 1、声明 ------- 本文仅仅是一篇讲述病毒原理的理论性文章,任何人如果通过本文中讲述的技术或利用本文 中的代码写出恶性病毒,造成的任何影响均与作者无关。 2、前言 ------- 病毒是什么?病毒就是一个具有一定生物病毒特性,可以进行传播、感染的程序。病毒同样 是一个程序,只不过它经常做着一些正常程序不常做的事情而已,仅此而已。在这篇文章中 我们将揭开病毒的神秘面纱,动手写一个病毒(当然这个病毒是不具有破坏力的,仅仅是一 个良性病毒)。 在网上有很多病毒方面的入门文章,但大部分都很泛泛,并不适合真正的初学者。真正的高 手没有时间也不屑于写这样一篇详细的入门文章,所以我便萌发了写这样一篇文章的冲动, 一来是对自己的学习进行一下总结,二来也是想让像我一样的初学者能少走一些弯路。如果 你有一定的病毒编写基础,那么就此打住,这是一篇为对病毒编程完全没有概念的读者编写 的,是一篇超级入门的文章 :P
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值