主要是在一个exe程序中添加一个弹窗
首先需要了解一下PE格式,下面是PE格式图
DOS头是程序最开始的位置
其中比较重要的两个字段e_magic和e_lfanew字段
e_magic 里面值是 “MZ”标记 可以通过这个标记判断是否是可执行文件
e_lfanew 是PE头相对于文件的偏移,通过这个偏移可以找到PE头
DOS头结构如下
typedef struct _IMAE_DOS_HEADER { //DOS .EXE header 位置 WORD e_magic; //Magic number; 0x00 WORD e_cblp; //Bytes on last page of file 0x02 WORD e_cp; //Pages in file 0x04 WORD e_crlc; //Relocations 0x06 WORD e_cparhdr; //Size of header in paragraphs 0x08 WORD e_minalloc; //Minimum extra paragraphs needed 0x0A WORD e_maxalloc; //Maximum extra paragraphs needed 0x0C WORD e_ss; //Initial (relative) SS value 0x0E WORD e_sp; //Initial SP value 0x10 WORD e_csum; //Checksum 0x12 WORD e_ip; //Initial IP value 0x14 WORD e_cs; //Initial (relative) CS value 0x16 WORD e_lfarlc; //File address of relocation table 0x18 WORD e_ovno; //Overlay number 0x1A WORD e_res[4]; //Reserved words 0x1C WORD e_oemid; //OEM identifier (for e_oeminfo) 0x24 WORD e_oeminfo; //OEM information; e_oemid specific 0x26 WORD e_res2[10]; //Reserved words 0x28 LONG e_lfanew; //File address of new exe header 0x3C } IMAGE_DOS-HEADER, *PIMAGE_DOS_HEADER;
NT头包含PE头和可选PE头
Signature 是标识PE头 0x00004550
IMAGE_FILE_HEADER 就是pe头
IMAGE_OPTIONAL_HEADER32 是可选PE头
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
下面是PE头
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;
PE头下比较重要的属性
NumberOfSections 节表数量
SizeOfOptionalHeader 可选PE头的大小
下面是可选PE头
//
// 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];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
可选PE头下比较重要的属性
ImageBase 文件在内存中的起始地址
SizeOfImage 拉伸后的内存大小
SizeOfHeaders dos头+nt头+节表 对齐后的大小
下面是节表
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;
节表中比较重要的属性
Misc.VirtualSize 内存中一个节的大小
VirtualAddress 内存中节开始的地址偏移(RVA)
SizeOfRawData 磁盘中这个节对齐后的大小
PointerToRawData 磁盘中节开始的地址偏移
简单来说就是使用跳转到MessageBox的地址在跳回来,当然也可以跳转到自己的代码。
首先需要找到MessageBox的地址,打开od,加载一个exe,在命令行输入bp MessageBoxA,这样就会在断点里面看到MessageBox的地址了
其中需要注意的是跳转的地址,e8后面就是需要计算的地址,
- x是E8后面真正跟着的地址
- 真正跳转的地址 = E8这条指令的下一行地址 + x
- x = 真正跳转的地址 - E8这条指令的下一行地址
- E8的这条指令的下一个地址 是 硬编码E8指令的长度 = 5个字节
- x = 要跳转的地址 - (E8的地址 + 5)
拉伸是指exe在文件模式下转换到运行模式中的过程
左边FIleBuffer是指exe在文件中状态,右边ImageBuffer是指exe运行中状态,红色部分代表数据区,由此可见 文件中状态的数据区和运行中状态的数据区是不同的,而我们关心的是exe运行后加载一个MessageBox,所以需要进行拉伸。
// PeAddMessage.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 // //下面是添加一个MessageBox 添加到一个exe程序中! #define _CRT_SECURE_NO_WARNINGS #include<windows.h> #include<stdio.h> #include<stdlib.h> int Deviation = 0; //偏移 PIMAGE_DOS_HEADER DOSHANDER = NULL;//DOS头 PIMAGE_NT_HEADERS NTHEADERS = NULL;//NT头 PIMAGE_SECTION_HEADER SECTION[100];//节表 BYTE shellcode[] = { 0x6a,00,0x6a,00,0x6a,00,0x6a,00,//6a是push 0xe8,00,00,00,00, //e8是call的硬编码 0xe9,00,00,00,00 //e9是jmp的硬编码 }; DWORD CodeAddressp = (DWORD) & MessageBox; //要跳转的地址 //x = 跳转的地址 - (E8的地址 + 5) /* 1.文件加载到内存 1.1 malloc申请内存 1.2 memset初始化申请的内存 1.3 open打开文件 1.4 frend读取文件到内存 1.5 给PE结构体赋值 1.5.1 获取DOS头大小 1.5.2 获取NT头大小 1.5.3 获取节表 2.进行拉伸 2.1 malloc申请内存 2.2 memset初始化申请的内存 2.3 SizeOfHeaders拷贝头 2.4 拷贝节表 3.还原 3.1 malloc申请内存 3.2 memset初始化申请的内存 3.3 拷贝头 3.4 拷贝节表 4.存盘 */ char* FileDirectory = (char*)"C:\\Users\\A\\source\\repos\\test\\Debug\\WindowsProject1.exe"; //打开的exe路径 char* FileDirectorytu = (char*)"C:\\Users\\A\\source\\repos\\test\\Debug\\WindowsProject2.exe"; //重新存储的exe路径 //获取文件大小 int _GetFileSize(char* FileRoute) { int size = 0; FILE* p = fopen(FileRoute, "r"); fseek(p, 0L, SEEK_END); //获取文件大小 size = ftell(p); fclose(p); return size; } int size = _GetFileSize(FileDirectory); //文件大小 BYTE* FileBuller(char* FileDirectory) { int Deviation = 0;//偏移 //读取文件 BYTE* _FileBuller = (BYTE*)malloc(sizeof(BYTE) * size + 1);//申请内存 if (_FileBuller == NULL) { printf("申请内存错误"); } //初始化内存 memset(_FileBuller, 0, size + 1); //打开文件 FILE* p = fopen(FileDirectory, "rb"); if (p == NULL) { printf("读取文件错误"); } //读取文件 int x = fread(_FileBuller, sizeof(BYTE), size, p); //给结构体赋值 //dos头大小 DOSHANDER = (PIMAGE_DOS_HEADER)(_FileBuller + Deviation); Deviation += DOSHANDER->e_lfanew; //nt头大小 NTHEADERS = (PIMAGE_NT_HEADERS)(_FileBuller + Deviation); Deviation += sizeof(IMAGE_NT_HEADERS); //获取节表 for (int f = 0; f < NTHEADERS->FileHeader.NumberOfSections; f++) { SECTION[f] = (PIMAGE_SECTION_HEADER)(Deviation + _FileBuller); Deviation += sizeof(IMAGE_SECTION_HEADER); //下一个节表的偏移 } fclose(p); return _FileBuller; } int nSection = 0; BYTE* ImageBuffer(BYTE* _FileBuller)//拉伸 { //申请内存 BYTE* _ImageBuffer = (BYTE*)malloc(sizeof(BYTE) * NTHEADERS->OptionalHeader.SizeOfImage + 1); if (_ImageBuffer == NULL) { printf("分配内存失败"); } //初始化内存 memset(_ImageBuffer, 0, NTHEADERS->OptionalHeader.SizeOfImage); //拷贝头 memcpy(_ImageBuffer, _FileBuller, sizeof(BYTE) * NTHEADERS->OptionalHeader.SizeOfHeaders); //拷贝节表 for (int q = 0; q < NTHEADERS->FileHeader.NumberOfSections; q++) { int _imageSECTION = SECTION[q]->VirtualAddress; int _fileSECTION = SECTION[q]->PointerToRawData; memcpy(_ImageBuffer + _imageSECTION, _FileBuller + _fileSECTION, SECTION[q]->SizeOfRawData); } return _ImageBuffer; } void Message(BYTE* _ImageBuffer, BYTE* _FileBuller) { /* 在一个程序中添加一个MessageBox */ //计算的地址第一个节表空白的地址 BYTE* PCodeOffset = (BYTE*)(((BYTE*)_ImageBuffer) + SECTION[nSection]->VirtualAddress + SECTION[nSection]->Misc.VirtualSize); //OEP要跳转的地址 int CodeOffset = SECTION[nSection]->VirtualAddress + SECTION[nSection]->Misc.VirtualSize; //修改OEP DWORD OEP = NTHEADERS->OptionalHeader.AddressOfEntryPoint; NTHEADERS->OptionalHeader.AddressOfEntryPoint = CodeOffset; //E8CALL BYTE* a = shellcode; a += 9;//a是数组e8后面的地址 a = (BYTE*)(CodeAddressp - (DWORD)(((CodeOffset + 8) + 5) + NTHEADERS->OptionalHeader.ImageBase));//把计算出来的地址赋值给a, 要跳转的地址 - (e800000000下一个地址 + ImageBase)因为在内存中运行所以要加ImageBase *(DWORD*)(shellcode + 9) = (DWORD)a; //a在赋值给数组 memcpy(PCodeOffset, shellcode, 18); //把数组拷贝到内存 //E9JMP BYTE* b = shellcode; b += 14; b = (BYTE*)((OEP + NTHEADERS->OptionalHeader.ImageBase) - (DWORD)(((CodeOffset + 13) + 5) + NTHEADERS->OptionalHeader.ImageBase)); *(DWORD*)(shellcode + 14) = (DWORD)b; memcpy(_ImageBuffer, _FileBuller, sizeof(BYTE) * NTHEADERS->OptionalHeader.SizeOfHeaders); memcpy(PCodeOffset, shellcode, 18); } void NewBuffer(BYTE* _ImageBuffer) { BYTE* _NewBuffer = (BYTE*)malloc(sizeof(BYTE) * size + 1);//申请内存 if (_NewBuffer == NULL) { printf("申请内存错误"); } memset(_NewBuffer, 0, size + 1);//初始化内存 //拷贝头 memcpy(_NewBuffer, _ImageBuffer, sizeof(BYTE) * NTHEADERS->OptionalHeader.SizeOfHeaders); //拷贝节表 for (int x = 0; x < NTHEADERS->FileHeader.NumberOfSections; x++) { int _imageSECTION = SECTION[x]->VirtualAddress; int _fileSECTION = SECTION[x]->PointerToRawData; memcpy(_NewBuffer + _fileSECTION, _ImageBuffer + _imageSECTION, SECTION[x]->SizeOfRawData); } FILE* p = fopen(FileDirectorytu, "wb+"); fwrite(_NewBuffer, sizeof(BYTE), sizeof(BYTE) * size + 1, p); } void main() { BYTE* x = FileBuller(FileDirectory); //读取到内存 BYTE* y = ImageBuffer(x);//内存操作 Message(y, x);//添加一个弹窗 NewBuffer(y);//存盘 }