typetechniquecreated2026-08-26updated2026-08-26golangpecompilerevasion

truncated-go-pe64

A structural corruption pattern observed in Go-compiled PE64+ x64 binaries where the on-disk image contains only the DOS/NT headers and a partial .text section, while all remaining sections (.rdata, .data, .idata, .reloc, .symtab, .rsrc) are entirely absent. The PE header still claims the full SizeOfImage, and every truncated section has PointerToRawData and SizeOfRawData pointing past the end of the physical file.

Detection / Fingerprint

  1. File size vs. claimed image size: filesize is dramatically smaller than SizeOfImage (e.g., 30 KB vs. 2.4 MB, 262 KB vs. 2.4 MB, 276 KB vs. 2.1 MB).
  2. pefile parse errors: "SizeOfRawData is larger than file" and "AddressOfEntryPoint lies outside the file."
  3. Section table audit: Every section after .text has PointerToRawData > filesize. Even .text claims a SizeOfRawData extending past EOF.
  4. Go build ID survives: The .text stub at offset 0x600 contains the Go build ID: "..." string, confirming the fragment is legitimate Go runtime code.
  5. Entry point anomaly: The AddressOfEntryPoint lies inside the claimed .text RVA bounds but the binary cannot execute because .rdata (Go string tables, type descriptors, import data) is missing.
  6. Null PE timestamp: All observed siblings have TimeDateStamp = 0x0 (standard for Go -trimpath builds).
  7. Seven sections: All observed siblings have exactly 7 sections (Go standard layout: .text, .rdata, .data, .idata, .reloc, .symtab, .rsrc).

Implementation Patterns Observed

Sample Size Claimed Size Build ID (truncated) Status
138b53c5 30 KB 2.4 MB 8_SND_5Y3k0Aps... Source-side corruption confirmed
134a00b7 262 KB 2.4 MB lbdBnnyBahB8NB14... Second confirmed sibling
93857f30 276 KB 2.1 MB _oMqNP3wSJk2Uh2m... Third confirmed sibling (largest stub)

Reproduce on Your Own VMs

Not applicable — this is a corruption/integrity pattern, not a build technique. To detect it in your own triage pipeline:

import pefile
import sys

pe = pefile.PE(sys.argv[1])
with open(sys.argv[1], 'rb') as f:
    f.seek(0, 2)
    file_size = f.tell()

for section in pe.sections:
    ptr = section.PointerToRawData
    size = section.SizeOfRawData
    if ptr + size > file_size:
        print(f"TRUNCATED: {section.Name.decode().strip(chr(0))}")

Defensive Countermeasures

  • Triage gate: Reject or flag PE files where filesize < (SizeOfImage / 4) or where any section's raw data extent exceeds the file boundary.
  • Re-fetch validation: If a sample from a public repository (MalwareBazaar, VirusTotal) shows this pattern, re-fetch the hash. If the re-fetch returns the same truncated size, the corruption is upstream — do not waste sandbox time.
  • Entropy cross-check: The surviving .text stub will have entropy ~6.1–6.2; the "absent" sections will report entropy 0.0 if pefile is forced to read them (or will throw parse errors).

Related