Skip to content

Detecting Packed Executables: UPX, Entropy and Other Signs

Static signs that a Windows executable is packed or protected — section names, entropy, W+X sections, entry point, imports, overlay — and their pitfalls.

Published on Updated on 4 min read

TL;DR. A packer compresses or encrypts the real program and adds a small stub that restores it in memory. Statically, you see the stub: few imports, odd section names, a big high-entropy blob, sometimes a writable-and-executable section and an entry point outside .text. No single sign is conclusive; together they are reliable. The fix is to unpack — with the packer's own tool when it exists, or dynamically in a sandbox — and analyse again.

The signs, one by one

Section names. Packers usually name their sections: UPX uses UPX0 (empty on disk, large in memory) and UPX1 (the compressed data plus the stub); MPRESS uses .MPRESS1/.MPRESS2; VMProtect .vmp0/.vmp1; Themida .themida. Names are trivial to change, so they are hints — but real-world samples rarely bother.

Markers. UPX writes the magic string UPX! and a version string into the headers. That is what upx -d relies on to decompress, and it is also why tampering with it breaks upx -d (UPX project).

Entropy. Shannon entropy, in bits per byte, measures how random the bytes look. Compiled x86/x64 code typically falls around 5–6.8; compressed or encrypted data is close to 8. The PE Parser flags sections at or above 7.2 (ignoring .rsrc, where images and archives are normal).

Permissions. A stub must write the unpacked code somewhere and then run it. A section marked both writable and executable is rare in compiler output and common in packers.

Entry point. Compilers put the entry point in the code section. A stub often lives in its own section, frequently the last one.

Imports. A packed program imports what the stub needs — typically LoadLibraryA, GetProcAddress, VirtualProtect, VirtualAlloc, ExitProcess — and resolves the rest at run time. Fewer than ten imports including GetProcAddress is a strong signal. It also makes the imphash useless for clustering: all files packed by the same tool share it.

Raw vs virtual size. A section with 0 bytes on disk but a large virtual size (UPX0) is space the stub fills at run time.

Overlay. Some packers, installers and droppers append their payload after the last section instead of inside it.

The sample, read statically

The synthetic m64.exe in the PE Parser's sample shows most signs at once, harmlessly (its only code is a ret): a section named UPX1 filled with random bytes (entropy 7.96) and marked RWX, a TLS callback, an overlay of 4 KB of noise, and imports that match known behaviour groups. The tool reports "UPX" from the section name only — there is no UPX! marker — and says what evidence each detection rests on.

False positives to expect

  • Installers (NSIS, Inno Setup, self-extracting archives) carry large compressed overlays or resources.
  • Protected commercial software and games use VMProtect, Themida or Denuvo legitimately.
  • Go and Rust binaries are large and statically linked; they are not packed but have unusual section layouts.
  • .NET assemblies import only mscoree.dll!_CorExeMain; the real code is IL in the metadata.
  • Signed files: a valid signature from a known publisher lowers the priority, but signed malware exists.

What to do next

  1. UPX: drop the file in the PE Parser, which unpacks it statically — also when the UPX! header was zeroed or the sections renamed to break upx -d — and rebuilds the original imports, so the imphash matches the original build. For other packers with an unpacker, run it on a copy and analyse the result.
  2. Otherwise unpack dynamically in an isolated sandbox and dump the process memory.
  3. Record the packed sample's hashes too: other victims will have the packed form.

Detection in the PE Parser is a small set of documented heuristics written for the tool — section names, markers, runtime strings, the Rich header — not a signature database like Detect It Easy's. Treat it as a pointer, and read the PE format guide for where each sign lives in the file.

FAQ

How can I tell if an EXE is packed without running it?

Look for high-entropy executable sections, sections that are both writable and executable, an entry point outside .text or in the last section, very few imports with LoadLibrary and GetProcAddress, packer section names such as UPX0 and UPX1, and packer markers such as the UPX! string.

What entropy means a file is packed?

Entropy close to 8 bits per byte means compressed or encrypted data. Compiled code usually sits between about 5 and 6.8; values above about 7.2 in an executable section are a strong hint of packing, but images, archives and certificates are high-entropy too.

Related articles

Step-by-step static triage of Windows executables with the free PE Parser: load files or a ZIP, read indicators, compare builds and export results.
Find and copy suspicious EXE, DLL and SYS files from a Windows host or image without running them: PowerShell, Velociraptor, disk images and pitfalls.
Where a Windows executable stores its times, which ones can be trusted, how reproducible builds and timestomping show up, and how to use them.