一、FileBuffer到ImageBuffer常见的误区
1.文件执行的总过程
- 一个硬盘上的文件读入到虚拟内存中(FileBuffer),是原封不动的将硬盘上的文件数据复制一份放到虚拟内存中
- 接着如果文件要运行,需要先将FileBuffer中的文件数据"拉伸",重载到每一个可执行文件的4GB虚拟内存中!此时称文件印象或者内存印象,即ImageBuffer
- 但是ImageBuffer就是文件运行时真正在内存中状态吗?或者说文件在ImageBuffer中就是表示文件被执行了吗?不!!!!!!
- 在ImageBuffer中的文件数据由于按照一定的规则被"拉伸",只是已经无线接近于可被windows执行的文件格式了!但是此时还不代表文件已经被执行了,因为此时文件也只是处在4GB的虚拟内存中,如果文件被执行操作系统还需要做一些事情,将文件真正的装入内存中,等待CPU的分配执行
- 所以不要理解为ImageBuffer中的状态就是文件正在被执行,后面操作系统还要做很多事情才能让ImageBuffer中的文件真正执行起来的
2.SizeOfRawData一定大于Misc.VirtualSize?
-
SizeOfRawData表示此节在硬盘上经过文件对齐后的大小;Misc.VirtualSize表示此节在内存中没有对齐的大小。那么是不是说SizeOfRawData一定大于等于Misc.VirtualSize呢?不一定!!!!!!!
-
我们写C语言的时候知道如果你定义一个数组已经初始化,比如
int arr[1000] = {0};,此时编译成.exe文件存放在硬盘上时,这1000个int类型的0肯定会存放在某一个节中,并且分配1000个0的空间,这个空间大小是多少,最后重载到ImageBuffer时还是多少,即Misc.VirtualSize不管文件在硬盘上还是内存中的值都是一致的。所以,SizeOfRawData一般都是大于等于Misc.VirtualSize的 -
但是如果我们定义成
int arr[1000];,表示数据还未初始化,并且如果程序中没有使用过或初始化过这块内存空间,那么我们平时看汇编会发现其实编译器还没有做任何事情,这就只是告诉编译器需要预留出1000个int宽度大小的内存空间。所以如果某一个节中存在已经被定义过但还未初始化的数据,那么文件在硬盘上不会显式的留出空间,即SizeOfRawData中不会算上未初始化数据的空间;但是此节的Misc.VirtualSize为加载到内存中时节的未对齐的大小,那么这个值就需要算上给未初始化留出来空间后的整个节的大小,故在内存中的节本身的总大小可能会大于硬盘中的此节文件对齐后的大小。
二、手动模拟FileBuffer到ImageBuffer过程
-
先在硬盘上找一个可执行文件,将文件的数据复制到内存中,即FileBuffer中(前面的练习做过很多次了)
-
根据SizeOfImage的大小,再使用malloc开辟一块ImageBuffer,用0x00初始化ImageBuffer(SizeOfImage即为文件加载到4GB虚拟内存的大小)
-
因为所有头和节表经过文件对齐后的这段数据经过PE loader加载到ImageBuffer是不会变的,所以直接可以将所有头和节表经过文件对齐后的这块数据从FileBuffer中复制到ImageBuffer中
-
接着就是复制所有节的数据:需要使用循环,先复制第一个节的内容。通过第一个节对应节表中的PointerToRawData的值确定第一个节的起始地址;再通过SizeOfRawData的值得到从起始地址开始需要复制多少字节的数据到ImageBuffer中;再接着将这些数据复制到ImageBuffer中的哪个位置呢?就需要通过此节对应的节表中的VirtualAddress决定将数据从ImageBuffer中的哪个地址开始赋值,由于是相对地址,所以还需要知道ImageBase,但是!!我们是用C语言模拟PE的加载过程,此时ImageBuffer的首地址是由malloc申请的,不是真正的ImageBase!(只有当文件真正执行时,操作系统把文件拉伸装入虚拟内存时,才是ImageBase)所以malloc申请的首地址 + VirtualAddress就是最终将第一节数据复制到ImageBuffer中的起始地址。后面的节的数据以此类推从FileBuffer复制到ImageBuffer中
为什么选择SizeOfRawData,不选择Misc.VirtualSize来确定需要复制的节的大小?因为上面说过,Misc.VirtualSize的值由于节中有未初始化的数据且未使用而计算出预留的空间装入内存后的总大小的值可能会很大,如果这个值大到已经包含了后面一个节的数据,那么按照这个值将FileBuffer中的数据复制到ImageBuffer中很可能会把下一个节的数据也复制过去,所以直接用SizeOfRawData就可以了。但是如果节中包含未初始化数据,这样做其实就不太准确了,但是可以大致模拟这个过程即可。(更好一点的做法是比较SizeOfRawData和VirtualSize,选择较小值)
三、内存偏移地址与文件偏移地址换算
-
比如一个文件加载到4GB内存中的某一个数据地址为0x501234,那么怎么算出这个内存地址对应到文件在硬盘上时的地址是多少,即算出文件偏移地址?
- 先算出此内存地址相对于文件在内存中的起始地址的偏移量
- 接着通过这个偏移量循环和每一个节的VirtualAddress做比较,当此偏移量大于某一个节的VirtualAddress并且小于此VirtualAddress + Misc.VirtualSize,就说明这个内存地址就在这个节中
- 再用此偏移量 - 此节的VirtualAddress得到这个内存地址相对于所在节的偏移量
- 接着找内存地址所在节的PointerToRawData,通过PointerToRawData + 内存地址相对于所在节的偏移量来得到此内存地址在硬盘上时相对于文件的偏移量
-
举例:现在我们要找0x501234对应的文件偏移是多少?
- 0x501234 - 0x500000 = 0x1234
- 因为0x1000 < 0x1234 < 0x1000 + Misc.VirtualSize,所以0x501234在可执行文件的第一个节中
- 0x1234 - 0x1000 = 0x234
- 由于第一个节的PointerToRawData为0x400,且假设FileBuffer的起始地址为0(相对的),则0x501234对应的文件偏移地址为0x400 + 0x234 = 0x634
四、作业
1.C语言实现如下功能
-
如图:
-
代码如下:
#include "stdafx.h" #include <stdlib.h> #include <string.h> typedef unsigned short WORD; typedef unsigned int DWORD; typedef unsigned char BYTE; //宏定义MZ标记和PE标记,方便后面判断 #define MZ 0x5A4D #define PE 0x4550 #define IMAGE_SIZEOF_SHORT_NAME 8 //DOS头 struct _IMAGE_DOS_HEADER { WORD e_magic; //MZ标记 WORD e_cblp; WORD e_cp; WORD e_crlc; WORD e_cparhdr; WORD e_minalloc; WORD e_maxalloc; WORD e_ss; WORD e_sp; WORD e_csum; WORD e_ip; WORD e_cs; WORD e_lfarlc; WORD e_ovno; WORD e_res[4]; WORD e_oemid; WORD e_oeminfo; WORD e_res2[10]; DWORD e_lfanew; //PE文件真正开始的偏移地址 }; //标准PE头 struct _IMAGE_FILE_HEADER { WORD Machine; //文件运行平台 WORD NumberOfSections; //节数量 DWORD TimeDateStamp; //时间戳 DWORD PointerToSymbolTable; DWORD NumberOfSymbols; WORD SizeOfOptionalHeader; //可选PE头大小 WORD Characteristics; //特征值 }; //可选PE头 struct _IMAGE_OPTIONAL_HEADER { WORD Magic; //文件类型 BYTE MajorLinkerVersion; BYTE MinorLinkerVersion; DWORD SizeOfCode; //代码节文件对齐后的大小 DWORD SizeOfInitializedData; //初始化数据文件对齐后的大小 DWORD SizeOfUninitializedData; //未初始化数据文件对齐后大小 DWORD AddressOfEntryPoint; //程序入口点(偏移量) DWORD BaseOfCode; //代码基址 DWORD BaseOfData; //数据基址 DWORD ImageBase; //内存镜像基址 DWORD SectionAlignment; //内存对齐粒度 DWORD FileAlignment; //文件对齐粒度 WORD MajorOperatingSystemVersion; WORD MinorOperatingSystemVersion; WORD MajorImageVersion; WORD MinorImageVersion; WORD MajorSubsystemVersion; WORD MinorSubsystemVersion; DWORD Win32VersionValue; DWORD SizeOfImage; //文件装入虚拟内存后大小 DWORD SizeOfHeaders; //DOS、NT头和节表大小 DWORD CheckSum; //校验和 WORD Subsystem; WORD DllCharacteristics; DWORD SizeOfStackReserve; //预留堆栈大小 DWORD SizeOfStackCommit; //实际分配堆栈大小 DWORD SizeOfHeapReserve; //预留堆大小 DWORD SizeOfHeapCommit; //实际分配堆大小 DWORD LoaderFlags; DWORD NumberOfRvaAndSizes; //目录项数目 //_IMAGE_DATA_DIRECTORY DataDirectory[16]; //这个先不管 }; //NT头 struct _IMAGE_NT_HEADERS { DWORD Signature; //PE签名 _IMAGE_FILE_HEADER FileHeader; _IMAGE_OPTIONAL_HEADER OptionalHeader; }; //节表 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; //该节特征属性 }; /*计算文件大小函数 参数:文件绝对路径 返回值:返回文件大小(单位字节) */ int compute_file_size(char* filePath){ FILE* fp = fopen(filePath,"rb"); if(!fp){ printf("打开文件失败"); exit(0); } fseek(fp,0,2); int size = ftell(fp); //fseek(fp,0,0); 单纯计算文件大小,就不需要还原指针了 fclose(fp); return size; } /*将文件读入FileBuffer函数 参数:文件绝对路径 返回值:FileBuffer起始地址 */ char* to_FileBuffer(char* filePath){ FILE* fp = fopen(filePath,"rb"); if(!fp){ printf("打开文件失败"); exit(0); } int size = compute_file_size(filePath); char* mp = (char*)malloc(sizeof(char) * size); //分配内存空间 if(!mp){ printf("分配空间失败"); fclose(fp); exit(0); } int isSucceed = fread(mp,size,1,fp); if(!isSucceed){ printf("读取数据失败"); free(mp); fclose(fp); exit(0); } fclose(fp); return mp; } /*FileBuffer到ImageBuffer函数 参数:FileBuffer起始地址 返回值:ImageBuffer起始地址 */ char* fileBuffer_to_ImageBuffer(char* fileBufferp){ _IMAGE_DOS_HEADER* _image_dos_header = NULL; _IMAGE_FILE_HEADER* _image_file_header = NULL; _IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL; _IMAGE_SECTION_HEADER* _image_section_header = NULL; _image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp; _image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4); _image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20); _image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header + _image_file_header->SizeOfOptionalHeader); DWORD sizeofImage = _image_optional_header->SizeOfImage; char* imageBufferp = (char*)malloc(sizeofImage); if(NULL == imageBufferp){ printf("动态申请ImageBuffer内存失败\n"); exit(0); } for(DWORD i = 0;i < sizeofImage;i++){ *(imageBufferp + i) = 0x00; } //strncpy(imageBufferp,fileBufferp,_image_optional_header->SizeOfHeaders); imageBufferp,fileBufferp值不会变 for(i = 0;i < _image_optional_header->SizeOfHeaders;i++){ *(imageBufferp + i) = *(fileBufferp + i); } for(i = 0;i < _image_file_header->NumberOfSections;i++){ for(DWORD j = 0;j < _image_section_header->SizeOfRawData;j++){ *(imageBufferp + _image_section_header->VirtualAddress + j) = *(fileBufferp + _image_section_header->PointerToRawData + j); } _image_section_header++; } return imageBufferp; } /*计算NewBuffer大小函数 参数:NewBuffer起始地址 返回值:unsigned int类型(单位:字节) */ DWORD compute_NewBuffer_size(char* newBufferp){ _IMAGE_DOS_HEADER* _image_dos_header = NULL; _IMAGE_FILE_HEADER* _image_file_header = NULL; _IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL; _IMAGE_SECTION_HEADER* _image_section_header = NULL; _image_dos_header = (_IMAGE_DOS_HEADER*)newBufferp; _image_file_header = (_IMAGE_FILE_HEADER*)(newBufferp + _image_dos_header->e_lfanew + 4); _image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20); _image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header + _image_file_header->SizeOfOptionalHeader); _IMAGE_SECTION_HEADER* last_image_section_header = _image_section_header + _image_file_header->NumberOfSections - 1; DWORD sizeofNewBuffer = last_image_section_header->PointerToRawData + last_image_section_header->SizeOfRawData; return sizeofNewBuffer; } /*ImageBuffer到NewBuffer函数 参数:ImageBuffer起始地址 返回值:NewBuffer起始地址 */ char* imageBuffer_to_NewBuffer(char* imageBufferp){ _IMAGE_DOS_HEADER* _image_dos_header = NULL; _IMAGE_FILE_HEADER* _image_file_header = NULL; _IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL; _IMAGE_SECTION_HEADER* _image_section_header = NULL; _image_dos_header = (_IMAGE_DOS_HEADER*)imageBufferp; _image_file_header = (_IMAGE_FILE_HEADER*)(imageBufferp + _image_dos_header->e_lfanew + 4); _image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20); _image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header + _image_file_header->SizeOfOptionalHeader); //重新计算newBuffer需要大小 /*方法一:SizeOfHEADER + 所有节的SizeOfRawData之和 int sizeofSections = 0; _IMAGE_SECTION_HEADER* _image_section_header_temp = _image_section_header; for(DWORD i = 0;i < _image_file_header->NumberOfSections;i++){ sizeofSections += _image_section_header_temp->SizeOfRawData; _image_section_header_temp++; } char* newBufferp = (char*)malloc(_image_optional_header->SizeOfHeaders + sizeofSections); */ //方法二:使用最后一个节的文件偏移地址 + 最后一个节对齐后的大小 DWORD size = compute_NewBuffer_size(imageBufferp); char* newBufferp = (char*)malloc(size); if(NULL == newBufferp){ printf("NewBuffer内存分配失败\n"); exit(0); } for(DWORD i = 0;i < size;i++){ *(newBufferp + i) = 0x00; } for(i = 0;i < _image_optional_header->SizeOfHeaders;i++){ *(newBufferp + i) = *(imageBufferp + i); } for(i = 0;i < _image_file_header->NumberOfSections;i++){ for(DWORD j = 0;j < _image_section_header->SizeOfRawData;j++){ //不用VirtualSize因为害怕会多复制覆盖下一个节 *(newBufferp + _image_section_header->PointerToRawData + j) = *(imageBufferp + _image_section_header->VirtualAddress + j); } _image_section_header++; } return newBufferp; } /*NewBuffer存盘函数 参数:需要写出数据的内存首地址,保存绝对路径,写出的数据大小(单位:字节) 返回值:成功返回1,失败返回0 */ int save_to_disk(char* newBufferp,char* storagePath,DWORD size){ FILE* fp = fopen(storagePath,"wb"); if(!fp){ printf("打开文件失败"); return 0; } int isSucceed = fwrite(newBufferp,size,1,fp); if(!isSucceed){ free(newBufferp); fclose(fp); return 0; } fclose(fp); return 1; } int main(int argc,char* argv[]){ char* filePath = "D:/C-language/file/notepad.exe"; //你要打开的PE文件绝对路径 char* storagePath = "D:/C-language/file/notepad_copy.exe"; //保存路径 char* fileBufferp = to_FileBuffer(filePath); char* imageBufferp = fileBuffer_to_ImageBuffer(fileBufferp); char* newBufferp = imageBuffer_to_NewBuffer(imageBufferp); int isSucceed = save_to_disk(newBufferp,storagePath,compute_NewBuffer_size(newBufferp)); if(isSucceed){ printf("存盘成功"); }else{ printf("存盘失败"); } free(fileBufferp); free(imageBufferp); free(newBufferp); return 0; }
2.编写一个函数,将RVA的值转换成FOA
就是将文件加载到内存时,已知一个数据在内存中的地址,将此地址转化成如果文件在硬盘上时的相对于文件起始地址的文件偏移地址。即将虚拟内存偏移地址转换成文件偏移地址。
说明:这里只考虑RVA在节中,不包含在节的空白区中!而且由于FileBuffer到ImageBuffer中,节中会有数据被初始化,这里目前来说无法解决,所以就假设节中没有未被初始化的数据,或者拉伸后未被初始化数据不影响地址转化
-
代码如下:
#include "stdafx.h" #include <stdlib.h> #include <string.h> typedef unsigned short WORD; typedef unsigned int DWORD; typedef unsigned char BYTE; //宏定义MZ标记和PE标记,方便后面判断 #define MZ 0x5A4D #define PE 0x4550 #define IMAGE_SIZEOF_SHORT_NAME 8 //DOS头 struct _IMAGE_DOS_HEADER { WORD e_magic; //MZ标记 WORD e_cblp; WORD e_cp; WORD e_crlc; WORD e_cparhdr; WORD e_minalloc; WORD e_maxalloc; WORD e_ss; WORD e_sp; WORD e_csum; WORD e_ip; WORD e_cs; WORD e_lfarlc; WORD e_ovno; WORD e_res[4]; WORD e_oemid; WORD e_oeminfo; WORD e_res2[10]; DWORD e_lfanew; //PE文件真正开始的偏移地址 }; //标准PE头 struct _IMAGE_FILE_HEADER { WORD Machine; //文件运行平台 WORD NumberOfSections; //节数量 DWORD TimeDateStamp; //时间戳 DWORD PointerToSymbolTable; DWORD NumberOfSymbols; WORD SizeOfOptionalHeader; //可选PE头大小 WORD Characteristics; //特征值 }; //可选PE头 struct _IMAGE_OPTIONAL_HEADER { WORD Magic; //文件类型 BYTE MajorLinkerVersion; BYTE MinorLinkerVersion; DWORD SizeOfCode; //代码节文件对齐后的大小 DWORD SizeOfInitializedData; //初始化数据文件对齐后的大小 DWORD SizeOfUninitializedData; //未初始化数据文件对齐后大小 DWORD AddressOfEntryPoint; //程序入口点(偏移量) DWORD BaseOfCode; //代码基址 DWORD BaseOfData; //数据基址 DWORD ImageBase; //内存镜像基址 DWORD SectionAlignment; //内存对齐粒度 DWORD FileAlignment; //文件对齐粒度 WORD MajorOperatingSystemVersion; WORD MinorOperatingSystemVersion; WORD MajorImageVersion; WORD MinorImageVersion; WORD MajorSubsystemVersion; WORD MinorSubsystemVersion; DWORD Win32VersionValue; DWORD SizeOfImage; //文件装入虚拟内存后大小 DWORD SizeOfHeaders; //DOS、NT头和节表大小 DWORD CheckSum; //校验和 WORD Subsystem; WORD DllCharacteristics; DWORD SizeOfStackReserve; //预留堆栈大小 DWORD SizeOfStackCommit; //实际分配堆栈大小 DWORD SizeOfHeapReserve; //预留堆大小 DWORD SizeOfHeapCommit; //实际分配堆大小 DWORD LoaderFlags; DWORD NumberOfRvaAndSizes; //目录项数目 //_IMAGE_DATA_DIRECTORY DataDirectory[16]; //这个先不管 }; //NT头 struct _IMAGE_NT_HEADERS { DWORD Signature; //PE签名 _IMAGE_FILE_HEADER FileHeader; _IMAGE_OPTIONAL_HEADER OptionalHeader; }; //节表 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; //该节特征属性 }; /*计算文件大小函数 参数:文件绝对路径 返回值:返回文件大小(单位字节) */ int compute_file_size(char* filePath){ FILE* fp = fopen(filePath,"rb"); if(!fp){ printf("打开文件失败"); exit(0); } fseek(fp,0,2); int size = ftell(fp); //fseek(fp,0,0); 单纯计算文件大小,就不需要还原指针了 fclose(fp); return size; } /*将文件读入FileBuffer函数 参数:文件绝对路径 返回值:FileBuffer起始地址 */ char* to_FileBuffer(char* filePath){ FILE* fp = fopen(filePath,"rb"); if(!fp){ printf("打开文件失败"); exit(0); } int size = compute_file_size(filePath); char* mp = (char*)malloc(sizeof(char) * size); //分配内存空间 if(!mp){ printf("分配空间失败"); fclose(fp); exit(0); } int isSucceed = fread(mp,size,1,fp); if(!isSucceed){ printf("读取数据失败"); free(mp); fclose(fp); exit(0); } fclose(fp); return mp; } /*虚拟内存偏移地址->文件偏移地址函数 参数:FileBuffer起始地址,RVA地址值 返回值:RVA对应的FOA,返回0则表示非法RVA */ DWORD RVA_to_FOA(char* fileBufferp,DWORD RVA){ _IMAGE_DOS_HEADER* _image_dos_header = NULL; _IMAGE_FILE_HEADER* _image_file_header = NULL; _IMAGE_OPTIONAL_HEADER* _image_optional_header = NULL; _IMAGE_SECTION_HEADER* _image_section_header = NULL; _image_dos_header = (_IMAGE_DOS_HEADER*)fileBufferp; _image_file_header = (_IMAGE_FILE_HEADER*)(fileBufferp + _image_dos_header->e_lfanew + 4); _image_optional_header = (_IMAGE_OPTIONAL_HEADER*)((char*)_image_file_header + 20); _image_section_header = (_IMAGE_SECTION_HEADER*)((char*)_image_optional_header + _image_file_header->SizeOfOptionalHeader); bool flag = 0; //用来判断最终RVA值是否在节中的非空白区 if(RVA < _image_section_header->VirtualAddress) return RVA; for(int i = 0;i < _image_file_header->NumberOfSections;i++){ if(RVA >= _image_section_header->VirtualAddress && RVA < _image_section_header->VirtualAddress + _image_section_header->Misc.VirtualSize){ flag = 1; break; }else{ _image_section_header++; } } if(!flag) return 0; DWORD mem_offset_from_section = RVA - _image_section_header->VirtualAddress; return _image_section_header->PointerToRawData + mem_offset_from_section; } int main(int argc,char* argv[]){ char* filePath = "D:/C-language/file/notepad.exe"; //你要打开的PE文件绝对路径 char* fileBufferp = to_FileBuffer(filePath); unsigned int FOA = RVA_to_FOA(fileBufferp,0x00008008); printf("0x%08X\n",FOA); //0x00007208 return 0; }-
注:这种方法可以应对绝大部分转换了,但是如果此时的内存偏移RVA所在的节含有未初始化数据,那么由于在虚拟内存拉伸后这部分数据进行初始化了,用RVA - 此节的起始地址,这个值会大于文件在硬盘上没有给未初始化数据分空间的大小,那么最后再用FileBuffer中此节的起始地址 + 此偏移,就会出现偏差。(目前还不知道怎么解决)
-
简单验证一下:

-
本文详细介绍了PE文件的结构,包括DOS头、PE头、可选头和节表等,并探讨了从FileBuffer到ImageBuffer再到NewBuffer的过程,以及内存偏移地址与文件偏移地址的转换。此外,提供了C语言实现的相关函数,用于模拟PE文件在内存中的转换和计算内存地址对应的文件偏移地址。

725

被折叠的 条评论
为什么被折叠?



