Skip to content
chrncl
Writing07 / 09
Static Analysis-about 8 min read

The Entropy of a Packed Binary

Entropy comes up constantly in malware triage and packer detection, and most explanations stop at 'high entropy means compressed or encrypted' without saying why.

Entropy comes up constantly in malware triage and packer detection, and most explanations of it stop at 'high entropy means compressed or encrypted' without saying why, which makes it feel like a magic number instead of something you can reason about. It's not magic. It's a direct measurement of how predictable a sequence of bytes is, and once that clicks, you stop treating it as a black-box score and start using it the way it's meant to be used, as one signal among several, not a verdict.

What the number actually measures

Shannon entropy for a block of bytes is H = -sum( p(x) * log2(p(x)) ), summed over every possible byte value x from 0 to 255, where p(x) is how often that byte value shows up in the block. For a block where every byte value from 0 to 255 appears with equal frequency, maximally unpredictable, entropy hits its ceiling of 8.0. For a block that's all zeroes, or all one repeated byte, entropy is 0.0: completely predictable, no information content at all in the information-theory sense.

Normal executable code sits somewhere in the middle, typically in the 5.5 to 6.5 range. x86/x64 instructions aren't random, certain opcodes, certain register encodings, certain byte patterns show up constantly (0x48 prefixes everywhere in x64 code, for instance), so the byte distribution is skewed, not uniform. Compressed or encrypted data, by design, looks like uniform noise: a good compressor removes redundancy specifically so there's nothing left to compress further, and a good cipher's whole job is making ciphertext indistinguishable from random bytes. Both push entropy up toward 8.0.

Why this correlates with packing

A packer takes a compiled binary, compresses or encrypts the original code and data, and wraps it in a small stub that decompresses or decrypts it back into memory at runtime before jumping to the real entry point. The packed sections, the ones holding the compressed payload, read high entropy because compressed data reads high entropy, full stop. The stub itself, being normal unpacked code, usually reads in the normal 5.5 to 6.5 range. So a classic packer signature on an entropy heatmap is: small normal-entropy stub section, followed by one dominant section sitting up near 7.8 to 8.0 that's way bigger than you'd expect a stub to need.

Computing this per-block rather than for the whole file at once is what makes it useful. Whole-file entropy on a partially packed binary just gives you a blurry average that doesn't tell you where the suspicious region actually is. Block-level, I use 256-byte windows, gives you a profile across the file, and the profile shape is the actual signal. A flat line around 6.0 the whole way through is a normal binary. A flat line around 6.0 that suddenly jumps to 7.9 and stays there for 80% of the file is a packed one, and now you know exactly which section to look at first.

Where it lies to you

The failure mode that trips people up the first time they rely on entropy alone: legitimate high-entropy content that has nothing to do with packing. Any already-compressed resource, a PNG icon, a ZIP archive bundled as a resource, an embedded JPEG, reads just as high-entropy as packed code, because compression is compression regardless of intent. A binary with a big .rsrc section full of application icons and a bundled update archive will show an entropy spike in that section that has nothing to do with the binary being malicious or protected. If you only looked at the number, you'd flag a perfectly normal installer.

The fix isn't a smarter entropy formula, the math is already correct, it's just answering a narrower question than 'is this malicious.' The fix is never trusting entropy in isolation. Cross-reference the high-entropy region against what section it's in (.rsrc sections are expected to sometimes run hot; .text sections running hot are much more interesting), against the section's declared characteristics (is it marked executable? that's a stronger signal combined with high entropy), and against import table sparseness (a binary that imports almost nothing is more likely to be unpacking its real imports at runtime).

Entropy is a genuinely useful first-pass filter, on a folder of a thousand samples, sorting by max-block-entropy gets you a triage order that's meaningfully better than file size or timestamp. It's just not a verdict. It's one weighted vote in a decision that should never rest on a single signal, which is true of basically every heuristic in static analysis and is the thing that separates a tool that's actually useful from one that just produces confident-looking false positives.