PE 文件型病毒编写实验(一)

这个学期终于圆了当年来读渣电的网络安全专业的一个梦:PE病毒编写。

想起了高中那会儿熬夜看看雪论坛瞎搞什么软件破解,可是搞到后面很多东西不理解做的不深入,所以就先一本心思的考上渣电,然后再来深入的学习一下PE的相关。

后面 渣电是考上啦,网络安全专业也考上啦,可是大一到大三虽然偶然零零星星地看了一些PE相关的东西,却一直没有集中化的时间来研究PE,因为总是有其它项目找上自己。这个学期刚好有计算机病毒这门课,刚好有借口来正儿八经的写。

今天也终于写完了,幸甚至哉!O(∩_∩)O哈哈~

下面介绍一下我的方法。希望这篇博文对入手 PE 病毒的 初学者有一定的帮助。毕竟里面有好多坑是自己一点点的踩过来的。

项目地址

https://github.com/jmhIcoding/PE-learning

实验工具:

Visual stdio 2013 Windows 下至尊C/C++ IDE。这个工具用来给编写病毒程序,其中会在C/C++里面嵌入汇编代码。

Winhex.

IDA. 静态代码分析工具,里面也有动态调试功能。这个工具主要用来调试病毒代码是否嵌入到宿主中,同时在宿主中的病毒代码是否需要进行微调。

实验原理:

病毒:病毒这个东西主要有广义和狭义的定义。

狭义的病毒是指那些具有将自己的精确拷贝复制到其它程序中的程序。

广义的病毒则是指所有那些引起计算机非正常工作的计算机指令或代码段。

而我们现在想要实验的PE病毒就是典型的狭义病毒,它会将自己的精确拷贝复制到其它程序。

文件型病毒:特指感染对象为文件的病毒。

PE 文件: Portable Executable 文件,是Windows操作系统一类重要文件,这一大类文件遵循相同的文件格式,这个文件格式叫做 PE 文件格式。这一大类文件包括:可执行EXE文件,动态链接库DLL文件,驱动dri文件,字体.font文件等等。

PE 文件型病毒:专门感染PE文件尤其是可执行文件的病毒。

既然我们要对可执行文件进行感染,那么我们肯定要首先了解一下PE文件。但是我又不打算这篇博文详细介绍PE文件。只点到为止吧。这里有一篇讲PE的:
http://blog.csdn.net/liuyez123/article/details/51281905

PE文件病毒感染的大致步骤:
1.解析目标exe文件,得到文件的入口点旧的 AddressOfEntryPoint
2.将病毒代码插入exe,修改相应的数据结构,最重要的是把入口点修改至病毒代码的起始点
3.病毒代码的最后jmp到旧的AddressOfEntryPoint.

然后我们的重点就是怎么讲这三步分解开来啦!
#编程

我们会先编写好一些基础功能函数,然后在这个基础上进行分析。

1.一些结构体的定义:
这些结构体主要是为了解析PE 文件

//peHeader.h
typedef unsigned short WORD;
typedef unsigned int DWORD;
typedef long LONG;
typedef unsigned long long ULONGLONG;
typedef char CHAR;
typedef unsigned char BYTE;
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
	void display()
	{
		printf("DOS Magic: %s:%X \n",typeid(e_magic).name(), *(WORD*)&e_magic);
		printf("%s:%X \n",typeid(e_lfanew), e_lfanew);
	}
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

typedef struct _IMAGE_FILE_HEADER {
	WORD    Machine;
	WORD    NumberOfSections;
	DWORD   TimeDateStamp;
	DWORD   PointerToSymbolTable;
	DWORD   NumberOfSymbols;
	WORD    SizeOfOptionalHeader;
	WORD    Characteristics;
	void display()
	{
		printf("%s : %d \n", typeid(NumberOfSections).name(), NumberOfSections);
		printf("%s : %d \n", typeid(SizeOfOptionalHeader).name(), SizeOfOptionalHeader);
	}
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;


//
// Directory format.
//

typedef struct _IMAGE_DATA_DIRECTORY {
	DWORD   VirtualAddress;
	DWORD   Size;
	void display()
	{
		printf("%s :%d \n", typeid(VirtualAddress).name(), VirtualAddress);
		printf("%s :%d \n", typeid(Size).name(), Size);
	}
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES    16

//
// Optional header format.
//

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];
	void display()
	{
		printf("%s : %d \n", typeid(this->Magic).name(), Magic);
		printf("%s : %d \n", typeid(this->AddressOfEntryPoint).name(), AddressOfEntryPoint);
		printf("%s : %d \n", typeid(this->ImageBase).name(), ImageBase);
		printf("%s : %d \n", typeid(this->FileAlignment).name(), FileAlignment);
		printf("%s : %d \n", typeid(this->SectionAlignment).name(), SectionAlignment);
		printf("%s : %d \n", typeid(this->SizeOfHeaders).name(), SizeOfHeaders);
	}
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;


typedef struct _IMAGE_NT_HEADERS {
	DWORD Signature;
	IMAGE_FILE_HEADER FileHeader;
	IMAGE_OPTIONAL_HEADER32 OptionalHeader;
	void display()
	{
		printf("NT_Header Signature %s : %X\n", typeid(Signature).name(),Signature);
	}
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

//节的属性
//
// Section characteristics.
//
//      IMAGE_SCN_TYPE_REG                   0x00000000  // Reserved.
//      IMAGE_SCN_TYPE_DSECT                 0x00000001  // Reserved.
//      IMAGE_SCN_TYPE_NOLOAD                0x00000002  // Reserved.
//      IMAGE_SCN_TYPE_GROUP                 0x00000004  // Reserved.
#define IMAGE_SCN_TYPE_NO_PAD                0x00000008  // Reserved.
//      IMAGE_SCN_TYPE_COPY                  0x00000010  // Reserved.

#define IMAGE_SCN_CNT_CODE                   0x00000020  // Section contains code.
#define IMAGE_SCN_CNT_INITIALIZED_DATA       0x00000040  // Section contains initialized data.
#define IMAGE_SCN_CNT_UNINITIALIZED_DATA     0x00000080  // Section contains uninitialized data.

#define IMAGE_SCN_LNK_OTHER                  0x00000100  // Reserved.
#define IMAGE_SCN_LNK_INFO                   0x00000200  // Section contains comments or some other type of information.
//      IMAGE_SCN_TYPE_OVER                  0x00000400  // Reserved.
#define IMAGE_SCN_LNK_REMOVE                 0x00000800  // Section contents will not become part of image.
#define IMAGE_SCN_LNK_COMDAT                 0x00001000  // Section contents comdat.
//                                           0x00002000  // Reserved.
//      IMAGE_SCN_MEM_PROTECTED - Obsolete   0x00004000
#define IMAGE_SCN_NO_DEFER_SPEC_EXC          0x00004000  // Reset speculative exceptions handling bits in the TLB entries for this section.
#define IMAGE_SCN_GPREL                      0x00008000  // Section content can be accessed relative to GP
#define IMAGE_SCN_MEM_FARDATA                0x00008000
//      IMAGE_SCN_MEM_SYSHEAP  - Obsolete    0x00010000
#define IMAGE_SCN_MEM_PURGEABLE              0x00020000
#define IMAGE_SCN_MEM_16BIT                  0x00020000
#define IMAGE_SCN_MEM_LOCKED                 0x00040000
#define IMAGE_SCN_MEM_PRELOAD                0x00080000

#define IMAGE_SCN_ALIGN_1BYTES               0x00100000  //
#define IMAGE_SCN_ALIGN_2BYTES               0x00200000  //
#define IMAGE_SCN_ALIGN_4BYTES               0x00300000  //
#define IMAGE_SCN_ALIGN_8BYTES               0x00400000  //
#define IMAGE_SCN_ALIGN_16BYTES              0x00500000  // Default alignment if no others are specified.
#define IMAGE_SCN_ALIGN_32BYTES              0x00600000  //
#define IMAGE_SCN_ALIGN_64BYTES              0x00700000  //
#define IMAGE_SCN_ALIGN_128BYTES             0x00800000  //
#define IMAGE_SCN_ALIGN_256BYTES             0x00900000  //
#define IMAGE_SCN_ALIGN_512BYTES             0x00A00000  //
#define IMAGE_SCN_ALIGN_1024BYTES            0x00B00000  //
#define IMAGE_SCN_ALIGN_2048BYTES            0x00C00000  //
#define IMAGE_SCN_ALIGN_4096BYTES            0x00D00000  //
#define IMAGE_SCN_ALIGN_8192BYTES            0x00E00000  //
// Unused                                    0x00F00000
#define IMAGE_SCN_ALIGN_MASK                 0x00F00000

#define IMAGE_SCN_LNK_NRELOC_OVFL            0x01000000  // Section contains extended relocations.
#define IMAGE_SCN_MEM_DISCARDABLE            0x02000000  // Section can be discarded.
#define IMAGE_SCN_MEM_NOT_CACHED             0x04000000  // Section is not cachable.
#define IMAGE_SCN_MEM_NOT_PAGED              0x08000000  // Section is not pageable.
#define IMAGE_SCN_MEM_SHARED                 0x10000000  // Section is shareable.
#define IMAGE_SCN_MEM_EXECUTE                0x20000000  // Section is executable.
#define IMAGE_SCN_MEM_READ                   0x40000000  // Section is readable.
#define IMAGE_SCN_MEM_WRITE                  0x80000000  // Section is writeable.


//
// Section header format.
//

#define IMAGE_SIZEOF_SHORT_NAME              8

typedef struct _IMAGE_SECTION_HEADER {
	BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];
	union {
		DWORD   PhysicalAddress;
		DWORD   VirtualSize;//exe是虚拟地址
	} Misc;
	DWORD   VirtualAddress;
	DWORD   SizeOfRawData;
	DWORD   PointerToRawData;
	DWORD   PointerToRelocations;
	DWORD   PointerToLinenumbers;
	WORD    NumberOfRelocations;
	WORD    NumberOfLinenumbers;
	DWORD   Characteristics;//是否是可读、可写、可执行
	void display()
	{
		printf("%s : 0x %s \n", typeid(Name).name(), Name);
		printf("Vsize:%s :0x %X \n", typeid(Misc).name(), Misc.VirtualSize);
		printf("RVA%s : 0x%X \n", typeid(VirtualAddress).name(), VirtualAddress);
		printf("FVA%s : 0x%X \n", typeid(PointerToRawData).name(), PointerToRawData);
		printf("Size of R data :%s : 0x%X \n", typeid(SizeOfRawData).name(), SizeOfRawData);
		printf("%s : 0x%X \n", typeid(Characteristics).name(), Characteristics);
	}
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

#define IMAGE_SIZEOF_SECTION_HEADER          40

2.定义基础工具类:

//BaseTool.h 
#pragma once
#include 
#include 
#include 
#include "peHeader.h"
#include 
#include 
using namespace std;
#define _DEBUG_H 

上面include的常用的头文件,注意typeinfo。
BaseTool类:

class BaseTool
{
public:
	FILE * fp;//PE 文件指针
	char * FILEPOINT;//文件的偏移指针
	LONG e_lfnew;//PE文件头
	LONG AddressOfentry;//入口点
	_IMAGE_SECTION_HEADER * section_headers;//节表项
	int number_of_section;//节表数目
	char FILENAME[128] ;//PE文件名
public:
	BaseTool() :fp(0), FILEPOINT(0), section_headers(0), number_of_section(0)
	{

	}
public:
	BaseTool(char *filename) :fp(0), FILEPOINT(0), section_headers(0), number_of_section(0)
	{
		fp = fopen(filename, "rb+");
		
		if (fp == NULL)
		{
			printf("Error when load file!!!!");
			exit(-1);
		}
		memset(FILENAME, 0, 128);
		memcpy(FILENAME, filename, strlen(filename));
	}
}

BaseTool(char* filename)构造函数结构某个PE文件名为参数,然后以"rb+"访问模式打开这个文件。

为什么要使用"rb+"模式? 以这个模式打开文件,就可以以二进制形式读写文件。注意 不要使用ANSIC模式打开文件。

接下来我们的来编写这个类的功能函数!
注意以下所有函数都是BaseTool的public 成员函数。
int getBytes(char * _dst,size_t _len, long _offset_file,FILE *fp);

int getBytes(char * _dst,size_t _len,long _offset_file,FILE *fp=NULL)
	{
		int cnt = 0;
		if (fp == NULL)
		{
			fp = this->fp;
		}
		fseek(fp, _offset_file, 0);
		while (_len--)
			{
				fscanf(fp, "%c", _dst++);
				cnt++;
			}
		return cnt;
	}

该函数从已经打开的文件指针fp从文件头偏移_oofset_file个位置处读取_len个字节的数据保存到目的地址_dst里面。参数fp默认设置为BaseTool的fp.

int setBytes(char _src, int _len, long _offset_file, FILEfp = NULL);

	int setBytes(char *_src, int _len, long _offset_file, FILE*fp = NULL)
		//最后需要将校验和修改
	{
		int cnt = 0;
		
		if (fp == NULL)
		{
			fp = this->fp;
			fseek(fp, _offset_file, 0);
			for (int i = 0; i < _len;i++)
			{
				fprintf(fp, "%c", _src[i]);
				cnt++;
			}
		}
		else
		{	
			char c = 0;
			fseek(this->fp, 0, 0);
			while (fscanf(this->fp,"%c",&c)!=EOF)
			{
				fprintf(fp, "%c", c);
			}
			fseek(fp, _offset_file, 0);
			for (int i = 0; i < _len; i++)
			{
				fprintf(fp, "%c", _src[i]);
				cnt++;
			}

		}

		return cnt;
	}

把内存中以_src为起始地址的_len个字节写入fp文件偏移 _offset_file处。

void display(const char * _src, int _len);

	void display(const char * _src, int _len)
	{
		for (int i = 0; i < _len;i++)
		{
			unsigned char ch= 0;
			ch = _src[i];
			printf("%0.2X ", ch);
		}
	}

以16进制,打印内存中以_src起始的_len个字节的内容,逐个字节打印。

LONG get_entry(FILE *fp = NULL)

	LONG get_entry(FILE *fp = NULL)
		//pe head!!!! not execution entry.
	{
		long rst = 0;
		getBytes((char *)&rst, sizeof(LONG), sizeof(IMAGE_DOS_HEADER)-sizeof(LONG),fp);
		return rst;
	}

获取文件fp的pe头的位置,注意pe的偏移量是sizeof(IMAGE_DOS_HEADER)-sizeof(LONG),这个是因为el_fnew是在IMAGE_DOS_HEADER的最后一个字段。使用了getBytes函数,从IMAGE_DOS_HEADER处读取了一定的pe头的偏移量。

IMAGE_DOS_HEADER get_IMAGE_DOS_HEADER(FILE *fp = NULL)

	IMAGE_DOS_HEADER get_IMAGE_DOS_HEADER(FILE *fp = NULL)
	{
		fp = (fp == NULL) ? this->fp : fp;
		char * buf = (char *)malloc(sizeof(IMAGE_DOS_HEADER));
		memset(buf, 0, sizeof(IMAGE_DOS_HEADER));
		getBytes(buf, sizeof(IMAGE_DOS_HEADER), 0);
		if (fp == this->fp)
		{
			this->e_lfnew = ((IMAGE_DOS_HEADER *)buf)->e_lfanew;
		}
		return (IMAGE_DOS_HEADER)*((IMAGE_DOS_HEADER *)buf);
	}

获取IMAGE_DOS_HEADER

_IMAGE_NT_HEADERS get_IMAGE_NT_HEADER(FILE * fp = NULL);

	_IMAGE_NT_HEADERS get_IMAGE_NT_HEADER(FILE * fp = NULL)
	{
		fp = (fp == NULL) ? this->fp : fp;
		LONG e_lfnew = (fp == NULL) ? this->e_lfnew : get_entry(fp);
		char * buf = (char *)malloc(sizeof(_IMAGE_NT_HEADERS));
		memset(buf, 0, sizeof(_IMAGE_NT_HEADERS));
		getBytes(buf, sizeof(_IMAGE_NT_HEADERS), this->e_lfnew);
		return (_IMAGE_NT_HEADERS)*((_IMAGE_NT_HEADERS*)buf);
	}

获取NT头,包括了Signature,FileHeader和OptionalHeader

void get_IMAGE_SECTION_HEADERS(_IMAGE_SECTION_HEADER *_dst, size_t *_cnt, FILE *fp = NULL);

获取节表项

	void get_IMAGE_SECTION_HEADERS(_IMAGE_SECTION_HEADER *_dst, size_t *_cnt, FILE *fp = NULL)
	{
		fp = (fp == NULL) ? this->fp : fp;
		*_cnt = (size_t)get_Number_OF_Section(fp);
		long  offset = get_entry() +sizeof(_IMAGE_NT_HEADERS);
		getBytes((char*)_dst, sizeof(IMAGE_SECTION_HEADER)*(*_cnt), offset,fp);
		if (fp == this->fp && section_headers == NULL)
		{
			number_of_section = *_cnt;
			section_headers = (IMAGE_SECTION_HEADER *)malloc(sizeof(IMAGE_SECTION_HEADER)*number_of_section);
			memcpy(section_headers, _dst, sizeof(IMAGE_SECTION_HEADER)*(*_cnt));
		}
	}

文件偏移量,虚拟地址,相对地址之间的换算函数:

	unsigned int rva_To_fa(unsigned int rva)
		//将相对虚拟地址转为文件偏移地址
	{
		unsigned int fa = 0;

		if (section_headers == NULL)
		{
			number_of_section = get_Number_OF_Section();
			section_headers = (IMAGE_SECTION_HEADER *)malloc(sizeof(IMAGE_SECTION_HEADER)*number_of_section);
			size_t cnt = 0;
			get_IMAGE_SECTION_HEADERS(section_headers, &cnt);
		}
		
		if (rva < (get_entry() + sizeof(_IMAGE_NT_HEADERS)+number_of_section*sizeof(IMAGE_SECTION_HEADER)))
			//rva还是在头部
		{
			return rva;
		}

		int i = 0;

		for ( i = 0; i < (number_of_section-1); i++)
		{
			if (rva >= section_headers[i].VirtualAddress && rva= section_headers[i].PointerToRawData && fa

文件偏移地址、虚拟地址、相对偏移地址的关系:
PE 文件型病毒编写实验(一)_第1张图片
ok,有了上面的基础函数,我们接下来编写病毒代码,病毒代码即是需要插入到宿主中的代码。

你可能感兴趣的:(密码学,计算机病毒,网络安全,pe病毒)