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
- File size vs. claimed image size:
filesizeis dramatically smaller thanSizeOfImage(e.g., 30 KB vs. 2.4 MB, 262 KB vs. 2.4 MB, 276 KB vs. 2.1 MB). - pefile parse errors:
"SizeOfRawData is larger than file"and"AddressOfEntryPoint lies outside the file." - Section table audit: Every section after
.texthasPointerToRawData > filesize. Even.textclaims aSizeOfRawDataextending past EOF. - Go build ID survives: The
.textstub at offset0x600contains theGo build ID: "..."string, confirming the fragment is legitimate Go runtime code. - Entry point anomaly: The
AddressOfEntryPointlies inside the claimed.textRVA bounds but the binary cannot execute because.rdata(Go string tables, type descriptors, import data) is missing. - Null PE timestamp: All observed siblings have
TimeDateStamp = 0x0(standard for Go-trimpathbuilds). - 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
.textstub 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
- unattributed — entity page holding all confirmed truncated siblings
- unclassified-go-pe64 — functional Go PE64+ cluster with similar build artefacts but intact binaries
- golang-stealer-build-pattern — shared Go build artefacts across functional families