Payload Placement

Payloads can be stored in one of the following PE sections:

- .data Section

The .data section of a PE file is a section of a program's executable file that contains initialized global and static variables. This section is readable and writable ( the memory protection of the region is specified as RW which indicates it is a read-write region), making it suitable for an encrypted payload that requires decryption during runtime.

Example:

#include <Windows.h>
#include <stdio.h>

// msfvenom calc shellcode
// msfvenom -p windows/x64/exec CMD=calc.exe -f c
// .data saved payload
unsigned char Data_RawData[] = {
    0xFC, 0x48, 0x83, 0xE4, 0xF0, 0xE8, 0xC0, 0x00, 0x00, 0x00, 0x41, 0x51,
    // …….. shellcode continuation
};

int main() {
    printf("[i] Data_RawData var : 0x%p \n", Data_RawData);
    printf("[#] Press <Enter> To Quit ...");
    getchar();
    return 0;
}

- .rdata Section

Variables that are specified using the const qualifier are written as constants. These types of variables are considered "read-only" data. The letter "r" in .rdata indicates this, and any attempt to change these variables will cause access violations.

Example (same code but the variable is now preceded by the const qualifier):

#include <Windows.h>
#include <stdio.h>

// msfvenom calc shellcode
// msfvenom -p windows/x64/exec CMD=calc.exe -f c
// .data saved payload
const unsigned char Data_RawData[] = {
    0xFC, 0x48, 0x83, 0xE4, 0xF0, 0xE8, 0xC0, 0x00, 0x00, 0x00, 0x41, 0x51,
    // …….. shellcode continuation
};

int main() {
    printf("[i] Data_RawData var : 0x%p \n", Data_RawData);
    printf("[#] Press <Enter> To Quit ...");
    getchar();
    return 0;
}

*Depending on the compiler and its settings, the .data and .rdata sections may be merged, or even merged into the .text section.

- .text Section

One must instruct the compiler to save it in the .text section, now is not just a matter of declaring a random variable.

The compiler must be told to place the Text_rawData variable in the .text section instead of the .rdata section.

The .text section is special in that it stores variables with executable memory permissions, allowing them to be executed directly without the need for editing the memory region permissions.

Useful for small payloads (less than 10 bytes)

Example:

#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,
    // …….. shellcode continuation
};

int main() {
    printf("[i] Text_RawData var : 0x%p \n", Text_RawData);
    printf("[#] Press <Enter> To Quit ...");
    getchar();
    return 0;
}

- .rsrc Section

Saving the payload in the .rsrc section is one of the best options as this is where most real-world binaries save their data.

It is also a cleaner method for malware authors, since larger payloads cannot be stored in the .data or .rdata sections due to size limits.

To store a payload in the .rsrc section:

  1. Inside Visual Studio, right-click on 'Resource files' then click Add > New Item.

  2. Click on 'Resource File'.

  3. This will generate a new sidebar, the Resource View. Right-click on the .rc file (Resource.rc is the default name), and select the 'Add Resource' option.

  4. Click 'Import'.

  5. Select the calc.ico file, which is the raw payload renamed to have the .ico extension.

  6. A prompt will appear requesting the resource type. Enter "RCDATA" without the quotes.

  7. After clicking OK, the payload should be displayed in raw binary format within the Visual Studio project

  8. When exiting the Resource View, the "resource.h" header file should be visible and named according to the .rc file from Step 2. This file contains a define statement that refers to the payload's ID in the resource section (IDR_RCDATA1). This is important in order to be able to retrieve the payload from the resource section later.

Once compiled, the payload will now be stored in the .rsrc section, but it cannot be accessed directly. Instead, several WinAPIs must be used to access it.

Example of how to access it with explanation of the used APIs:

#include <Windows.h>
#include <stdio.h>
#include "resource.h"

int main() {
    HRSRC hRsrc = NULL;
    HGLOBAL hGlobal = NULL;
    PVOID pPayloadAddress = NULL;
    SIZE_T sPayloadSize = NULL;

    // FindResourceW - Get the location to the data stored in the resource (.rsrc) by its id *IDR_RCDATA1* (this is defined in the header file)
    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;
    }

    // LoadResource - Get HGLOBAL, or the handle of the specified resource data since it's 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;
    }

    // LockResource - Get the address of our payload in .rsrc section
    pPayloadAddress = LockResource(hGlobal);
    if (pPayloadAddress is null) {
        // in case of function failure
        printf("[!] LockResource Failed With Error : %d \n", GetLastError());
        return -1;
    }

    // SizeofResource - 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;
}

However, since the payload can't be edited directly from within the resource section, it must be moved to a temporary buffer.

To do so, memory is allocated the size of the payload using HeapAlloc and then the payload is moved from the resource section to the temporary buffer using memcpy, adding the following to the previous example:

// Allocating memory using a HeapAlloc call
PVOID pTmpBuffer = HeapAlloc(GetProcessHeap(), 0, sPayloadSize);

if (pTmpBuffer != NULL) {
    // copying the payload from the 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);

Since pTmpBuffer now points to a writable memory region that is holding the payload, it's possible to decrypt the payload or perform any updates to it.

Last updated