目录
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;
}
执行结果: