关于资源的修改

68 篇文章 2 订阅
31 篇文章 1 订阅

前两天写一个程序修改exe的资源信息。资源信息的读取API还是比较多的,但是修改的就不多了。经常使用的ResHack工具,修改资源方便的很

,但是却没有源码参考。只得自己动手。修改资源的方法可以从API和PE两种方式入手。

一、首先介绍从PE入手修改资源:
这里我从网上摘抄了一个修改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而来):

好了,整个资源的结构已经弄清楚了。现在我们要做的就是得到每个子资源的入口地址。这里要用到的一个结构是:
// 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覆盖之即可。

二、下面介绍用API修改资源的方法。
使用PE格式修改资源看起来很复杂,但只要了解了PE结构就很容易上手。但一直另我没能理解的是,RecHack工具,我使用该工具倘若仅仅是修

改资源中的一个无关紧要的字符串,结果修改前后的两个PE文件就会在多处发生不同。所以,我个人认为资源的修改还是小心为妙。采用API来

修改应该会保险一些。

涉及到的API
1、Use the LoadLibrary function to load the executable file Hand.exe. 
2、Use the FindResource and LoadResource functions to locate and load the dialog box resource. 
3、Use the LockResource function to retrieve a pointer to the dialog box resource data. 
4、Use the BeginUpdateResource function to open an update handle to Foot.exe. 
5、Use the UpdateResource function to copy the dialog box resource from Hand.exe to Foot.exe. 
6、Use the EndUpdateResource function to complete the update.

以上是MSDN中提供的修改资源信息的步骤和函数。
我下面以修改一个PE文件的版本信息为例,来说明资源的修改
1、通过GetFileVersionInfoSize获取资源版本信息缓冲大小
DWORD GetFileVersionInfoSize(LPCTSTR lptstrFilename,    LPDWORD lpdwHandle);
2、申请缓冲后调用GetFileVersionInfo函数,得到资源版本相关的全部信息
BOOL GetFileVersionInfo( LPCTSTR lptstrFilename,    DWORD dwHandle,    DWORD dwLen,    LPVOID lpData);
3、通过函数VerQueryValue得到制定内容的缓冲
BOOL VerQueryValue( LPCVOID pBlock,    LPCTSTR lpSubBlock,    LPVOID *lplpBuffer,    PUINT puLen);
其中参数lpSUbBlock可以有三个值:
/  返回VS_FIXEDFILEINFO 结构体的指针,该指针必然在pBlock缓冲内部
/VarFileInfo/Translation  返回在资源的页面语言信息,该指针业在pBlock内
/StringFileInfo/lang-codepage/string-name 通过制定的lang-codepage查询相应的资源中的字符串缓冲,同样该缓冲也指向pBlock
由此可见,我们得到了pBlock的相应内容的缓冲地址,修改里边的内容就很容易了。
修改完毕后,调用BeginUpdateResource   UpdateResource  和EndUpdateResource 这样一个文件的版本信息就修改完了。

然而在实际工作中发现,调用这些函数返回的字符串缓冲在pBlock缓冲的尾部,看起来很想是刚刚分配的,而修改该缓冲而后update,资源版

本的字符串并没有被修改。后来尝试采用Unicode的API,结果返回的缓冲就真的在pBlock的原始缓冲并且通过update可以成功修改了。

这里只是简单的列举了具体的操作步骤。以后再府上源代码。


采用API修改资源的信息也还是很有局限性的,有很多内容都不能从现有的API中查询,如果采用字符串匹配的方式替换,又还是走了PE结构的

路。所以,打算以后有时间还是改写一些程序功能强大点的资源修改器。

最后,提一下加壳文件的资源版本修改后,整个程序就会异常。所以任何修改资源的方法也都是存在局限性的。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值