7 minute read

Hello, cybersecurity enthusiasts and white hackers!

malware

In this post I want to look at another simple PE trick: file bloating, also known as binary padding. The idea is to make an executable much larger without changing its main behavior. This technique is mapped to MITRE ATT&CK T1027.001 - Binary Padding.

Historically, attackers used oversized files to waste analyst time or to exceed file-size limits in scanners, sandboxes, mail gateways, and upload services. Modern security products should not trust size as a safety signal, and many of them can inspect large objects or apply partial scanning. Therefore, bloating is not a reliable bypass by itself.

For this laboratory experiment our program is harmless as usual: it only displays a Meow-meow! message box. We will compare two ways to enlarge it:

  1. compile a large byte array into a real PE section;
  2. append bytes after the last section as an overlay.

Both files still run, but their layouts are different and defenders can detect the difference.

PE sections and overlay

A normal PE file contains headers followed by the raw data of its sections. Each section has a PointerToRawData and SizeOfRawData value in the section table. The Windows loader maps the required sections into memory according to the PE headers.

Data placed after the end of the last section is commonly called an overlay:

malware

An overlay is not automatically malicious. Installers, self-extracting archives, packers, and signed files can legitimately contain data outside ordinary sections. Context matters.

practical example

First, create the baseline program (hack.c):

/*
 * hack.c
 * harmless PE file-bloating experiment
 * author @cocomelonc
 * https://cocomelonc.github.io/malware/2026/08/25/malware-tricks-64.html
 */
#include <windows.h>

int WINAPI WinMain(
  HINSTANCE instance,
  HINSTANCE previousInstance,
  LPSTR commandLine,
  int showCommand
) {
  (void)instance;
  (void)previousInstance;
  (void)commandLine;
  (void)showCommand;

  MessageBoxA(NULL, "Meow-meow!", "=^..^=", MB_OK);
  return 0;
}

Compile it with MinGW-w64:

x86_64-w64-mingw32-gcc hack.c -o hack.exe -mwindows -Wall -Wextra -s

malware

Check the initial size and hash:

ls -lh hack.exe
sha256sum hack.exe

malware

practical example - method 1: a large PE section

The first approach is to define an initialized array and explicitly place it in a new section named .bloat. At least one non-zero initializer is used so the compiler has to store the array in the executable instead of treating it as zero-initialized storage.

Create hack-bloat.c:

/*
 * hack-bloat.c
 * initialized padding stored in a PE section
 * author @cocomelonc
 */
#include <windows.h>

#define BLOAT_SIZE (16U * 1024U * 1024U)

__attribute__((used, section(".bloat")))
const unsigned char bloat[BLOAT_SIZE] = { 0x4d };

int WINAPI WinMain(
  HINSTANCE instance,
  HINSTANCE previousInstance,
  LPSTR commandLine,
  int showCommand
) {
  (void)instance;
  (void)previousInstance;
  (void)commandLine;
  (void)showCommand;

  MessageBoxA(NULL, "Meow-meow!", "=^..^=", MB_OK);
  return 0;
}

Compile the bloated version:

x86_64-w64-mingw32-gcc hack-bloat.c -o hack-bloat.exe -mwindows -Wall -Wextra -s

malware

Then compare both artifacts:

ls -lh hack.exe hack-bloat.exe
sha256sum hack.exe hack-bloat.exe

malware

and:

x86_64-w64-mingw32-objdump -h hack-bloat.exe

malware

The section table should now contain .bloat with approximately 16 MiB of raw data. Because this is a real section, the padding participates in the PE layout. Depending on the section characteristics generated by the toolchain, it can also affect the virtual image size.

malware

Run it on the Windows test VM:

.\hack-bloat.exe

malware

The visible behavior is the same as the baseline “malware” program

practical example - method 2: append an overlay

The second method leaves the original section table unchanged and appends padding to the end of a copy of the executable. The following small utility copies an input file and writes a selected number of mebibytes after it (pad.c):

/*
 * pad.c
 * append a fixed byte pattern to a file
 * author @cocomelonc
 */
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define CHUNK_SIZE (64U * 1024U)

static int copy_file(FILE *source, FILE *destination) {
  unsigned char buffer[CHUNK_SIZE];
  size_t count;

  while ((count = fread(buffer, 1, sizeof(buffer), source)) != 0) {
    if (fwrite(buffer, 1, count, destination) != count)
      return 0;
  }

  return !ferror(source);
}

int main(int argc, char *argv[]) {
  FILE *source = NULL;
  FILE *destination = NULL;
  unsigned char padding[CHUNK_SIZE];
  unsigned long megabytes;
  unsigned long chunks;
  char *end = NULL;
  int result = EXIT_FAILURE;

  if (argc != 4) {
    fprintf(stderr, "usage: %s <input> <output> <MiB>\n", argv[0]);
    return EXIT_FAILURE;
  }

  errno = 0;
  megabytes = strtoul(argv[3], &end, 10);
  if (errno != 0 || end == argv[3] || *end != '\0' || megabytes == 0) {
    fprintf(stderr, "invalid padding size: %s\n", argv[3]);
    return EXIT_FAILURE;
  }

  source = fopen(argv[1], "rb");
  if (source == NULL) {
    perror("fopen input");
    goto cleanup;
  }

  destination = fopen(argv[2], "wb");
  if (destination == NULL) {
    perror("fopen output");
    goto cleanup;
  }

  if (!copy_file(source, destination)) {
    fprintf(stderr, "failed to copy input file\n");
    goto cleanup;
  }

  memset(padding, 0x41, sizeof(padding));
  chunks = megabytes * (1024U * 1024U / CHUNK_SIZE);

  for (unsigned long i = 0; i < chunks; ++i) {
    if (fwrite(padding, 1, sizeof(padding), destination) != sizeof(padding)) {
      fprintf(stderr, "failed to append padding\n");
      goto cleanup;
    }
  }

  printf("appended %lu MiB to %s\n", megabytes, argv[2]);
  result = EXIT_SUCCESS;

cleanup:
  if (destination != NULL && fclose(destination) != 0)
    result = EXIT_FAILURE;
  if (source != NULL)
    fclose(source);
  return result;
}

Build the utility on Linux and append a 16 MiB overlay:

gcc pad.c -o pad -Wall -Wextra -O2
./pad hack.exe hack-overlay.exe 16
ls -lh hack.exe hack-overlay.exe
sha256sum hack.exe hack-overlay.exe

malware

malware

Copy hack-overlay.exe to the Windows VM and run it:

.\hack-overlay.exe

malware

Windows should still execute the original program because its headers and section contents have not changed. The appended 0x41 bytes are outside the final section and are not part of the program logic.

if we use this technique on a signed production binary any post-build modification must be assumed to invalidate its Authenticode signature; verify the result explicitly with signtool verify /pa or Sysinternals sigcheck.

finding the overlay

The rough start of an overlay can be calculated from the greatest end offset of all raw PE sections:

\[\text{raw\_end} = \max_i(\text{PointerToRawData}_i + \text{SizeOfRawData}_i)\]

If the physical file size is greater than raw_end, the remaining bytes are candidates for overlay data. With the Python pefile module:

import os
import sys
import pefile

path = sys.argv[1]
pe = pefile.PE(path, fast_load=True)

raw_end = max(
    section.PointerToRawData + section.SizeOfRawData
    for section in pe.sections
)
file_size = os.path.getsize(path)
overlay_size = max(0, file_size - raw_end)

print(f"file size:    {file_size} bytes")
print(f"section end:  {raw_end} bytes")
print(f"overlay size: {overlay_size} bytes")

Run it against all three samples:

python3 -m pip install pefile
python3 overlay-size.py hack.exe
python3 overlay-size.py hack-bloat.exe
python3 overlay-size.py hack-overlay.exe

malware

The .bloat array should be accounted for by a section, whereas the appended padding should be reported as overlay. A production parser must also understand the PE security directory and common installer formats; simply labeling every overlay as malicious creates false positives.

defensive notes

Useful indicators for this technique include:

  • a file size that is unusual for the claimed application;
  • an extremely large section with low entropy or a repeated byte pattern;
  • a large overlay that the program never reads;
  • a major difference between the on-disk file size and the meaningful PE content;
  • inconsistent metadata, signature state, and expected software distribution path.

A basic hunting rule can flag unusually large unsigned PE files, but it should remain a low-confidence signal:

import "pe"

rule Suspicious_Oversized_Unsigned_PE_Draft
{
  meta:
    description = "Large unsigned PE file; triage signal only"
    author = "cocomelonc"

  condition:
    uint16(0) == 0x5a4d and
    filesize > 15MB and
    pe.number_of_signatures == 0
}

run:

yara detect.yar ./

malware

Large legitimate applications are common, so size alone is not enough for a verdict. A better workflow combines PE structure, entropy, signature verification, reputation, execution telemetry, and knowledge of the expected publisher.

For example, Shannon entropy value for our hack-bloat.exe:

python3 entropy.py -f hack-bloat.exe

malware

conclusion

File bloating changes the artifact, not its core behavior. An initialized array becomes part of a PE section, while appended bytes form an overlay outside the ordinary section layout. Both approaches increase the hash and file size, but they leave different forensic traces.

This simple experiment is useful for understanding why scanners must parse file formats instead of relying on superficial properties. It also shows why defenders should treat oversized sections and overlays as investigation clues rather than automatic proof of malware.

The sample code is available for educational purposes only. Test it in an isolated lab environment.

I hope this post is useful for malware researchers, C/C++ programmers, and blue teamers studying PE structure and masquerading indicators, and adds a weapon to the red teamers arsenal.

MITRE ATT&CK: T1027.001 Binary Padding
Microsoft PE format
pefile documentation
source code in github

This is a practical case for educational purposes only.

Thanks for your time happy hacking and good bye!
PS. All drawings and screenshots are mine