7-PE文件资源的CRUD

目录

1. 函数说明

1.1 BeginUpdateResource

1.2 UpdateResource

1.3 EndUpdateResource

1.4 FindResource

1.5 SizeofResource

1.6 LoadResource

1.7 LockResource

1.8 LoadLibraryEx

2. 从PE文件中加载资源

2.1 从当前进程中加载资源

2.1.1 exe添加资源

2.1.2 从exe中加载资源dll

2.1.3 ResHacker.exe查看文件资源

2.2 从DLL中加载资源

2.2.1 DLL添加资源

2.2.2 动态加载dll并导出dll的资源

2.2.3 ResHacker.exe查看文件资源

2.3 从静态exe文件加载资源

2.4 资源压缩和加密

2.5 代码

3. PE文件资源的增删改查

3.1 官方Demo

3.2 开源代码示例

3.3 PE文件增加/修改资源

4. 读取PE文件版本号--不使用Windows API

4.1 背景说明

4.2 通过资源读取位文件版本


1. 函数说明

1.1 BeginUpdateResource

函数功能:

开始更新指定文件的资源

函数原型:

HANDLE BeginUpdateResourceA(
  [in] LPCSTR pFileName,
  [in] BOOL   bDeleteExistingResources
);

函数参数:

pFileName:要更新资源的二进制文件。
    当前程序需要有对文件的写权限
bDeleteExistingResources:指示是否删除pFileName参数指定的文件的现有资源
    TRUE:删除指定文件现有的全部资源,更新后的文件仅包含UpdateResource函数添加的资源
    FALSE:更新后的文件会包含现有的资源,除非使用UpdateResource函数进行删除或替换

函数返回值:

成功返回有效句柄,此句柄用于后续的UpdateResource函数和EndUpdateResource函数。

如果指定的文件不是PE文件、没有写权限、文件不存在等,则返回NULL。

1.2 UpdateResource

函数功能:

添加、删除、替换PE文件中的资源。

函数原型:

BOOL UpdateResourceA(
  [in]           HANDLE hUpdate,
  [in]           LPCSTR lpType,
  [in]           LPCSTR lpName,
  [in]           WORD   wLanguage,
  [in, optional] LPVOID lpData,
  [in]           DWORD  cb
);

函数参数:

hUpdate:BeginUpdateResource返回的句柄
lpType:要更新的资源类型,有两种方式指定:
    字符串指针:如果字符串的第一个字符是#,则其余字符表示指定资源类型的整数标识符的十进制数。例如:字符串“#258”表示标识符258。
    MAKEINTRESOURCE(ID),其中ID是表示预定义资源类型的整数值。预定义的资源类型可以在VS在查看,如下所示:
lpName:要更新的资源名称,有两种方式指定:
    字符串指针:创建新资源时,不要使用以“#”字符开头的字符串。
    MAKEINTRESOURCE(ID),其中ID是资源ID
wLanguage:要更新的资源的语言标识符.
    有关构成语言标识符的主语言标识符和子语言标识符的列表,参考MAKELANGID宏。
    语言标识符:https://learn.microsoft.com/en-us/windows/win32/intl/language-identifiers
    MAKELANGID:https://learn.microsoft.com/en-us/windows/win32/api/winnt/nf-winnt-makelangid
lpData:要插入到hUpdate标识的文件中的资源数据。
    如果资源是预定义类型,则数据必须有效且正确对齐。
    注意:这是要存储在hUpdate标识的文件中的原始二进制数据,而不是LoadIcon、LoadString等特定资源的加载函数提供的数据。
    所有包含字符串或文本的数据必须采用Unicode格式,lpData不得指向ANSI数据
    如果lpData设置为NULL,cbData设置为0,则从hUpdate标识的文件中删除指定的资源。
cb:lpData参数指定的资源数据的大小,单位字节

函数返回值:

成功返回TRUE,失败返回FALSE

补充说明:

① 对于包含资源配置(RC Config)数据的文件,资源更新有一些限制。例如:

  • 语言中立文件:language-neutral (LN) files
  • 特定语言的资源文件:language-specific resource (.mui) files

详细说明:UpdateResourceA function (winbase.h) - Win32 apps | Microsoft Learn

② 预定义资源类型:

详细值定义:Resource Types (Winuser.h) - Win32 apps | Microsoft Learn

③ 程序可以多次使用UpdateResource来更新资源数据。每次调用UpdateResource都会生成一个添加、删除和替换列表,但不会立即将数据写入hUpdate标识的文件中。程序必须使用EndUpdateResource函数将所有的更新操作写入文件。

1.3 EndUpdateResource

函数功能:

提交或放弃资源更新操作

函数原型:

BOOL EndUpdateResourceA(
  [in] HANDLE hUpdate,
  [in] BOOL   fDiscard
);

函数参数:

hUpdate:BeginUpdateResource返回的句柄
fDiscard:是否放弃之前UpdateResource函数所做的资源更新操作。
    TRUE:不做更改
    FALSE:将资源更新写入文件

函数返回值:

成功返回TRUE,失败返回FALSE

补充说明:

  • 在调用此函数前,确保除BeginUpdateResource返回的文件句柄之外的所有文件句柄都已关闭。否则函数失败,GetLastError返回110

1.4 FindResource

函数功能:

从指定模块中获取指定类型、指定名称的资源句柄

函数原型:

HRSRC FindResourceA(
  [in, optional] HMODULE hModule,
  [in]           LPCSTR  lpName,
  [in]           LPCSTR  lpType
);

函数参数:

hModule:模块句柄。如果此参数为NULL,则将搜索用于当前进程的模块。
lpName:资源名称。或者此参数为MAKEINTRESOURCEA(ID),其中ID是资源的整数标识符
lpType:资源类型。或者此参数为MAKEINTRESOURCE(ID),其中ID是资源类型的整数标识符

函数返回值:

成功返回资源句柄,函数失败返回NULL

1.5 SizeofResource

函数功能:

获取指定资源的大小,单位:字节

函数原型:

DWORD SizeofResource(
  [in, optional] HMODULE hModule,
  [in]           HRSRC   hResInfo
);

函数参数:

hModule:模块句柄,该模块的可执行文件包含资源
hResInfo:资源句柄,必须使用FindResource或FindResourceEx函数创建此句柄。

函数返回值:

成功返回资源的字节数,失败返回零。

1.6 LoadResource

函数功能:

获取HGLOBAL类型句柄,该句柄可以用于获取指向内存中指定资源的第一个字节的指针。

函数原型:

HGLOBAL LoadResource(
  [in, optional] HMODULE hModule,
  [in]           HRSRC   hResInfo
);

函数参数:

hModule:模块句柄,该模块的可执行文件包含资源。如果参数为NULL,则从用于创建当前进程的模块加载资源。
hResInfo:要加载的资源句柄,此句柄由FindResource或FindResourceEx函数返回。

函数返回值:

成功返回资源相关数据句柄,失败返回NULL

1.7 LockResource

函数功能:

获取指向内存中指定资源的指针

函数原型:

LPVOID LockResource(
  [in] HGLOBAL hResData
);

函数参数:

hResData:资源句柄,LoadResource函数返回此句柄

函数返回值:

如果加载的资源可用,则返回值是指向内存中资源的第一个字节的指针;否则为NULL。

补充说明:

  • LockResource返回的指针在卸载包含该资源的模块之前一直有效。没有必要解锁资源,因为创建资源的进程终止时,系统会自动删除这些资源。
  • 不要使用FindResourceA函数或FindResourceExA函数返回的句柄锁定资源。这样的句柄指向随机数据。

1.8 LoadLibraryEx

函数功能:

将指定的模块加载到进程地址空间中

函数原型:

HMODULE LoadLibraryExA(
  [in] LPCSTR lpLibFileName,
       HANDLE hFile,
  [in] DWORD  dwFlags
);

函数参数:

lpLibFileName:要加载的模块,此模块可以是DLL或exe。
    如果模块是exe,则静态依赖的DLL不会被加载,相当于加载DLL时dwFlags参数指定了DONT_RESOLVE_DLL_REFERENCES
hFile:保留参数,必须是NULL
dwFlags:加载模块时要采取的操作。如果为0,则此函数行为与LoadLibrary函数行为相同。可以是以下值:

DONT_RESOLVE_DLL_REFERENCES

如果模块是DLL,则不调用DllMain 进行进程和线程的初始化或终止,且静态依赖的其他DLL不会被加载。

注意:此flag仅用于向下兼容,不要使用此flag。如果想要只访问DLL中的数据或资源,则使用LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE或LOAD_LIBRARY_AS_IMAGE_RESOURCE或二者。

LOAD_IGNORE_CODE_AUTHZ_LEVEL

如果使用此flag,则系统不会检查AppLocker规则或为DLL应用软件限制策略。此flag仅适用于正在加载的DLL,不适用与其依赖。建议在安装过程中必须运行提取的DLL的安装程序中使用此flag。

LOAD_LIBRARY_AS_DATAFILE

如果希望加载DLL来从中提取消息或资源,则使用此flag。

系统会将文件当成一个数据文件映射到调用进程的虚拟地址空间。所以不能使用此DLL来调用GetModuleFileName、GetProcAddress 等函数。

此flag可以和LOAD_LIBRARY_AS_IMAGE_RESOURCE一起使用。

LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE

功能与LOAD_LIBRARY_AS_DATAFILE类似,但是针对除DLL以外,是以独占写权限打开的,其他进程无法在DLL被使用时无法以写权限进行访问。但是DLL可以被其他进程进行加载。

可以与LOAD_LIBRARY_AS_IMAGE_RESOURCE一起使用

LOAD_LIBRARY_AS_IMAGE_RESOURCE

如果希望加载DLL来从中提取消息或资源,则使用此flag。

系统会将模块作为映像文件映射到进程的虚拟地址空间。但是,加载器不会加载静态依赖或执行其他常见的初始化步骤。

除非应用程序依赖于具有图像内存布局的文件,否则此值应与LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE或LOAD_LIBRARY _AS_DATAFILE一起使用

LOAD_LIBRARY_SEARCH_APPLICATION_DIR

在应用程序的安装目录中搜索DLL及其依赖项。不搜索标准搜索路径中的目录。

此值不能与LOAD_WITH_ALTERED_SEARCH_PATH组合使用。

LOAD_LIBRARY_SEARCH_DEFAULT_DIRS

此值相当于LOAD_LIBRARY_SEARCH_APPLICATION_DIR、LOAD_LIBRARY_SEARCH_SYSTEM32、LOAD_LIBRARY_SEARCH_USER_DIRS的组合。

不搜索标准搜索路径中的目录。此值不能与LOAD_WITH_ALTERED_SEARCH_PATH组合使用。

......

函数返回值:

成功返回模块句柄,失败返回NULL

补充说明:

① 将DLL作为数据文件或资源加载

LOAD_LIBRARY_AS_DATAFILE、LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE、LOAD_LIBRARY_AS_IMAGE_RESOURCE会影响进程对指定模块的引用计数。如果指定了这三个中任何一个,则加载程序会检查此DLL是否已经被当前进程作为可执行模块加载成功。

  • 如果已经加载,则LoadLibraryEx返回DLL的句柄,并增加引用计数;
  • 如果未被加载,则系统将该模块映射为数据或映像文件。LoadLibraryEx会返回加载的数据或映像文件的句柄,但不会增加模块的引用计数,CreateToolhelp32Snapshot或EnumProcessModules函数也无法看到此模块。

如果对同一文件使用LOAD_LIBRARY_AS_DATAFILE、LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE、LOAD_LIBRARY_AS_IMAGE_RESOURCE调用两次LoadLibraryEx,则会为指定模块创建两个单独的映射。

② 指定DLL及其依赖项的搜索路径

2. 从PE文件中加载资源

2.1 从当前进程中加载资源

2.1.1 exe添加资源

在VS中,资源视图->右键->添加->资源->导入:选择要添加作为资源的DLL

资源类型可以自定义一个。

添加完成以后如下所示:

2.1.2 从exe中加载资源dll

#include "stdafx.h"
#include <Windows.h>
#include <string>
#include <fstream>
#include <iostream>
#include "resource.h"

// 加载pe文件的资源
BOOL LoadMyResource(HMODULE hModule, const char* lpszResourceType, int uiResourceName, std::string& outBuf)
{
	BOOL res = FALSE;
	do
	{
		// 1. Get the resources in the specified module
		HRSRC hRsrc = FindResourceA(hModule, MAKEINTRESOURCEA(uiResourceName), (LPCSTR)lpszResourceType);
		if (hRsrc == NULL)
			break;
		// 2. Get resource size
		DWORD dwSize = SizeofResource(hModule, hRsrc);
		if (dwSize <= 0)
			break;
		// 3. Load resource into memory
		HGLOBAL hGlobal = LoadResource(hModule, hRsrc);
		if (hGlobal == NULL)
			break;
		// 4. Lock resource
		LPVOID lpVoid = (hGlobal);
		if (lpVoid == NULL)
			break;

		outBuf.assign((char*)lpVoid, dwSize);
		res = TRUE;
	} while (0);

	return res;
}

void writeToFile(const std::string& data, const std::string& filePath)
{
	std::ofstream ofs(filePath, std::ios::out | std::ios::binary);
	if (!ofs.is_open())
	{
		std::cout << "打开文件失败" << std::endl;
		return;
	}
	ofs << data << std::endl;
	ofs.close();
}

void testExeRes()
{
	std::string data;
	BOOL res = LoadMyResource(GetModuleHandleA(NULL), "BIN", IDR_BIN1, data);
	std::string filePath = "exeRes.dll";
	writeToFile(data, filePath);
}

void testDLLRes()
{
	std::string data;
	std::string dllPath = "ResContainer.dll";
	HMODULE hModule = LoadLibraryA(dllPath.c_str());
	BOOL res = LoadMyResource(hModule, "BIN", IDR_BIN1, data);
	std::string filePath = "dllRes.dll";
	writeToFile(data, filePath);
}

int main()
{
	testExeRes();
	//testDLLRes()

	system("pause");
    return 0;
}

执行结果:

原先的DLL导出函数如下:

我们将其作为资源添加到exe中,从内存中加载并写到文件中,如下所示:

可以看到,DLL的导出函数是完全一样的。

2.1.3 ResHacker.exe查看文件资源

使用ResHacker.exe可以查看文件的资源,如下所示:

2.2 从DLL中加载资源

2.2.1 DLL添加资源

在VS中,资源视图->右键->添加->资源->导入:选择要添加作为资源的DLL

资源类型可以自定义一个。

添加完成以后如下所示:

2.2.2 动态加载dll并导出dll的资源

#include "stdafx.h"
#include <Windows.h>
#include <string>
#include <fstream>
#include <iostream>
#include "resource.h"

// 加载pe文件的资源
BOOL LoadMyResource(HMODULE hModule, const char* lpszResourceType, int uiResourceName, std::string& outBuf)
{
	BOOL res = FALSE;
	do
	{
		// 1. Get the resources in the specified module
		HRSRC hRsrc = FindResourceA(hModule, MAKEINTRESOURCEA(uiResourceName), (LPCSTR)lpszResourceType);
		if (hRsrc == NULL)
			break;
		// 2. Get resource size
		DWORD dwSize = SizeofResource(hModule, hRsrc);
		if (dwSize <= 0)
			break;
		// 3. Load resource into memory
		HGLOBAL hGlobal = LoadResource(hModule, hRsrc);
		if (hGlobal == NULL)
			break;
		// 4. Lock resource
		LPVOID lpVoid = LockResource(hGlobal);
		if (lpVoid == NULL)
			break;

		outBuf.assign((char*)lpVoid, dwSize);
		res = TRUE;
	} while (0);

	return res;
}

void writeToFile(const std::string& data, const std::string& filePath)
{
	std::ofstream ofs(filePath, std::ios::out | std::ios::binary);
	if (!ofs.is_open())
	{
		std::cout << "打开文件失败" << std::endl;
		return;
	}
	ofs << data << std::endl;
	ofs.close();
}

void testExeRes()
{
	std::string data;
	BOOL res = LoadMyResource(GetModuleHandleA(NULL), "BIN", IDR_BIN1, data);
	std::string filePath = "exeRes.dll";
	writeToFile(data, filePath);
}

void testDLLRes()
{
	std::string data;
	std::string dllPath = "ResContainer.dll";
	HMODULE hModule = LoadLibraryA(dllPath.c_str());
	BOOL res = LoadMyResource(hModule, "BIN", IDR_BIN1, data);
	std::string filePath = "dllRes.dll";
	writeToFile(data, filePath);
}

int main()
{
	//testExeRes();
	testDLLRes();

	system("pause");
    return 0;
}

执行结果:

原先的DLL导出函数如下:

我们动态加载dll,并从这个dll中导出资源:

可以看到,DLL的导出函数是完全一样的。

2.2.3 ResHacker.exe查看文件资源

使用ResHacker.exe可以查看文件的资源,如下所示:

2.3 从静态exe文件加载资源

FindResource函数有一个参数需要传入模块句柄:

  • 如果从当前进程加载资源,则GetModuleHandleA(NULL)可以获得模块句柄;
  • 如果从DLL加载资源,则LoadLibraryA可以获得DLL模块的句柄;
  • 如果从本地某个静态的exe文件中,该怎么做呢?

使用LoadLibraryEx加载exe文件获取其模块句柄。

2.4 资源压缩和加密

对于放在资源里的文件,可以:

① 压缩文件

可以将文件压缩以后存入资源;使用时,获取内存中的资源,并对其进行解压使用

② 加密文件

可以将文件加密以后存入资源;使用时,获取内存中的资源,并对其进行解密使用

2.5 代码

代码详见:

3. PE文件资源的增删改查

3.1 官方Demo

Using Resources - Win32 apps | Microsoft Learn

官方Demo功能:将Dialog资源从Hand.exe复制到Foot.exe:

3.2 开源代码示例

https://github.com/avih/perc.git

3.3 PE文件增加/修改资源

代码详见:

4. 读取PE文件版本号--不使用Windows API

4.1 背景说明

在VS中,我们如果想给一个exe添加版本信息,操作如下:

对于生成的exe,其版本信息会保存在资源中。如下所示:

一般来说,如果想获取一个文件的版本信息,需要调用WinAPI:

  • GetFileVersionInfoSize
  • GetFileVersionInfo
  • VerQueryValueA

但是我们也可以直接通过读取文件的资源进行获取

4.2 通过资源读取位文件版本

#include <Windows.h>
#include <iostream>
#include <string>
#include <winver.h>
#pragma comment(lib, "Version.lib")

bool getFileVersionByWinAPI(std::string filePath, std::string& version)
{
	DWORD dwHandle = 0;
	DWORD dwLen = GetFileVersionInfoSizeA(filePath.c_str(), &dwHandle);
	if (dwLen == 0)
	{
		return false;
	}

	CHAR* pBuffer = new CHAR[dwLen];
	BOOL ok = GetFileVersionInfoA(filePath.c_str(), dwHandle, dwLen, pBuffer);
	if (!ok)
	{
		delete[] pBuffer;
		return false;
	}

	VS_FIXEDFILEINFO* pVerValue = NULL;
	UINT nSize;
	ok = VerQueryValueA(pBuffer, "\\", (LPVOID*)&pVerValue, &nSize);
	if (!ok)
	{
		delete[] pBuffer;
		return false;
	}

	char VerStr[64];
	sprintf_s(VerStr, "%d.%d.%d.%d",
		HIWORD(pVerValue->dwFileVersionMS),
		LOWORD(pVerValue->dwFileVersionMS),
		HIWORD(pVerValue->dwFileVersionLS),
		LOWORD(pVerValue->dwFileVersionLS)
	);

	version = VerStr;
	delete[] pBuffer;
	return true;
}

typedef struct tag_VS_VERSIONINFO
{
	WORD             wLength;        // 00 length of entire version resource
	WORD             wValueLength;   // 02 length of fixed file info, if any
	WORD             wType;          // 04 type of resource (1 = text, 0 = binary)
	WCHAR            szKey[16];      // 06 key -- VS_VERSION_INFO
	WORD             Padding1;       // 26 padding byte 1
	VS_FIXEDFILEINFO Value;          // 28 fixed information about this file (13 dwords)
} VS_VERSIONINFO, *PVS_VERSIONINFO;  // 5C

bool getFileVersionFromResource(std::string filePath, std::string& version)
{
	bool res = false;
	do
	{
		HMODULE hMod = LoadLibraryExA(filePath.c_str(), NULL, 
			DONT_RESOLVE_DLL_REFERENCES | LOAD_LIBRARY_AS_DATAFILE | LOAD_LIBRARY_AS_IMAGE_RESOURCE);
		if (hMod == NULL)
			break;

		HRSRC hRsrc = FindResourceA(hMod, MAKEINTRESOURCE(1), RT_VERSION);
		if (hRsrc == NULL)

			break;

		DWORD dwSize = SizeofResource(hMod, hRsrc);
		if (dwSize <= 0)
			break;

		HGLOBAL hGlobal = LoadResource(hMod, hRsrc);
		if (hGlobal == NULL)
			break;

		LPVOID lpVoid = LockResource(hGlobal);
		if (lpVoid == NULL)
			break;
		
		VS_VERSIONINFO* pVer = (VS_VERSIONINFO*)(lpVoid);
		
		char verStr[64];
		sprintf_s(verStr, "%d.%d.%d.%d",
			HIWORD(pVer->Value.dwFileVersionMS),
			LOWORD(pVer->Value.dwFileVersionMS),
			HIWORD(pVer->Value.dwFileVersionLS),
			LOWORD(pVer->Value.dwFileVersionLS)
		);
		version = verStr;

		res = true;
	} while (0);

	return res;
}

int main()
{
	std::string filePath = "C:\\Program Files\\Common Files\\Glodon Shared\\GCP\\2.0.0.642\\1GCPHost.exe";
	std::string versionFromRes;
	std::string versionFromWinAPI;

	getFileVersionFromResource(filePath, versionFromRes);
	std::cout << versionFromRes << std::endl;

	getFileVersionByWinAPI(filePath, versionFromWinAPI);
	std::cout << versionFromWinAPI << std::endl;
	return 0;
}

执行结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值