Binary Exploitation · Learn

How image-based heap overflows work

An image is a file full of numbers that tell a program how to draw a picture. When the program that reads those numbers trusts them too much, a booby-trapped image can make it write data past the end of the memory it set aside, corrupting the program from the inside. Here is how that happens and how to stop it.

Binary Exploitation · LearnApplication Penetration Testing Download PDF
TL;DR

An image-based heap overflow is a memory-safety bug where a program that decodes an image writes past the end of a heap buffer it allocated for that image. It happens because the decoder sizes the buffer from values it reads out of the file (width, height, chunk lengths), and an attacker sets those values so the buffer comes out too small while the copy that fills it uses the real, larger amount of data. The out-of-bounds write corrupts nearby heap memory, and with careful heap layout it can be turned into remote code execution, often with no action beyond viewing or receiving the image.

By Pranav Khune, Lead Pentester, SecureLayer7Updated

What an image-based heap overflow is

The heap is the pool of memory a program requests at runtime for data whose size is not known ahead of time, such as the pixels of an image. The program asks the allocator for a block of a certain size, gets back a pointer, and is trusted to stay inside that block.

An image decoder is the code that turns a compressed image file (PNG, JPEG, WebP, GIF) back into raw pixels. To do that it reads a header describing the image, allocates a heap buffer to hold the result, then copies the decoded data in. A heap overflow happens when that copy writes more bytes than the buffer can hold, spilling attacker-influenced data into whatever memory sits next to it.

How the size calculation goes wrong

The bug almost always lives in the arithmetic that sizes the buffer. A decoder typically computes the buffer size from fields in the file, for example width * height * bytes_per_pixel, or the length of a compressed data chunk.

An attacker controls those fields, so they can pick values that make the multiplication wrap around. On a 32-bit size, width * height * 4 with large enough width and height exceeds the maximum integer and wraps to a small number. The decoder allocates that small buffer, then the decode loop writes the full, real number of pixels far past the end. The same happens when a length field lies about how big a chunk is, or when a bounds check compares against the wrong variable.

From a stray write to code execution

An out-of-bounds write on its own corrupts memory, which usually just crashes the program. Turning it into control is about what sits next to the overflowed buffer.

Attackers use heap grooming: they make the program allocate and free objects in a pattern that places a useful target right after the buffer they will overflow. Good targets are values the program will later trust and act on, such as a function pointer, a C++ vtable pointer, a length field, or the allocator’s own metadata. Overwriting one of those redirects execution to code or data the attacker controls. Because images are often decoded before anything is shown, this can run with no clicks.

Why image decoders are a favourite target

Images are decoded almost everywhere: browsers, messaging apps, email clients, servers that generate thumbnails, and platforms that re-encode uploads. Much of that decoding runs automatically, before a person interacts with the content, which is what makes these bugs zero-click.

The decoders are usually written in C or C++ for speed, and the file formats are complex, with many chunk types, colour models, and optional features. That combination, wide reach plus intricate parsing in a memory-unsafe language, is why heap overflows in image libraries keep surfacing. A 2023 flaw in the WebP decoding library, tracked as CVE-2023-4863, reached browsers and any application that bundled the same library, and was exploited in the wild.

How testers find them

In an assessment these are found by fuzzing the decoder: feeding it a large number of malformed and mutated image files and watching for crashes. Coverage-guided fuzzers reach deep code paths, and a memory sanitiser (ASan) turns a silent out-of-bounds write into an immediate, precise report of where it happened.

Manual review focuses on the size arithmetic and the copy loops: every place a length or dimension from the file feeds an allocation or a memcpy. Any multiplication or addition on attacker-controlled sizes that is not done with checked arithmetic is a candidate. Differential testing, decoding the same file with two implementations, surfaces parsers that disagree about how large the output should be.

How to defend against them

The durable fix is memory safety. Decoding untrusted images in a memory-safe language, or in a well-audited wrapper that bounds-checks every write, removes the class. Where the C or C++ decoder stays, use checked arithmetic for every size calculation so an overflow fails loudly instead of allocating a short buffer, and validate declared dimensions against sane limits before allocating.

Contain the blast radius by sandboxing the decoder in a low-privilege process, so a compromise of the parser does not become a compromise of the whole application. Keep image libraries patched, since one library flaw affects every application that bundles it. Platform mitigations (ASLR, a non-executable heap, and control-flow integrity) raise the cost of turning a write into execution, but they do not remove the bug, so treat them as depth, not a fix.

References

  1. [1]MITRE CWE-122: Heap-based Buffer Overflow(MITRE)
  2. [2]MITRE CWE-787: Out-of-bounds Write(MITRE)
  3. [3]MITRE CWE-190: Integer Overflow or Wraparound(MITRE)
  4. [4]NVD: CVE-2023-4863 (WebP heap buffer overflow)(NVD)
  5. [5]OWASP: Buffer Overflow(OWASP)
Related terms

Common questions

Image heap overflows, asked often

Scope an engagement

Test the software that parses your files, before an attacker does.

Our application and product penetration tests exercise the parsers and native components most tools skip, with fuzzing and manual review, and ship findings with reproducible proof and a fix your developers can implement.

See the methodology30-min scoping call, fixed-price proposal in 48 hours.