Understanding Malware Patching: EOF (End Of File)

Jean-Pierre LESUEUR (Microsoft MVP)
Phrozen
Published in
15 min readFeb 10, 2024

Abstract

In this new series of articles, we will delve into a crucial aspect of malware development: the ability to modify certain elements of an already compiled piece of malware, particularly when dealing with closed-source malware. This feature is especially significant for updating the malware’s default configuration.

When malware developers create their malicious programs, such as a Remote Access Trojan (RAT) or a Downloader (Just to quote two example), and decide to distribute them as compiled binaries, they face a critical decision point. They must consider how to enable their users to modify crucial dynamic data within the malware’s payload (also called a stub). We will examine both well-known and lesser-known techniques, providing insights into the methods utilised in malware development and distribution.

In each article of our series, we will provide comprehensive examples crafted in Delphi to illustrate the technique discussed. These examples are designed to demonstrate the storage and retrieval of configuration settings for a hypothetical HTTP Downloader, encompassing a variety of data types such as strings, numbers, and booleans. Specifically, we will cover how to configure and modify:

  • The HTTP Server Host (a string, array of character),
  • The HTTP Server Port (a number),
  • The File to Download URI (a string), and
  • The option to retry on download error (a boolean).

These parameters have been selected to showcase the different methods of storing and managing various data types within malware configurations, offering a practical perspective on the customisation capabilities that malware authors provide to their users.

To illustrate the articles in this series, I’ve chosen to use Delphi for two primary reasons. Firstly, Delphi is a language that I personally enjoy using for quickly developing code snippets that heavily depend on Windows API. Its efficiency and effectiveness in this domain make it an ideal choice for demonstrating the concepts discussed. Secondly, Delphi is based on the Pascal language, which is known for its algorithmic clarity and simplicity. This characteristic makes it accessible and easy to read, even for those who may not have a strong background in programming.

It’s also worth noting that Delphi code can be relatively easily ported to C, C++ or other language. For the purposes of our discussions on malware configuration and modification techniques, I will primarily focus on direct usage of Microsoft Windows APIs rather than relying on the native Delphi Windows function wrappers.

The Concept

As briefly mentioned in the abstract, when a malware developer sets out to create a tool that is easily reusable — whether for their own use or to share with others in public or private communities — they must consider how to dynamically change the malware’s configuration settings. This necessity arises for example in the context of a file downloader (as shown in this series of articles), where malware user needs to update HTTP server information (File url, Server host, port etc..) for Remote Access Trojans (RATs) and other piece of malware, the complexity of configuration can escalate quickly.

Given that malware is often distributed as closed-source (compiled binary), the author must seek methods to allow users to patch a malware stub with their own configurations and requirements.

At this effect, malware authors frequently provide a fancy and user-friendly interface (GUI) to enable users, particularly those with limited technical skills often referred to as “script kiddies,” to patch malware settings. This approach allows users to easily create their own payloads without the need to interact with CLI or modify configuration files directly.

Bandook 1.35 by Princeali Server Editor.

It worth noting that malware authors commonly refer to the feature for modifying stub settings as “Edit Server,” even though, in the context of a Remote Access Trojan (RAT), it is actually (most of the time) the client side of the malware that is being edited. This terminology dates back to a time when RATs used direct connections, with the “client” being the attacker’s program and the “server,” the victim’s program. This naming convention has persisted a long time to avoid confusion, despite the shift in the underlying technology and approach. We will here refer as editing / patching or updating the stub.

Turkojan 4.0 by CigiCigi (FµNGµ§)

There are multiple techniques for achieving this, and our series will explore the following:

  • EOF (End Of File) / PE Overlay
  • Resources (PE Resource Section)
  • Dedicated PE Section
  • Others (including hard-coded addresses, placeholders, local files, remote files, etc.)

It’s important to note that we will not discuss techniques such as source code patching and compilation. Although prevalent, our articles will focus exclusively on compiled binaries.

It’s worth mentioning that malware authors frequently offer end-users multiple techniques (user’s choice) for embedding configuration data within their creations. For instance, DarkComet allowed its users to choose between EOF and Resource for configuration storage.

A Word About Data Format

This first article, though extensive, serves as an essential foundation for our series. Before delving into the core topics, it’s crucial to address several preliminary aspects, including the format of the configuration data that will be stored. Malware configuration can use any data format, each with its own set of advantages and considerations, some examples:

  • Plain Text: Often in the form of CSV (Comma-Separated Values) or PSV (Pipe-Separated Values) to separate different configuration properties. This format is straightforward but might lack the complexity needed for more detailed configurations.
127.0.0.1|443|/download/malware.exe|1
  • JSON (JavaScript Object Notation): This format is particularly well-suited for complex configurations. It is easier to code, maintain, read, and extend. However, it necessitates robust JSON parser libraries within the malware, which could increase its size.
{
"hosts": [
{
"host": "127.0.0.1",
"port": 443,
"uri": "download/malware1.exe",
"retry": true
},
{
"host": "127.0.0.1",
"port": 443,
"uri": "download/malware2.exe",
"retry": false
}
]
}
  • XML (eXtensible Markup Language): Offers similar benefits to JSON in terms of complexity and extensibility but is more cumbersome to implement. XML parsing also requires substantial libraries, making it less common in malware written in low-level languages. However, it’s more frequently seen in malware developed in high-level languages like Java, C#, or Python.
<configuration>
<hosts>
<host>
<ip>127.0.0.1</ip>
<port>443</port>
<uri>download/malware1.exe</uri>
<retry>true</retry>
</host>
<host>
<ip>127.0.0.1</ip>
<port>443</port>
<uri>download/malware2.exe</uri>
<retry>false</retry>
</host>
</hosts>
</configuration>
  • Structures: Finally, this method is perhaps the most commonly employed, and it is the focus of our demonstration. Here, each malware configuration property is equivalent to a property within a structure, serialised as a byte array. This approach is the most challenging to decipher — demanding a thorough understanding of how the malware writes and reads data, alongside significant reverse engineering efforts to identify each property’s type and size. The primary drawback is that managing and expanding complex configurations can quickly become difficult.
struct malwareConfig {
uint16_t Magic;
char Host[100];
uint16_t Port;
char FileNameURI[255];
bool RetryIfFail;
};

It’s essential to know that in real-world scenarios, configurations are rarely stored in plain text. Instead, they are obfuscated, either through encryption or custom encoding techniques. Given that configuration data is critical for attribution, forensic analysis, malware identification and takedown, it is imperative to this information safe in the time. This obfuscation serves as a strategic measure to complicate the analysis process, thereby extending the malware’s effective lifespan and hindering efforts to dissect and counteract its functions.

Some talented reverse engineers and programmers are specialised in creating tools to extract and parse common encountered malware configurations, shorting security response time.

EOF (End Of File)

The Theory

EOF (End Of File), also known as PE Overlay, is a technique used for storing data — ranging from configuration settings to entire files — by appending it to the end of a Portable Executable (PE) file. This method works because appending data to a PE file does not alter or corrupt its execution. The underlying reason this technique works is that the PE Header, which define the Windows application structure and execution, ignores any content that falls outside the specifications described within it. More simply, out of “window” data is ignored.

Shortly, the Windows Loader, which is responsible for creating a new process, maps various sections (Ex: code, data, exports, imports, and resources etc..) into the new process memory. These sections are essential for the application’s correct execution. Each section within the PE file is defined by a header (IMAGE_SECTION_HEADER) that instructs the Windows Loader on where to place the section’s content in memory. Since the data appended at the EOF is not described in the application’s PE Header, it is effectively ignored by the Windows Loader and not mapped into memory. This allows the appended data to remain in the file without impacting the executable’s functionality, making EOF an effective method for stealthily including additional data within a PE file.

You can quickly verify the theory by using a simple command to append additional data to any valid PE file, regardless of its architecture. The command to use in a Windows environment is:

# copy /b <pe_file.exe> + <extra_file.1> + ... <dest_file.exe>
copy /b dbgview64.exe + extra_data.txt "dbgview64+EOF.exe"
dbgview64+EOF.exe is now missing Microsoft Corporation Digital Signature.
Appended content to application file.

After executing this command, you will see that the resulting file, “dbgview64+EOF.exe”, remains a valid application and can run normally. However, it’s important to note that appending data in this manner changes the content of the file, which in turn alters its checksum. Consequently, if the original file was digitally signed, this signature would no longer be valid due to the modification of the file’s content.

The Programmatic Angle: Writing Data

Now that you have a solid understanding of the concept (hopefully), let’s explore how we can take advantage of some Windows APIs to achieve the same result, with the capability of reading the appended data back. Our focus will initially be on the builder side of the process. This component is responsible for patching new data into the stub. The steps required to accomplish this are as follows:

  1. Open the target stub file.
  2. Set the file cursor to the end of the file.
  3. Define and update malware configuration structure.
  4. Write the malware configuration data to the current position of the file cursor.

That’s it!

The whole process is straightforward: to open the target stub file, we will use the Windows API CreateFileW for Unicode strings, or CreateFileA when working with ANSI strings:

hFile := CreateFileW(
'TargetStub.exe', // Target stub file
GENERIC_WRITE, // We want to write data on that file
0, // Prevent other process from opening the file
nil, // Ignored
OPEN_EXISTING, // File must exists
FILE_ATTRIBUTE_NORMAL, // File attribute (common)
0 // Ignored
);
if hFile = INVALID_HANDLE_VALUE then
Exit(); // Check GetLastError()

If the operation is successful, we will obtain a handle to our stub file.

It’s important to note that for greater control, malware authors often perform additional checks after opening a stub file. At a minimum, they validate that the file is a valid executable. In more robust scenarios, they may even check if it is the expected stub file. These precautions help in ensuring that the malware configuration data is appended to the correct file, enhancing the malware’s reliability and effectiveness.

The next step involves moving the file cursor at the end of the file, for which we use the SetFilePointer API. To achieve this, we specify FILE_END as the parameter for dwMoveMethod. Additionally, we set the distance to move to zero, indicating our desire to position the cursor precisely at the end of the file:

const INVALID_SET_FILE_POINTER = $FFFFFFFF;

if SetFilePointer(hFile, 0, nil, FILE_END) = INVALID_SET_FILE_POINTER then
Exit(); // Check GetLastError()

With the file cursor placed at the end of the file, we can now proceed to populate the malware configuration structure with the desired values:

const MAGIC_NUMBER = $BEEF;

type
TMalwareConfig = record
Magic : Word;
Host : String[100];
Port : Word;
FileNameURI : String[255];
RetryIfFail : Boolean;
end;
PMalwareConfig = ^TMalwareConfig;

This example demonstrate a basic malware downloader’s configuration. A notable aspect of this configuration is the use of the Magic property. This property serves two essential functions. Firstly, it enables the stub to verify the presence of valid and expected configuration data by checking if the Magic attribute matches a predefined MAGIC_NUMBER. Secondly, it allows the builder to determine if a malware configuration already exists within the file. If so, the builder can opt to replace the existing configuration instead of appending a new version to the end of the file indefinitely.

var AConfig : TMalwareConfig;

// ...

ZeroMemory(@AConfig, SizeOf(TMalwareConfig));
AConfig.Magic := MAGIC_NUMBER;
AConfig.Host := '127.0.0.1';
AConfig.Port := 443;
AConfig.FileNameURI := 'download/malware.exe';
AConfig.RetryIfFail := True;

After preparing the structure, we use the Windows API WriteFile to write the entire structure's content as raw bytes to the file’s EOF:

var ABytesWritten : DWORD;

// ...

if not WriteFile(
hFile, // Stub file handle
AConfig, // Structure
SizeOf(TMalwareConfig), // Structure Size
ABytesWritten, // __out__
nil // Ignored
) then
Exit();

Once the malware configuration data has been written to the stub file, it should be positioned at the end, as expected. You can verify with your favorite hex editor:

The presence of padding, typically a series of NULL bytes, within the file is perfectly normal and expected, especially when dealing with structures that include string, more generally, any type of arrays. Unlike data formats such as JSON, XML, where only the used space is accounted for, structures in a binary format allocate space for the entire array, regardless of whether it is fully used. This approach ensures that the structure can be read back accurately without errors, as the predefined space allocation allows for consistent data structure layouts.

It’s worth noting that there are techniques to minimise this unnecessary space in structures, optimising the storage however, these methods are beyond the scope of this article.

Reading Data

To read back the data that was previously appended to our stub file, the process is straightforward and mirrors the writing process in simplicity but involves a few key differences. Here is the step-by-step recipe for reading back the data:

  • Open the stub file (current process file name)
  • Position the file cursor to the end minus the size of our structure.
  • Read the structure content from cursor.
  • Display structure information.

In contrast to the builder process, which opens an external stub file, reading the data involves the stub accessing its own EOF data. To achieve this, the GetModuleFileNameA or GetModuleFileNameWAPI can be used with its parameter set to NULL to retrieve the file path of the current application (i.e., the stub itself):

SetLength(ACurrentFile, (MAX_PATH * 2));

ARequiredLen := GetModuleFileNameW(0, PWideChar(ACurrentFile), Length(ACurrentFile));

SetLength(ACurrentFile, ARequiredLen);

Using the GetModuleFileName API effectively involves a few steps: allocating enough space for our string, calling the API and adjusting string length.

There are other techniques to retrieve the module name, such as the one used by Embarcadero in its IDE, which involves SetString for their GetModuleName wrapper.

Having obtained the path to the current stub file, the next step is to open it in read-only mode using CreateFileW. It's crucial to set the file sharing parameter to FILE_SHARE_READ to prevent other processes from interfering with the file content. Although it might seem unnecessary in this context, since the file being read is already loaded into memory and thus cannot be modified, adopting this practice still is important.

var hFile : Thandle;

// ...

// TODO

hFile := CreateFileW(
PWideChar(ACurrentFile), // Our stub file ( current process image path )
GENERIC_READ, // We want to read data ( Read Only )
FILE_SHARE_READ, // Prevent other process to modify file content
nil, // Ignored
OPEN_EXISTING, // File must exists
FILE_ATTRIBUTE_NORMAL, // Attribute set by default to normal
0 // Ignored
);
if hFile = INVALID_HANDLE_VALUE then
Exit();

Now that we have opened the stub file for reading, the next step is to position the file cursor at the location where our EOF structure begins. This process differs from when we append data to the EOF during writing. For reading, we need to account for the size of our EOF structure and subtract that size from the end of the file to locate the start of our data structure:

if SetFilePointer(
hFile, // Our current stub file handle
-SizeOf(TMalwareConfig), // Decrease cursor by the size of the structure
// relative to the end (FILE_END)
nil, // Ignored
FILE_END // Cursor is placed at the end of the file
) = INVALID_SET_FILE_POINTER then
Exit()

Having positioned the file cursor at the correct location just before the EOF structure, we’re now set to read the content of our structure. This final step involves using the ReadFile Windows API, a crucial function for reading data from a file.

An optional, yet highly recommended step, is to verify the presence of the structure. This can be achieved by comparing the magic property of the structure with a hardcoded value, as discussed earlier. This ensures that the structure we are about to read is indeed the expected configuration data, adding a layer of validation to the process.

Although beyond the scope of this beginner-friendly article, it’s worth mentioning that the integrity of the structure could be further verified using additional mechanisms. We could include checksums to confirm the data has not been tampered with.

if not ReadFile(
hFile, // Our current stub file handle
AConfig, // Our EOF structure
SizeOf(TMalwareConfig), // Structure size
ABytesRead, // Bytes read during operation (not mandatory)
nil // Ignored
) then
Exit();

// Check if EOF structure exists and is valid
if AConfig.Magic <> MAGIC_NUMBER then
Exit();

// Display structure content
WriteLn(Format('-> Host: %s', [AConfig.Host]));
WriteLn(Format('-> Port: %d', [AConfig.Port]));
WriteLn(Format('-> File Name URI: %s', [AConfig.FileNameURI]));
WriteLn(Format('-> Retry If Fails: %s', [BoolToStr(AConfig.RetryIfFail)]));

That’s it! You should now have a clearer understanding of how EOF works and why it’s such a game-changing feature for malware developers. EOF is probably the easiest method to store changing data in an already compiled Windows application. However, the downside is that it is very easy to detect.

How to Detect Overlay Data

PE Studio Detecting Overlay Data (EOF)

Indeed, detecting EOF data in Windows applications is surprisingly straightforward, primarily due to the structured nature of the PE Header, which outlines the entire file structure. Security tools, such as the acclaimed PE Studio by Marc Ochsenmeier, automate the process of identifying what he call overlay data.

However, detection can also be achieved programmatically through PE Header parsing. The fundamental concept revolves around calculating the exact size of the EOF, which is a relatively simple task. The first step involves determining the total size of the target file, which can be accomplished using the GetFileSize API. The next piece of crucial information, derived from the PE Header, is the combined size of the PE Header (whole header / sub-headers) itself and the content size of its sections. By subtracting the expected size of the file, as described by the PE Header, from the actual size of the file, one can potentially uncover the presence of EOF data.

Long time ago, I created a small project in both Delphi and C++ to demonstrate how to detect, dump, and clear EOF data in Windows applications. While the project dates back some time, it serves as a practical example of implementing these concepts.

A Last Interesting Note

Before conclusion, it’s worth discussing how malware determines if it has already been installed on a system. This capability is crucial for avoiding redundant installations. The method employed by the malware depends on its sophistication and ingenuity, with multiple approaches possible.

A common strategy involves comparing the current malware process image path with the expected destination described in its configuration. For instance, if the malware finds itself running from %TEMP%\malware.exe(persistance destination describe by malware configuration) it can deduce that it has likely already been installed on the machine. This allows the malware to adapt its behaviour accordingly.

Another prevalent technique involves leaving specific artifacts on the system, such as files or registry keys. These artifacts act as a footprint or a marker indicating the malware’s presence. While effective, this method is more susceptible to detection by security software, as these artefacts can be scanned for and identified as malicious indicators.

Conclusion

This first article was rich in content, teaching the foundational concepts necessary to explore our first technique: EOF. Future iterations of this series will directly focus on specific storage and patching techniques, ensuring a more concise read. Designed with beginners in mind, this article aims to be accessible whether you’re just starting out or have more advanced knowledge. I sincerely hope you found this initial exploration informative and engaging. Your feedback is highly valued, so please feel free to share your thoughts and suggestions.

I’ve created a small open-source project that encapsulates all the code snippets from this article into a practical demonstration of both a builder and a reader. This project is available in the Unprotect Project’s GitHub repos. We’re planning to introduce a new category dedicated to Malware Config Techniques. This addition is in recognition of the significant role that malware config storage plays as a strategy for malware developers to evade detection or slow down the detection process.

https://github.com/Unprotect-Project/MalwareConfigs/

--

--