Introduction

The previous modules introduced RC4 and AES as methods of encrypting payload data. This module instead focuses on payload obfuscation, which transforms the payload without using conventional encryption.

A malware developer should have several tools available at their disposal to achieve the same task in order to stay unpredictable. Payload obfuscation can be seen as a different "tool" when compared to payload encryption, yet both are ultimately used for the same purpose.

After going through this module, one should be able to use advanced payload obfuscation techniques, some of which are being used in the wild, such as in Hive ransomware.

What is IPv4/IPv6Fuscation

IPv4/IPv6Fuscation is an obfuscation technique in which the shellcode's raw bytes are represented as either IPv4 or IPv6 address strings. The resulting array of address strings looks innocuous, as it resembles configuration data, log output, or a subnet allow-list, and does not carry the entropy or byte patterns that a static scanner would associate with shellcode.

To illustrate the encoding, consider the following bytes taken from the beginning of the x64 Msfvenom calc shellcode:

FC 48 83 E4 F0 E8 C0 00 00 00 41 51 41 50 52 51.

To support shellcode of any length, this module applies PKCS#7 padding before encoding and strips it after decoding. This removes the requirement that the shellcode size should be a multiple of the encoding block size (i.e., 4 for IPV4, and 16 for IPV6).

PKCS#7 Padding for Obfuscation

The PKCS#7 padding scheme was covered in detail in the previous Payload Encryption - AES Encryption module. The obfuscation implementation reuses the same idea, differing only in the block size used to compute how many bytes are appended:

Encoding Block Size Padding Byte Range
IPv4Fuscation 4 bytes 0x01 – 0x04
IPv6Fuscation 16 bytes 0x01 – 0x10
AES-256-CBC 16 bytes 0x01 – 0x10

The IPv4 version of PadBufferPkcs7 is shown below. The IPv6 version is a copy of the same routine with IPV4_BYTES_COUNT swapped for IPV6_HEX_BYTES_COUNT.

#define IPV4_BYTES_COUNT        	4// We need 4 bytes from the shellcode to form one IPv4 string
#define IPV6_HEX_BYTES_COUNT		16// We need 16 bytes from the shellcode to form one IPV6 string

static BOOL PadBufferPkcs7(IN PBYTE pInputBuffer, IN DWORD dwInputSize, OUT PBYTE* ppPaddedBuffer, OUT PDWORD pdwPaddedSize)
{
    PBYTE   pbPaddedBuffer      = NULL;
    DWORD   cbPaddedBuffer      = 0x00;
    DWORD   dwPadLen            = 0x00;
    BOOL    bResult             = FALSE;

    // Parameter check
    if (!pInputBuffer || !dwInputSize || !ppPaddedBuffer || !pdwPaddedSize)
        goto _END_OF_FUNC;

    // PKCS#7 padding: pad to the next multiple of 4. If already aligned, add a full block.
    dwPadLen        = IPV4_BYTES_COUNT - (dwInputSize % IPV4_BYTES_COUNT);
    cbPaddedBuffer  = dwInputSize + dwPadLen;

    // Allocating the padded buffer of size `cbPaddedBuffer`
    if (!(pbPaddedBuffer = (PBYTE)LocalAlloc(LPTR, cbPaddedBuffer)))
        goto _END_OF_FUNC;

    // Copy input into the buffer, then fill the trailing pad bytes with the pad length
    memcpy(pbPaddedBuffer, pInputBuffer, dwInputSize);
    memset(pbPaddedBuffer + dwInputSize, (BYTE)dwPadLen, dwPadLen);
    //     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
		//     calculate memory address next to last byte copied from original payload. Here starts padding zone!

    // Populate output parameters
    *ppPaddedBuffer = pbPaddedBuffer;
    *pdwPaddedSize  = cbPaddedBuffer;
    bResult         = TRUE;

_END_OF_FUNC:
    if (pbPaddedBuffer && !bResult)
        LocalFree(pbPaddedBuffer);
    return bResult;
}

The current PadBufferPkcs7 implementation differs from the version used in the Payload Encryption - AES Encryption module in two ways. The padded buffer is allocated with LocalAlloc instead of HeapAlloc, so it must be released with LocalFree. LocalAlloc is another WinAPI memory-allocation routine that allocates a block from the process heap. In this example, the LPTR flag requests a fixed, zero-initialized buffer.

The padding calculation also uses IPV4_BYTES_COUNT, which is set to 4, rather than the 16-byte AES block size. This is because each IPv4 string represents exactly four bytes of the original buffer. The IPv6 version follows the same logic but uses IPV6_HEX_BYTES_COUNT, which is set to 16.

The PKCS#7 behavior itself remains unchanged. If the input size is already a multiple of the required block size, a complete padding block is still appended. For IPv4Fuscation, this means adding four bytes of 0x04, while IPv6Fuscation adds sixteen bytes of 0x10.

IPv4Fuscation Implementation