Understanding Malware Patching: Resources

Jean-Pierre LESUEUR (Microsoft MVP)
Phrozen
Published in
19 min readMay 17, 2024

Abstract

Recently, we launched a new series of articles explaining how malware authors empower their users by enabling them to generate customized versions of malware payloads. Our first article explored the technique known as EOF (End Of File), also referred to as PE Overlay, which has been commonly used by malware creators in the past. In this second article, we will explore another widely recognized and employed method for storing and retrieving malware configurations: the use of Windows Application Resources.

Before reading this article, it is highly recommended to read the first one to catch the essential concepts behind the necessity and stakes of allowing end-users of distributed malware to edit the configuration of the payload: Understanding Malware Patching: EOF (End Of File)

What Are Resources

Resources” refer to various types of data that are bundled with an application executable (.exe), dynamic link library (.dll) or any other Portable Executable files (.scr, .mui, .ocx, etc.). These resources can include things like images, icons, sounds, dialog box layouts, strings, manifests and other (any) types of data that are used by the application during runtime.

One purpose of resources is to separate application data from code, making it easier to organise, manage, and modify. Resources are stored in a separate section (PE section) of the executable file and can be accessed programmatically by the application.

Resources are not always contained within the current application; programmers often opt to distribute different resources across one or multiple other application files or libraries to enhance data segregation. For instance, it’s quite common to house application images or icons or even program translation texts within specific DLLs, a practice aimed at achieving better separation of concerns.

Exploring application resources can be done programmatically or through dedicated applications. One of the most renowned tools for exploring Windows application resources is undoubtedly “Resource Hackerby Angus Johnson. This tool allows users to easily inspect, modify, add, and extract resources from PE files.

Resource Hacker by Angus Johnson

Most Portable Executable explorers also offer the capability to inspect resources. However, these explorers tend to be more “raw” and generalist and then less user-friendly compared to specialized tools like Resource Hacker (requires some knowledge about PE format).

PEView (Viewing .rsrc section header)

We won’t dig into the internal of Windows file resources or the Portable Executable format in this article. Countless excellent articles already cover this subject extensively. Just keep in mind that resource information such as the Directory Table, Entry, and String, along with the resource data itself, are hosted within a dedicated PE section known as “.rsrc,” in accordance with the PE format specification and accessing application resources is typically accomplished through dedicated Windows APIs, although it’s not the sole method. Dedicated Windows API’s serves as a wrapper specifically designed for parsing the PE resource section for convenience. The PE resource section stands out as one of the most complex PE sections to work with, but Windows has simplified resource manipulation through their dedicated APIs. It’s worth noting that some malware authors might bypass using these well-known and potentially flagged APIs by manually parsing the resource section via the PE header informations.

An excellent starting point for a good peak into the Portable Executable (PE) format is Microsoft’s thorough documentation. You can find it here.

In this article, we will only focus on the technique employed by malware authors to enable their end-users to configure their payloads using Windows APIs for application resource management. Additionally, as an extra exercise, you can explore performing this without relying on Windows APIs to gain a deeper understanding of PE resources and the PE Header in general.

A Quick Theory

Due to their versatility and user-friendly nature, application resources are a commonly exploited technique by malware authors to enable end-users to edit the configuration of distributed payloads. The wide nature of data types that can be stored within resources (anything) — ranging from raw binary files and other applications or libraries to images and plain text — makes them an ideal vehicle for this purpose. Malware authors can allow their end-users to create or patch a special resource containing the configuration data located in either a standard resource category (Ex: RCDATA, RTEXT, ICON etc..) or a non-standard resource category (Defined by the malware author). This flexibility enables the customization and adaptation of malware payloads to suit specific needs or evade poor detection mechanisms.

Indeed, it’s not uncommon for malware authors to use the RCDATA resource category, as specified by Microsoft, for storing binary data, despite some configurations being stored in plain text. While it’s generally advisable to select an appropriate resource standard category based on the data type to be stored, there’s no strict rule against deviating from this guideline.

Opting for a user-defined resource (non-standard) category, can offer advantages in terms of data separation. However, it may also trigger immediate suspicion from security analysts and antivirus programs, as it deviates from typical usage patterns.

Storing malware configurations, although still used today, has become less favored compared to the past, primarily due to its accessibility through PE viewers/editors and specialized resource editing software. Even in scenarios where the payload is obfuscated or encrypted — quite a common practice — detection during the initial stages of static analysis using high level tools is highly probable. Furthermore, the inclusion and usage of Windows API calls for resource management in a malware sample can raise red flags, sparking suspicion and potentially leading to its tracing by security analysts. As a result, malware developers are continually refining their tactics to evade detection, often by exploring alternative methods for storing and managing configuration data.

At the end of the practical chapter, as a bonus, I’ll include a practical example illustrating a method to enhance the complexity of detecting stored resource payloads. This technique involves storing the malware within an image, a normal element typically found in a dedicated resource category. However, in reality, the image serves as a covert carrier for our malware payload. By using such techniques, malware authors can significantly complicate the static analysis detection process, as the malicious payload remains hidden within seemingly benign resources.

The Programmatic Angle

When handling resource data programmatically with dedicated Windows APIs, the process is straightforward and relatively simple. The principles involved are similar to those used for reading and writing data to the End Of File (EOF). Despite variations in the specific APIs employed for resource manipulation, the underlying principles remain almost the same.

Writing Data (Builder)

We will follow the following recipe to write our fictitious malware configuration to a dedicated resource:

  1. Acquire an handle to stub resources.
  2. Create a “local” copy of the target stub resources, incorporating our configuration, to be stored in RCData.
  3. Commit changes and release the stub resources handle.

As for the EOF technique, our malware configuration, represented as a record, will be stored as raw bytes. As raw resources are best suited for this purpose, we’ll use the RCDATA resource category. For the resource identifier, we’ll use “MALZCONF.” This name serves to illustrate our article, but in practice, it could be anything.

To acquire a handle to the target stub file’s resource data, we call the Windows API function BeginUpdateResourceW:

HANDLE BeginUpdateResourceW(
[in] LPCWSTR pFileName,
[in] BOOL bDeleteExistingResources
);
var AStubFile : String;
var hResource : THandle;

// ...

hResource := BeginUpdateResourceW(PWideChar(AStubFile), True);

In a simple call, it’s important to note a few key aspects. Invoking BeginUpdateResource doesn’t lock the target file for reading or writing, allowing multiple calls on the same file without returning errors. Additionally, other applications may still access and modify the file concurrently.

The only role of BeginUpdateResource is to obtain a handle on a valid portable executable file, enabling manipulation of its resources via a kind of “local copy and list of changes”. The changes are only committed to the file after calling EndUpdateResourceW, which is the only API that may fail if, for example, the file is opened by another process.

The “bDeleteExistingResources” function parameter is set to true here because our stub doesn’t contain any other critical resources necessary for its operation. When this parameter is set to true, target file resource section is reset, all existing resources won’t be preserved. Only new ones will be present.

Set this function parameter to true only if existing resources are non-critical and you intend to retain only your own resources. In production malware, this option is rarely set to true to avoid interfering with other genuine resources and to maintain stealth if the malware itself is hosted inside a genuine application (“Trojanised” application).

With a handle to the target stub resource, we are ready to call the UpdateResourceW Windows API to insert our malware configuration resource:

BOOL UpdateResourceW(
[in] HANDLE hUpdate,
[in] LPCWSTR lpType,
[in] LPCWSTR lpName,
[in] WORD wLanguage,
[in, optional] LPVOID lpData,
[in] DWORD cb
);
var AConfig : TMalwareConfig;

const RES_NAME = 'MALZCONF';

UpdateResourceW(
hResource, // Resource Handle
RT_RCDATA, // Resource Category
RES_NAME, // Resource Name
0, // LANG_NEUTRAL > SUBLANG_NEUTRAL Language
@AConfig, // Malware Config Structure (Pointer)
SizeOf(TMalwareConfig) // Size of our Structure
)

“lpType” is set to RT_RCDATA, a standard resource category in Microsoft Windows, serving as an alias for:

MAKEINTRESOURCEW(10); // RT_RCDATA
// Or
PWideChar(10); // RT_RCDATA

As we have seen previously, this resource category is best suited for raw data. Replacing this constant with a custom identifier would create a new, non-standard resource category.

The “lpName” parameter serves as our malware configuration resource name identifier.

The “wLanguage” parameter is a something we haven’t talked yet. In reality, the hierarchy of a resources isn’t just two-level (“Resource Category” -> “Resource Identifier”); there’s a third level known as the resource language. This language designation enables the provision of different language versions for a single resource. This functionality is ideal for applications supporting multiple languages, enabling the presence of multiple copies of a resource, each with the correct translation depending on system configuration or user choice.

The resource language is defined as a Word (2-bytes value) and calculated using the MAKELANGID Macro, which takes two parameters: the Primary language identifier and a Sublanguage identifier. Setting this parameter to 0 is equivalent to calling:

MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL);

Which is the default option for non-internationalized resources.

Note that if you wish, and if it’s part of your evasion or obfuscation strategy, you can still use internationalized resources with a different set of languages to store and retrieve chunks of your data.

Both “lpData” and “cb” respectively serve the purpose of copying a memory region pointed to by “lpData,” which contains our malware configuration record memory location, to our new resource, “cb” is the size of memory region to be copied.

Finally, to commit changes to our target stub file, we call “EndUpdateResourceW”.

BOOL EndUpdateResourceW(
[in] HANDLE hUpdate,
[in] BOOL fDiscard
);
EndUpdateResourceW(
hResource, // Resource Handle
False // False = Write Data, True = Discard
);

This API comes into play to finally apply modifications to the target file. Until this moment, changes were essentially cached, awaiting commitment. By calling “EndUpdateResource” with “fDiscard” set to False, the PE header and structure of the target file are written and rebuilt with our changes. This API should be called only when you are ready to apply modifications and when no other pending operations are for the target file; otherwise, it will fail. The “fDiscard” parameter can be used to ensure everything is correct (for example, every call to UpdateResource) before pushing the big button.

Changes to the stub file should now be reflected in your favorite Resource Explorer tool or PE Editor:

MALZCONF resource content located in standard RCData Resource Category

Reading Data (Stub)

Now that we’ve covered how to write and update application resources to store our fictitious malware configuration, let’s delve into how to read back our data.

While updating a resource cannot be done via Windows APIs in a running application or any loaded module, reading application resources can be done in various scenarios — whether in a running module or application, either current or remote. To obtain a pointer to the concerned resource block, we will use the FindResourceW function.

HRSRC FindResourceW(
[in, optional] HMODULE hModule,
[in] LPCWSTR lpName,
[in] LPCWSTR lpType
);
var hResourceBlock : THandle;

const RES_NAME = 'MALZCONF';

hResourceBlock := FindResourceW(0, RES_NAME, RT_RCDATA);

When the first parameter is set to zero, it indicates that we are attempting to read the target resource from the current loaded module. In the case of our stub, this refers to the current process image itself. It’s important to note that if the resource was stored in the non-default language, you must use FindResourceExW to specify the language.

The returned resource block handle corresponds to the DATA entry memory offset of our target resource along with resource PE Section, which is relative to the module’s base address (RVA). For example, if the base address is 0x000F0000, the resource section RVA is 0x45000, and the DATA Entry offset is 0x48, the value will be equal to 0x00135048.

0xF0000 + 0x45000 + 0x48 = 0x135048

Both the resource section data entry offset and the resource section RVA typically remain constant. You can gather these two pieces of information using your favorite PE Viewer or Editor or by parsing PE Header Information programmatically. Simply append these constants with the module’s base address (which may change due to ASLR) to avoid the need to use this API.

Identify Resource Section RVA manually (PEView)
Identify DATA Entry Offset manually (PEView)

From the previous step, we gathered the offset of our malware configuration resource data entry relative to current process base address.

Despite being called a handle, the FindResourceW call returns a pointer to our resource data entry:

“MALZCONF” Resource Data Entry (PEView)

The next step is to retrieve the memory offset to the resource data using the LoadResource API using retrieve resource data entry memory offset (hResourceBlock):

HGLOBAL LoadResource(
[in, optional] HMODULE hModule,
[in] HRSRC hResInfo
);
var hResource : THandle := LoadResource(0, hResourceBlock);

This time, LoadResource is responsible for retrieving a pointer (also confusingly called an handle) to the data as specified in the resource data entry. For example, in the last PEView image: “0x0004506C”, this is where the resource data fits in memory. Remember, since this is a Relative Virtual Address (RVA), this value will be relative to the current process base address.

If the base address is 0xF0000, the data memory offset will be equal to 0x13506C.

At this step, we are already able to access the content of the resource, as you can see with this example:

DumpMalwareConfig(PMalwareConfig(Pointer(hResource))^);
Configuration Dump via DumpMalwareConfig() Call

Hmm, this is kinda weird. Normally, we should call the LockResource API to obtain the memory offset of the resource data. However, you will be surprised to see that both LoadResource and LockResource return the exact same memory offset:

LPVOID LockResource(
[in] HGLOBAL hResData
);
var hResource : THandle := LoadResource(0, hResourceBlock);
var pData : Pointer := LockResource(hResource);

WriteLn(Format('ImageBase: [0x%p], hResource(LoadResource): [0x%p], pData(LockResource): [0x%p]', [
Pointer(GetModuleHandle(nil)),
Pointer(hResource),
pData
]));

Result in:

In modern Windows versions, the LockResource API has been essentially useless since Windows 2000. Calling LockResource is no longer necessary; it is still present for backward compatibility from when resources were handled differently. Previously, LoadResource was used to load the resource into memory, and LockResource was called to access this mapped memory resource securely. However, today we often still use both, either because programmers are unaware of this backward compatibility detail or simply to follow the usual pattern.

To avoid possible API detection, skipping the use of this superfluous API can be beneficial.

The last function that may come in handy before closing this chapter is the SizeofResource API. As the name implies, it retrieves the size of the resource data. In our case, it is not necessary since we already know the size of our malware config record. However, this function can be useful for extracting resources when the size is not already known:

DWORD SizeofResource(
[in, optional] HMODULE hModule,
[in] HRSRC hResInfo
);
var pDataSize : DWORD := SizeOfResource(0, hResourceBlock);

In this context, hModule refers to our current process, and hResourceBlock is the data entry structure offset retrieved by the FindResourceW call.

Detection

Detecting suspicious resources can be fairly easy depending on how they were stored (location, format, name, etc.) and how they are retrieved. The first step is to use tools like Resource Hacker or more specialized tools for inspecting malicious indicators in Windows application files, such as the mighty PEStudio by Marc Ochsenmeier:

PEStudio www.winitor.com — Resources

As you can see, PEStudio highlights suspicious resources with unknown or uncommon signatures.

Another step involves observing the imports section for resource management APIs. Once again, PEStudio can easily highlight and group those APIs:

PEStudio — Imports

Keep in mind that more skilled malware developers can circumvent using those APIs to access resource data. Therefore, do not rely on imports as a serious indicator of malicious activity. As we explained previously, calculating the resource offset manually is relatively simple with minimal effort.

The most effective method to detect malicious usage of resources is through static analysis via disassembly and dynamic analysis. This approach can require significant time and effort, depending on how the malicious sample implements its techniques.

Retro fun fact: Back in the mid-2000s, roughly between 2005 and 2008, Avira Antivirus gained a reputation for its effectiveness in detecting malware that used the resource section to store additional payloads. Due to the uncommon nature and size of these payloads, Avira at that time, presented a formidable challenge for malware authors seeking to evade detection. However, one particularly successful technique employed during that era to bypass Avira’s detection was to split the malicious resource into multiple smaller chunks, each just a few kilobytes in size. By doing so, Avira would often overlook these smaller resources, considering them individually too small to pose a threat, thereby enabling the malware to evade detection. This strategy underscored the cat-and-mouse game between malware authors and antivirus software developers, highlighting the constant evolution of techniques used on both sides of the cybersecurity landscape.

Bonus: The malware is in the details

Running Stub with Extracted and Executed Shellcode

As we mentioned earlier, detecting rudimentary malware artifacts in resources can often be child’s play due to their nature (such as plain-text data, high-entropy data, or other suspicious binaries) or their unexpected location, naming, and properties.

Many malware authors have abandoned this technique in favor of more stealthy methods, which we’ll discuss in the next articles of this series. This shift is due to the ease with which malware relying on resources to function can sometimes be detected.

However, there are some sneaky techniques that exist to make malicious resources appear perfectly normal and innocent. This is precisely what the Bonus project demonstrate.

Level: 🍊 Intermediate

The Goal

Execute shellcode that spawns a local thread running a calc.exe instance. The shellcode must be stored in resources, encoded inside a bitmap image to remain unnoticeable (via steganography).

How it works

To minimize detection and avoid raising suspicions, we must always blend into the background. As application resources on Windows include categories like RT_BITMAP, primarily designed to store Device-Independent Bitmap (DIB) files for application needs, leveraging this category is ideal.

The shellcode is blended within our host bitmap file using a simple steganography technique.

Steganography is the practice of concealing a message, file, or other data within another message, file, or medium in order to hide its existence. It aims to ensure that the hidden message is only accessible to the intended recipient and remains undetected by others.

The technique employed is as follows: the shellcode is converted to its binary representation (byte per byte) using the shift right operator and stored in a byte array containing it’s binary representation.

// Snippet to print the binary representation of a byte (8-bit)
var AByte : byte := 241;
for var I := SizeOf(AByte) -1 downto 0 do
Write((AByte shr I) and 1); // Print "11110001"
WriteLn;

Each pixel of the host bitmap is scanned, and each bit of the shellcode is continuously encoded into the Red value of the current pixel. In 24-bit and 32-bit bitmaps, pixels are represented as RGB and RGBA respectively, where R(byte) = Red, G(byte) = Green, B(byte) = Blue, and for 32-bit bitmaps, A(byte) = Alpha to support transparency.

Bitmap Header (24-bit / 32-bit) — No palette

To encode and blend the shellcode into the Red value of the pixel, we use a straightforward technique: if the current scanned shellcode bit is equal to “1”, we ensure the current pixel’s Red value is odd; otherwise, if the current shellcode bit is equal to “0”, we ensure the current pixel’s Red value is even. This simple yet effective technique is incredibly efficient since adjusting a value to its nearest odd or even value only changes 1 least-significant bit, which keeps the current pixel’s Red value very close to its original value, rendering it completely invisible to the human eye.

// Force value to be odd
var AByte : Byte := 240; // 11110000
AByte := AByte or 1; // Set the least significant bit to 1
// AByte now = 11110001 (241)

// Force value to be even
AByte := AByte and not 1; // Clear the least significant bit to 0
// AByte now = 11110000 (240)

It’s crucial to consider one small detail: our stub (the application responsible for reading and decoding the shellcode from the image) doesn’t know the size of our shellcode, so it doesn’t know how many pixels to scan. Therefore, we must first encode the size of the shellcode into our bitmap using the same technique before encoding the shellcode itself. At runtime, the stub will initially retrieve the UInt64 (64-bit) encoded size of the shellcode, then continue scanning the bitmap until the complete shellcode is decoded.

Payload Format Schema Encoded within Red Pixel Values per Bitmap Pixel

The demonstrated technique however has a downside: storing our shellcode requires a lot of space, as one bit of the shellcode equal one pixel. For example, if our shellcode size is 16 bytes, it will require 16 bytes * 8 (byte to bit length) pixels, totaling 128 pixels required for storage. This can quickly become lengthy. We could consider changing the algorithm by leveraging the Green and Blue channels, or even the Alpha channel for 32-bit bitmaps, to reduce the number of pixels needed for encoding. This could help optimize the space usage while maintaining stealthiness.

Project Source Code

The source code for the Bonus project is available on the official Unprotect Project GitHub account and is entirely written in the latest version of Delphi. You can download the Delphi Community Edition for free and build the project without any additional dependencies.

⚠️ Make sure to compile the stub in release mode to avoid resource patching issues that can corrupt the stub file. I have investigated why Debug releases of Delphi binaries cause resource patching to corrupt the file but haven’t found any clues.

The Delphi programming language was chosen because, even in 2024, it has evolved significantly over the last decade in terms of functionalities and components. Delphi is a very mature language for modern application development, particularly on Windows. It remains an excellent choice for malware development due to its seamless integration with Windows APIs in the IDE, allowing for rapid development of offensive tools.

However, choosing a programming language involves not only its advantages but also personal preferences. Fortunately, Delphi’s Pascal-derived syntax is very close to algorithmic pseudocode, making it easy to understand even for my readers, not fluent in Delphi or programming in general. Additionally, in this project, I did not rely on any strong Delphi-specific libraries / objects. Bitmaps are handled manually, including resource patching, reading, and memory management — no shortcuts were taken. As a result, porting this project to C/C++ would be a piece of 🎂.

Components

Builder: This application is responsible for encoding and hiding user-defined shellcode within the chosen Bitmap file. It then patches the Stub resource section with the encoded Bitmap.

Stub: This application is responsible for decoding the hidden shellcode within the resource Bitmap and executing it in a new local thread.

example_bitmap.bmp: A default bitmap for testing is provided within the root repository. You can choose either a Bitmap file or a DIB file (without Bitmap File Header) for testing purposes.

shellcode (Binary File Format): For security reasons, no default shellcode is provided within the project. You can use your custom-created shellcode or generate a shellcode using msfvenom with the desired effects. Make sure to match the shellcode architecture with the stub architecture; otherwise, execution will fail.

Bonus Conclusion, an evil scenario

This bonus application demonstrates the challenges posed by evasion techniques in malware development. Malware authors continually encounter new obstacles and employ new smart methods to evade both security software and human detection.

Our example illustrated a straightforward technique: encoding our malicious payload (shellcode) within an image to hide it from human eye. However, consider the following, potentially more dangerous scenario:

A malicious actor could employ this technique within a widely used application that contains genuine bitmap resources crucial for program functionality. They could then hide a malicious shellcode within one of these bitmap resources, using the same encoding technique demonstrated earlier. Next, they could locate a code cave within an executable section of the same application and insert a bitmap decoder (this time in low-level like assembly). This decoder would be responsible for reading the image, decoding the hidden shellcode, and executing it.

The execution of this decoder code could be triggered by hijacking the original entry point of the target application or by specific actions within the application itself. This approach poses a significant threat as it leverages trusted resources and mechanisms within the application, making detection and mitigation more challenging.

Identifying a trojanized application can be exceedingly difficult until the damage is already done. That’s why it’s imperative to exercise extreme caution when downloading even well-known applications, as they could have been compromised. Always verify the integrity and signature of the software, if available, to mitigate the risk of unwittingly installing malicious software.

Conclusion

This bonus project concludes the second chapter of the “Understanding Malware Patching” series, this time focusing on application resources. In the next chapter, we will delve into a more complex technique to implement using pure Windows API: Custom PE section for storing data.

As always, you can find a complete example illustrating this article in the resource sub-directory on the official GitHub repository:

I hope you enjoy the format of my articles. While they may be a bit lengthy, the goal is to make them accessible to both experienced readers interested in delving deeper into certain topics related to malware, as well as to novices or individuals who are not in the field but might be curious to learn more about this fascinating world.

What also sets my articles apart from those that are much more technical-focus is my love for adding a touch of nostalgia by including flashbacks to anecdotes from the past. Coming from the malware scene since its early days, this is an additional element that I can bring to my articles.

Even if I can only contribute a simple idea through my articles, it means a lot to me. Please feel free to give me feedback and recommendations anytime.

--

--