zoukankan      html  css  js  c++  java
  • 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
  • 相关阅读:
    tomcat设置编码utf8
    servlet详细理解
    设置utf8编码问题
    yarn状态机的可视化
    以卵石游戏(杭州电1527)
    Android Studio虚拟机配置虚拟键盘
    linux网络编程--跳水send和recv
    基于redis AE异步网络架构
    谈加班文化
    ios8加入通知栏开始
  • 原文地址:https://www.cnblogs.com/qq78292959/p/2077121.html
Copyright © 2011-2022 走看看