使用VS2013实现修改程序的资源

Jonesy

发布日期: 2018-11-16 16:37:41 浏览量: 501
评分:
star star star star star star star star star star_border
*转载请注明来自write-bug.com

背景

之前写了一个从程序中释放自定义资源功能的小程序,既然资源可以被定位出来,并能够获取其数据写入到本地文件中。那么,自然可以相信,我们能够实现一个这样的小程序,替换或者说更改其他程序中的资源。

事实上,的确是可以这么做的。因为EXE格式的文件是PE文件,如果熟悉PE文件格式,那么就会了解PE文件中有个资源节表块,按一定的结构格式专门存放着程序的所有资源。所以,我们可以根据PE结构去获取程序的资源。

当然,如果你不了解PE结构的话,也能实现这个功能的小程序。因为,Windows为我们提供了相应操作资源的API函数接口,我们可以不用去了解API函数具体的实现原理,具体是如何遍历或是更改PE结构的,就能实现我们想要的功能。

现在,我把这个小程序的实现原理和实现过程,写成文档,分享给大家。

函数介绍

BeginUpdateResource 介绍

该函数返回一个可被UpdateResource函数使用的句柄以便在一个可执行文件中增加、删除或替换资源。

函数声明

  1. HANDLE BeginUpdateResource(
  2. LPCTSTR pFileName, // executable file name
  3. BOOL bDeleteExistingResources // deletion option
  4. );

参数

  • pFileName:指向一个表示结束的空字符串指针,它是用来指定用以更新资源的基于32-位可执行文件的文件名。应用程序必须获得访问这个文件的可写权限,并且此文件在当前状态下不能被执行。如果pFileName未被指定完全路径,系统将在当前路径下搜寻此文件。
  • bDeleteExistingResources:说明是否删除pFileName参数指定的现有资源。如果这个参数为TRUE则现有的资源将被删除,而更新可执行文件只包括由UpdateResource函数增加的资源。如果这个参数为FALSE,则更新的可执行文件包括现有的全部资源,除非通过UpdateResource特别说明被删除或是替换的。

返回值

  • 如果此函数运行成功,其值将通过使用UpdateResource和EndUpdateResource函数返回一个句柄。如果被指定的文件不是一个可执行文件,或者可执行文件已被装载,或者文件不存在,或是文件不能被打开写入时,则返回值为空。若想获得更多的错误信息,请调用GetLastError函数。

UpdateResource 介绍

增加 删除 或替文件中的资源。

函数声明

  1. BOOL UpdateResource(
  2. HANDLE hUpdate, // update-file handle
  3. LPCTSTR lpType, // resource type
  4. LPCTSTR lpName, // resource name
  5. WORD wLanguage, // language identifier
  6. LPVOID lpData, // resource data
  7. DWORD cbData // length of resource data
  8. );

参数

  • hUpdate:指定更新文件句柄。此句柄由BeginUpdateResource函数返回。
  • lpType:指向说明将被更新的资源类型的字符串,它以NULL为终止符。这个参数可以是一个通过宏MAKENTRESOURCE传递的整数值,含义参见EnumResLangProc\lpType。
  • lpName:指向说明待被更新的资源名称的字符串,它以NULL为终止符。这个参数可以是一个通过宏MAKEINTRESOURCE传递的整数值。
  • wLanguage:指定将被更新资源的语言标识。要了解基本的语言标识符以及由这些标识符组成的字语言标识符的列表,可参见宏MAKELANGID。
  • lpData:指向被插入可执行文件的资源数据的指针。如果资源是预定义类型值之一,那么数据必须是有效且适当排列的。注意这是存储在可执行文件中原始的一进制数据,而不是由Loadlcon,LoadString或其他装载特殊资源函数提供的数据。所有包含字符串、文本的数据必须是Unicode格式;IpData不能指向ANSI数据。如果lpData为NULL,所指定的资源将从可执行文件中被删除。
  • cbData:指定lpData中的资源数据数据大小,以字节计数。

返回值

  • 如果函数运行成功,返回值为非零;如果函数运行失败,返回值为零。若想获得更多的错误信息,请调用GetLastError函数。

EndUpdateResource 介绍

终止在可执行文件中的资源更新。

函数声明

  1. BOOL EndUpdateResource(
  2. HANDLE hUpdate, // update-file handle
  3. BOOL fDiscard // write option
  4. );

参数

  • hUpdate:用于资源更新的句柄。此句柄通过BeginUpdateResource函数返回。
  • fDiscard:用来说明是否向可执行文件中写入资源更新内容。如果此参数为TRUE,则在可执行文件中无变化;如果此参数为FALSE,则在可执行文件中写入变化。

返回值

  • 如果函数运行成功,并且通过调用UpdateResource函数指定的不断积聚的资源修正内容被写入指定的可执行文件,那么其返回值为非零。如果函数运行失败,其返回值为零。若想获得更多的错误信息,请调用GetLastError函数。

实现原理

现在,我们结合上述介绍的API函数,讲解下程序实现的原理。

  • 首先,我们先打开替换文件,并获取替换文件的数据

  • 然后,我们对目标文件的资源进行替换。先定根据目标文件的“资源类型”以及“资源名称”定位出被替换的资源,接着将替换文件的资源数据写入进去

  • 结束资源的更改操作

这样,我们就完成了资源的更改操作。

编码实现

  1. BOOL ChangeExeRes(char *pszSrcFileName, char *pszInstallFileName, UINT uiDestResId, char *pszDestResType)
  2. {
  3. BYTE *pData = NULL;
  4. DWORD dwDataSize = 0;
  5. // 打开目标文件获取数据
  6. HANDLE hFile = ::CreateFile(pszInstallFileName, GENERIC_READ,
  7. FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING,
  8. FILE_ATTRIBUTE_ARCHIVE, NULL);
  9. if (INVALID_HANDLE_VALUE == hFile)
  10. {
  11. FreeRes_ShowError("CreateFile");
  12. return FALSE;
  13. }
  14. // 获取文件大小
  15. dwDataSize = ::GetFileSize(hFile, NULL);
  16. // 申请动态内存
  17. pData = new BYTE[dwDataSize];
  18. if (NULL == pData)
  19. {
  20. FreeRes_ShowError("new");
  21. return FALSE;
  22. }
  23. // 读取数据
  24. DWORD dwRet = 0;
  25. if (FALSE == ::ReadFile(hFile, pData, dwDataSize, &dwRet, NULL))
  26. {
  27. FreeRes_ShowError("ReadFile");
  28. return FALSE;
  29. }
  30. // 关闭文件句柄
  31. ::CloseHandle(hFile);
  32. // 更改资源
  33. HANDLE hUpdate = ::BeginUpdateResource(pszSrcFileName, FALSE);
  34. if (NULL == hUpdate)
  35. {
  36. FreeRes_ShowError("BeginUpdateResource");
  37. return FALSE;
  38. }
  39. // 如果资源ID存在, 则替换资源; 否则创建资源
  40. BOOL bRet = ::UpdateResource(hUpdate, pszDestResType, (LPCSTR)uiDestResId, LANG_NEUTRAL, pData, dwDataSize);
  41. if (FALSE == bRet)
  42. {
  43. FreeRes_ShowError("UpdateResource");
  44. return FALSE;
  45. }
  46. ::EndUpdateResource(hUpdate, FALSE);
  47. // 释放内存
  48. delete[]pData;
  49. pData = NULL;
  50. return TRUE;
  51. }

程序测试

我们在 main 函数中调用上述封装好的接口函数,进行测试。

main函数为:

  1. int _tmain(int argc, _TCHAR* argv[])
  2. {
  3. // 更改资源
  4. BOOL bRet = ChangeExeRes("C:\\Users\\DemonGan\\Desktop\\ResourceFree_Test\\Debug\\ResourceFree_Test.exe", "C:\\Users\\DemonGan\\Desktop\\test.txt", 130, "MYRES");
  5. if (FALSE == bRet)
  6. {
  7. printf("Change Resource Error!\n");
  8. }
  9. else
  10. {
  11. printf("Change Resource OK!\n");
  12. }
  13. system("pause");
  14. return 0;
  15. }

测试结果:

在测试之前,使用资源编辑器 “eXeScope” 查看 “ResourceFree_Test.exe”文件里的资源情况:资源类型“MYRES”,资源ID为“130”的资源内容如下图所示。

然后,我们创建一个替换上述资源的替换文件 “test.txt”,内容如下图所示。

然后,运行我们的更改资源的小程序,提示更改资源成功。

然后,使用再使用资源编辑器 “eXeScope” 查看 “ResourceFree_Test.exe”文件里的资源情况:资源类型“MYRES”,资源ID为“130”的资源内容如下图所示。

由上图可见,资源已经被成功更改替换了,替换的数据内容正是我们的替换文件的数据内容。所以,程序测试成功。

总结

对于这个小程序来说,只需理解使用BeginUpdateResource、UpdateResource以及EndUpdateResource这 3 个API函数就好。然后,自己动手多操作一两次,加深理解。

参考

参考自《Windows黑客编程技术详解》一书

上传的附件 cloud_download ChangeResource_Test.7z ( 154.02kb, 2次下载 )

发送私信

如果这世界上真有奇迹,那只是努力的另一个名字

12
文章数
9
评论数
eject