作者:AntGhazi/2001.12.14 主页:antghazi.yeah.net
在网上有很多关于PE文件格式的说明,讲得最多莫过于IMAGE_DOS_HEADER、IMAGE_NT_HEADERS、IMAGE_SECTION_HEADER、等。而对于节的介绍最多的,也莫过于函数引入引出节。而关于资源节.rsrc的介绍则少之又少。好了,废话少说。 PE文件格式如下: ![Pe File Format 6737 bytes ]](http://www.csdn.net/develop/article/images/11618_1.gif) 对于PE的详细介绍在MSDN中也有,邹丹(www.zaodan.com)与罗大侠(asm.yeah.net)的主页上也有细详的介绍。这里我在修改ICON中的一种做法。讲解中所用到的语句并不全面,重要的是这个思路。最后面我会给出一个直修改资源的函数。 首先,我们需要两个可执行文件,并且已知这两个exe文件都有图标资源。 1、 peSource.exe (从此文件中提取图标) 2、 peDesc.exe (将图标写入此文件) 第二部,分别打开这两个文件,hFileSource设为只读,hFileDesc设为可写。 HANDLE hFileSource; HANDLE hFileDesc; 打开后,大家最常用的莫过于文件映射,这里为方便与直观,我们直接把文件读到一个内存块中。 //先得到长度 DWORD dwSourceSize =::GetFileSize(hFileSource); DWORD dwDescSize =::GetFileSize(hFileDesc); DWORD byte_write=0; //读取 char *pFileSource =new char[dwSourceSize]; char *pFileDesc =new char[dwDescSize];
::ReadFile(hFileSource,pFileSource,dwSourceSize,&byte_write,0); ::ReadFile(hFileDesc,pFileDesc,dwDescSize,&byte_write,0);
好了,现在我们已经分别将两个文件读入内存中。让我们先将pFileSource指到资源节的头部。Section的结构说明如下: 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; 通常情况,资源节的名称一般都为:.rsrc。目前我们只考虑这种情况。 IMAGE_DOS_HEADER *dosHeadA=(IMAGE_DOS_HEADER *)pFileSource; //DOS头 IMAGE_NT_HEADERS *ntHeadA=(IMAGE_NT_HEADERS *) (pFileSource + dosHeadA->e_lfanew); //NT头 IMAGE_SECTION_HEADER *secHeadA=(IMAGE_SECTION_HEADER *)((char *)ntHeadA+ sizeof(IMAGE_NT_HEADERS)); //第一个节的首地址
//循环找出.rsrc节 for(int i=0;i<ntHeadA->FileHeader .NumberOfSections ;i++,secHeadA++){ if(strcmp((char *)secHeadA->Name,".rsrc")==0){ //找到.rsrc节 break; } } 好了,现在我们已经找到.rsrc节表。根据节表,我们就可以找到资源的入口地址。 IMAGE_RESOURCE_DIRECTORY *dirResourceA=(IMAGE_RESOURCE_DIRECTORY *)((char *)pFileSource + secHeadA->PointerToRawData); //得到资源入口地址
到这里,我才开始讲到我们今天的目的----资源结构,下面有几个需要用到的结构与相关的解释: // Resource Format. //
// // Resource directory consists of two counts, following by a variable length // array of directory entries. The first count is the number of entries at // beginning of the array that have actual names associated with each entry. // The entries are in ascending order, case insensitive strings. The second // count is the number of entries that immediately follow the named entries. // This second count identifies the number of entries that have 16-bit integer // Ids as their name. These entries are also sorted in ascending order. // // This structure allows fast lookup by either name or number, but for any // given resource entry only one form of lookup is supported, not both. // This is consistant with the syntax of the .RC file and the .RES file. //
typedef struct _IMAGE_RESOURCE_DIRECTORY { //资源树结构 DWORD Characteristics; //标识此资源的类型 DWORD TimeDateStamp; WORD MajorVersion; WORD MinorVersion; WORD NumberOfNamedEntries; WORD NumberOfIdEntries; //此结构下还包函有的资源结构树,即:还有几个子树。 // IMAGE_RESOURCE_DIRECTORY_ENTRY DirectoryEntries[]; //请注意这里,下面还会讲到。 } IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY; 此结构的其他解释请见VC的头文件winnt.h. 整个资源的结构就好像一棵树型,不同资源如:menu,icon,dialog,cursor等。都如同每根树枝,树枝的Characteristics会标识不同的资源类型,而每根树枝又会有子树枝。这样一直循环,直到IMAGE_RESOURCE_DIRECTORY的NumberOfIdEntries为零时才结束。通常情况,子树都为为三层。每一个子树的类型由IMAGE_RESOURCE_DIRECTORY中的Characteristics来标识。如:当第一层的Characteristics==3时,则说明此结构为ICON资源。Characteristics类型定义如下(可在winuser.h中找到): /* * Predefined Resource Types */ #define RT_CURSOR MAKEINTRESOURCE(1) #define RT_BITMAP MAKEINTRESOURCE(2) #define RT_ICON MAKEINTRESOURCE(3) #define RT_MENU MAKEINTRESOURCE(4) #define RT_DIALOG MAKEINTRESOURCE(5) #define RT_STRING MAKEINTRESOURCE(6) #define RT_FONTDIR MAKEINTRESOURCE(7) #define RT_FONT MAKEINTRESOURCE(8) #define RT_ACCELERATOR MAKEINTRESOURCE(9) #define RT_RCDATA MAKEINTRESOURCE(10) #define RT_MESSAGETABLE MAKEINTRESOURCE(11)
总结构如下(偷懒,copy而来):
![[PE ResourceTree 12787 bytes ]](http://www.csdn.net/develop/article/images/11618_2.gif) 好了,整个资源的结构已经弄清楚了。现在我们要做的就是得到每个子资源的入口地址。这里要用到的一个结构是: // Each directory contains the 32-bit Name of the entry and an offset, // relative to the beginning of the resource directory of the data associated // with this directory entry. If the name of the entry is an actual text // string instead of an integer Id, then the high order bit of the name field // is set to one and the low order 31-bits are an offset, relative to the // beginning of the resource directory of the string, which is of type // IMAGE_RESOURCE_DIRECTORY_STRING. Otherwise the high bit is clear and the // low-order 16-bits are the integer Id that identify this resource directory // entry. If the directory entry is yet another resource directory (i.e. a // subdirectory), then the high order bit of the offset field will be // set to indicate this. Otherwise the high bit is clear and the offset // field points to a resource data entry. //
typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY { union { struct { DWORD NameOffset:31; DWORD NameIsString:1; }; DWORD Name; WORD Id; }; union { DWORD OffsetToData; //指向资源的入口址 struct { DWORD OffsetToDirectory:31; DWORD DataIsDirectory:1; //指向下一级目录的相对地址 }; }; }IMAGE_RESOURCE_DIRECTORY_ENTRY, *PIMAGE_RESOURCE_DIRECTORY_ENTRY; 上面对IMAGE_RESOURCE_DIRECTORY_ENTRY的解释也已经是非常清楚了。 结构中有两个成员:OffsetToData,DataIsDirectroy,当DiataIsDirectroy大于0时,则说明此结构还有下一级目录,否则,OffsetToData肯定不为0。那OffsetToData的值就是我们所得到的资源入口的RVA了。 那么,IMAGE_RESOURCE_DIRECTORY_ENTRY结构应该怎么得到呢?让我们再看一下,IMAGE_RESOURCE_DIRECTORY的结构说明吧。
typedef struct _IMAGE_RESOURCE_DIRECTORY { //资源树结构 DWORD Characteristics; //标识此资源的类型 DWORD TimeDateStamp; WORD MajorVersion; WORD MinorVersion; WORD NumberOfNamedEntries; WORD NumberOfIdEntries; // 、、//IMAGE_RESOURCE_DIRECTORY_ENTRY DirectoryEntries[]; //紧跟在后面的就是 IMAGE_RESOURCE_DIRECTORY_ENTRY结构数组,DirectoryEntries数组的个数实际上也就是NumberOfIdEntries.你也可以理解为 IMAGE_RESOURCE_DIRECTORY_ENTRY DirectoryEntries[NumberOfIdEntries];, } IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY; 那这样一看来,IMAGE_RESOURCE_DIRECTORY_ENTRY的第一个地址等于父树地址加上IMAGE_RESOURCE_DIRECTORY结构的大小即可。 如:IMAGE_RESOURCE_DIRECTORY *dirTempB=(IMAGE_RESOURCE_DIRECTORY *)((char *)dirResourceB+entryResourceB->OffsetToDirectory);
最后一个是IMAGE_RESOURCE_DATA_ENTRY结构,比较简单,大家看一下就知道了。 // Each resource data entry describes a leaf node in the resource directory // tree. It contains an offset, relative to the beginning of the resource // directory of the data for the resource, a size field that gives the number // of bytes of data at that offset, a CodePage that should be used when // decoding code point values within the resource data. Typically for new // applications the code page would be the unicode code page. //
typedef struct _IMAGE_RESOURCE_DATA_ENTRY { DWORD OffsetToData; DWORD Size; DWORD CodePage; DWORD Reserved; } IMAGE_RESOURCE_DATA_ENTRY, *PIMAGE_RESOURCE_DATA_ENTRY;
好了,讲了这么多,现在我们可以开始计算了,(我们以读取第三层第一个ICON为例<通常资源都为三层>): 前面我们已经得到根资源的地址:dirResourceA
IMAGE_RESOURCE_DIRECTORY *dirResourceA=(IMAGE_RESOURCE_DIRECTORY *)((char *)pFileA + secHeadA->PointerToRawData); //根 IMAGE_RESOURCE_DIRECTORY_ENTRY *entryResourceA=(IMAGE_RESOURCE_DIRECTORY_ENTRY *)((DWORD)dirResourceA + sizeof (IMAGE_RESOURCE_DIRECTORY)); IMAGE_RESOURCE_DIRECTORY *dirTemp; //第二层 IMAGE_RESOURCE_DIRECTORY_ENTRY *entryTemp; IMAGE_RESOURCE_DIRECTORY *dirTempICON; //第三层 IMAGE_RESOURCE_DIRECTORY_ENTRY *entryTempICON; IMAGE_RESOURCE_DATA_ENTRY *entryData; //资源入口结构 for(i=0;i<(dirResourceA->NumberOfIdEntries+dirResourceA->NumberOfNamedEntries);i++,entryResourceA++){ //所有资源 if(entryResourceA->Name==3){ //ICON dirTemp=(IMAGE_RESOURCE_DIRECTORY *)((char *)dirResourceA+entryResourceA->OffsetToDirectory); entryTemp=(IMAGE_RESOURCE_DIRECTORY_ENTRY *)((char *)dirTemp+sizeof(IMAGE_RESOURCE_DIRECTORY)); for(int k=0;k<(dirTemp->NumberOfIdEntries+dirTemp->NumberOfNamedEntries);k++,entryTemp++){ //子目录 if(entryTemp->DataIsDirectory >0){ //还有子目录 dirTempICON=(IMAGE_RESOURCE_DIRECTORY *)((char *)dirResourceA + entryTemp->OffsetToDirectory ); entryTempICON=(IMAGE_RESOURCE_DIRECTORY_ENTRY *)((char *)dirTempICON + sizeof(IMAGE_RESOURCE_DIRECTORY)); entryData=(IMAGE_RESOURCE_DATA_ENTRY *)((char *)dirResourceA + entryTempICON->OffsetToData ); //资源入口结构 break; //得到后跳出 } } } }
最后,读入内存中: DWORD dwIconSize=entryDataA->Size; char *pSrcIcon=entryDataA->OffsetToData - secHeadA->VirtualAddress + (char *)dirResourceA; char *pSourceIcon= new char[dwIconSize+1]; memcpy(pSourceIcon,pSrcIcon,dwIconSize); 最后得到的数据就在pSourceIcon中了。 同理,得到另一个文件中的ICON入口地址,用pSourceIcon覆盖之即可。 函数地址:http://go3.163.com/antghazi/main3.htm 写得比较零乱,如有不明或有错误的地方,欢迎指正。 mailto:[email protected] http://antghazi.yeah.net AntGhazi/2001.12.14 
|