这个学期终于圆了当年来读渣电的网络安全专业的一个梦: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
文件偏移地址、虚拟地址、相对偏移地址的关系:
ok,有了上面的基础函数,我们接下来编写病毒代码,病毒代码即是需要插入到宿主中的代码。