Modify/Update resources of an Exe/DLL on the fly
Sometimes it is useful to modify (add / remove / delete) resources in an exe and/or DLL file at run time.
Introduction
Sometimes, it is useful to modify (add / remove / delete) resources in an EXE and/or DLL file at run time. This is not the limit, we can also modify the icon and version information on the fly and give a new look to our EXE/DLL files and load them with new features.
What are the ways to do this? Obviously, writing a separate module that will modify the resources. Let's go through the details, but before that, we need to understand the background details of the key terms.
Background knowledge
DLL
A DLL (Dynamic Link Library) is a file that includes the source code or callable functions that provide a service to the application currently being executed.
EXE
EXE is a stand-alone application that could be executed on any platform. EXE is an independent application while a DLL is dependent on an application's call.
Window resource
A window resource (I am talking about Win32 resource) instance consists of the following attributes:
- Type: cursors, dialogs, bitmaps etc.
- Name: a Unicode string or a 2-byte numeric identifier.
- Language: a 2-byte numeric value indicating the language of the resource instance; e.g., English (U.S.) = 1033, Chinese (Traditional) = 1028.
- Data: the actual data of the resource (e.g., a bitmap image).
Logic details
Here, I am introducing the logic and code that replaces a BITMAP
resource in an exe file by an externally provided bitmap image.
To modify an exe file, we need to do the following steps:
- get the handle of the exe file.
- find the resources (get handle) that replace the existing resources of the exe file.
- load the resource (which replaces the existing resource of the exe file) into global memory.
- lock the resource into global memory.
- update the resource in the exe file.
- end the resource update.
- close the handles.
Code snippets and explanations
// Get the bmp into memory HANDLE hFile = CreateFile(bmpPath, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); DWORD FileSize = GetFileSize(hFile, NULL);
In the above lines of code, I am taking the handle of my BITMAP
file and calculating its size. Here, bmpPath(char array)
stores the full path of my .bmp file, GENERIC_READ
is the requested access to the file, the third parameter 0
specifies that if CreateFile
succeeds, the file cannot be shared and cannot be opened again until the handle to the file is closed, OPEN_EXISTING
specifies to open the file only if it exists, and FILE_ATTRIBUTE_NORMAL
has the file attributes and flags, FILE_ATTRIBUTE_NORMAL
being the most common default value for files. After execution of the above two lines, hFile
contains a .bmp image handle, and FileSize
contains its size in bytes.
//reading image into global memory.
BYTE *pBuffer = new BYTE[FileSize];
ReadFile(hFile, pBuffer, FileSize, &dwBytesRead, NULL);
The second step (in the above two lines) is to read data in to memory. Here, dwBytesRead
is the integer type variable that receives the number of bytes read when using a synchronous hFile
parameter. ReadFile
sets this value to zero before doing any work.
// Write in the new resources
HANDLE hExeFile = BeginUpdateResource(exePath, FALSE);
After execution of the above line, hExeFile
contains the handle that can be used by the UpdateResource
function to modify (add, delete, or replace) resources in a binary module. exePath(char array)
stores the full path of the exe file.
The next step is more important and needs extra care. The BITMAP
resource in the exe file has an ID, and first, we need to retrieve it. We can get it easily using an API function. For the time being, I am using a hardcoded ID directly. The BITMAP
image which replaces the exe's BITMAP
resource must be in binary form and must not contain header information when it is passed as an argument to the UpdateResource
function. The main reason behind this is, the exe file just replaces the raw BITMAP
data, not its header information, which means, after the execution of the UpdateResource
function, the new image contains the same header information as the old one, but new BITMAP
data. To implement this, we have to explicitly skip the header information in the BITMAP
file that replaces the exe resource.
// just skipping the header information and calculating modifying size.
BYTE *newBuffer = pBuffer + sizeof(BITMAPFILEHEADER);
DWORD NewFileSize = FileSize - sizeof(BITMAPFILEHEADER);
Now, after doing all the necessary work, UpdateResource
will be executed.
// update resouce.
UpdateResource(hExeFile, RT_BITMAP, MAKEINTRESOURCE(130),
MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),
(LPVOID)newBuffer, NewFileSize);
In the above line, hExeFile
is a module (exe file) handle returned by the BeginUpdateResource
function, referencing the file to be updated. Also, we need to pass the resource's type, name, and language ID information. Here, RT_BITMAP
is the resource type, MAKEINTRESOURCE(130)
is its name (130 is the ID of the exe's BITMAP
resource), and MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US)
is its language ID. newBuffer
is the binary data of the .bmp file that does not have header information.
After execution of the UpdateResource
function, it is necessary to call the EndUpdateResource
function because it commits or discards the changes made prior to a call to UpdateResource
.
EndUpdateResource(hExeFile, FALSE);
// release handle and memory.
CloseHandle(hFile);
delete[] pBuffer;