4-Byte Heap Overflow To RCE In Minecraft

Hrvoje Misetic (Security Researcher · Independent)

OffensiveCon 2026 · Day 1 · Main Stage

Overview

This talk, presented by Hrvoje Misetic at OffensiveCon, delves into the intricate process of achieving remote code execution (RCE) in Minecraft's Bedrock Edition through a 4-byte heap overflow vulnerability. The research meticulously details the discovery of a critical flaw in a widely used image parsing library, stbimage, and outlines a sophisticated exploitation chain that navigates modern memory mitigations like Segment Heap and Control Flow Guard (CFG). Given Minecraft's immense popularity, boasting 85 million active players, a server-to-client RCE vulnerability represents a significant security risk, allowing malicious servers to compromise connecting players' machines.

Watch on YouTube

Visual summary for 4-Byte Heap Overflow To RCE In Minecraft by Hrvoje Misetic
Visual summary for 4-Byte Heap Overflow To RCE In Minecraft by Hrvoje Misetic

Key moments

  1. 0:00 Introduction and talk agenda overview
  2. 1:17 Why focus on Minecraft Bedrock C++ edition
  3. 2:27 Identifying the vulnerable stbimage image parsing library
  4. 4:26 Detailed explanation of the 4-byte heap overflow
  5. 6:53 Segment heap (LFH) and heap shaping challenges
  6. 7:30 Variable size heap and encoded chunk headers

4-Byte Heap Overflow To RCE In Minecraft

Speakers: Hrvoje Misetic, Security Researcher, Independent (formerly Web3 Auditor and Security Researcher at Adacta)

Conference: OffensiveCon

YouTube: https://www.youtube.com/watch?v=QgybUtLXS9U

Overview

This talk, presented by Hrvoje Misetic at OffensiveCon, delves into the intricate process of achieving remote code execution (RCE) in Minecraft's Bedrock Edition through a 4-byte heap overflow vulnerability. The research meticulously details the discovery of a critical flaw in a widely used image parsing library, stb_image, and outlines a sophisticated exploitation chain that navigates modern memory mitigations like Segment Heap and Control Flow Guard (CFG). Given Minecraft's immense popularity, boasting 85 million active players, a server-to-client RCE vulnerability represents a significant security risk, allowing malicious servers to compromise connecting players' machines.

Misetic's presentation is particularly notable for its deep dive into Minecraft's internal mechanisms, including its custom scripting language, Molang, and its unique heap management challenges on the Universal Windows Platform (UWP). The talk highlights the complexities of exploiting memory corruption bugs in real-world, closed-source applications, demonstrating ingenious techniques for heap shaping, ASLR bypass, arbitrary read/write, and CFG circumvention. The research serves as a compelling case study for advanced memory exploitation, offering valuable insights for both offensive and defensive security practitioners.

Background

▶ Watch: Introduction and talk agenda overview (0:00)

Minecraft, one of the most popular games globally, exists in two primary editions: Java and Bedrock. The Java Edition is desktop-only (Windows, Linux, macOS) and, as its name suggests, is written in Java. In contrast, the Bedrock Edition is a C++ implementation available across a much broader range of platforms, including consoles, phones (Android/iOS), and Windows. Given the objective of finding and exploiting a memory corruption bug, the research focused exclusively on the C++-based Bedrock Edition, specifically the Windows version, due to the team's familiarity with its debugging environment.

The chosen attack model was server-to-client. This model is particularly attractive in Minecraft's multiplayer ecosystem, where community servers are immensely popular, with top Bedrock servers hosting over 50,000 concurrent players. A malicious server, by controlling a large state and interacting with connecting clients, could potentially trigger vulnerabilities. The challenge lay in finding a client-side bug that could be reliably triggered by a server.

Initial reverse engineering of the closed-source Bedrock executable was avoided where possible. Instead, the focus shifted to resource packs, a client-side feature provided by servers to modify the game's appearance (textures, sounds, animations). These resource packs contain various image files (e.g., PNG, GIF). This immediately exposed the image parsing surface as a potential attack vector. By searching for common image format strings (like "GIF") within the executable, the researchers identified stb_image, an open-source single-file C/C++ image processing library, as the one used by Minecraft. This was later confirmed through debugging.

stb_image has a history of memory corruption vulnerabilities, with reports as recent as 2023. The first step was to identify the exact version of stb_image used by Minecraft. Through a manual commit search, the specific commit hash was identified, dating back to 2018—nearly seven years old at the time of the research. Initial attempts to leverage existing stb_image vulnerabilities proved unfruitful, as none were directly applicable or deemed easily exploitable in this specific, older version. This led to the decision to fuzz the library directly.

Key Findings

▶ Watch: Identifying the vulnerable stb_image image parsing library (2:27)

The core of this research revolves around the discovery and exploitation of a 4-byte heap overflow in the stb_image library, specifically within its GIF parsing functionality. This vulnerability, while previously reported in 2018 (and missed by the researchers initially), was present in the outdated stb_image version used by Minecraft. The overflow occurs in the out_gif_code function, where g_history is written to at index / 4, and a pointer P (calculated using index into g_out) is later written to with four bytes from the color table. The root cause was identified as g_current_index being used before a bounds check, which, with a specially crafted GIF file setting width to zero, caused current_index to start off-by-one, leading to an out-of-bounds write. This resulted in a controlled 4-byte out-of-bounds write, as the color_table is attacker-controlled. The vulnerability was reported to Mojang in July and patched approximately three months later by updating the stb_image library to its latest version.

To exploit this primitive, a reliable heap spraying technique was crucial. Minecraft's sign blocks, which allow players to display arbitrary text, proved to be an ideal candidate. The game uses std::string to store this text. Due to Small String Optimization (SSO), strings longer than 16 bytes trigger a separate heap allocation for their data, with a pointer stored within the std::string object itself. This mechanism allowed for allocating an arbitrary number of chunks, controlling their size and data, and selectively freeing them by manipulating sign text. A significant hurdle was a server-side limitation of 512 bytes for sign text. This was bypassed by patching the server executable: modifying a CMP RAX, 0x200 (comparing text length to 512 decimal) followed by a JBE (jump below or equal) instruction to an unconditional JMP. This allowed the server to send, and the client to allocate, larger buffers (e.g., 1024 bytes), achieving a near-perfect heap spray.

The most complex aspect was achieving ASLR bypass and arbitrary read/write primitives, ultimately leading to RCE. The solution lay in exploiting Molang, Minecraft's custom scripting language, which is provided within resource packs. Molang variables (MolangVariable objects) have a specific in-memory structure, including an FNV1 hash of the variable name, an std::string for the name, a 32-bit type field, a 4-byte padding, and a 64-bit value field. Crucially, the value field is copied as a complete 64-bit value during assignments, even for 32-bit float numbers. Molang manages variables per-entity using a MolangVariableMap (a vector of pointers to MolangVariable objects) and a MolangIndexMap (a vector of 16-bit unsigned integers used with a global hash map to locate variables by unique index).

The core insight for arbitrary read/write was to corrupt entries in the MolangIndexMap to point out-of-bounds of the MolangVariableMap's backing buffer. By carefully placing other objects (like an adjacent entity object) next to the MolangVariableMap, a corrupted index could be made to read an adjacent pointer, treating it as a MolangVariable pointer. This allowed reading and writing through Molang variables A and B to arbitrary memory locations.

A significant challenge emerged due to Molang numbers being 32-bit floats. Writing a 64-bit address directly posed two problems: if the lower 32 bits exceeded float_max, it became an invalid float, and the upper 32 bits of the 64-bit value were often erased. This was overcome by a sophisticated pointer deconstruction and reconstruction technique. By manipulating two Molang variables, A and B, and exploiting the 4-byte padding within MolangVariable structs, the 64-bit pointer could be split into multiple 16-bit float-safe parts. Specifically, MolangIndexMap's last and end pointers (which are 2 bytes apart when the map is filled to capacity minus one) were used as a temporary region to deconstruct a 64-bit address into three 16-bit chunks. Each chunk, being a valid float, could be individually adjusted, and the pointer then reconstructed, enabling full 64-bit pointer control and arbitrary adjustment.

For ASLR bypass, the MolangIndexMap.last pointer was initially set to end - 638. This caused a Molang variable's value to reside in the first field of an adjacent chunk, which, when that chunk was an entity object, was its vtable pointer. Reading this Molang variable leaked the vtable address. After the leak, 27 unique variables were added to increment last back to end - 2, restoring the arbitrary read/write setup with known ASLR offsets.

Finally, to achieve RIP control despite Control Flow Guard (CFG), the researchers identified that statically linked OpenSSL code within the Minecraft client executable was compiled without CFG enforcement. They targeted OpenSSL's CRYPTO_set_mem_functions, which allows runtime modification of malloc, free, and realloc callbacks. Overwriting one of these callbacks (e.g., free) with a custom function pointer could give RIP control. However, direct stack pivot was not possible due to a lack of controllable registers. A more refined approach targeted OpenSSL's EC_KEY_METHOD_init function. This function reads a global default_EC_KEY_METHOD pointer into RBX, then reads an init function pointer from RBX+0x10 into RDX, and calls RDX. Crucially, at the moment RDX is called, RAX still contains the initial default_EC_KEY_METHOD pointer. By setting the init function pointer to a MOV RSP, RAX gadget found in the executable and pointing default_EC_KEY_METHOD to a controlled buffer, a stack pivot was achieved. The ROP chain then executed GetModuleHandleA("ucrtbase.dll"), GetProcAddress(ucrtbase, "system"), and finally system("cmd.exe"), popping a shell.

Technical Deep Dive

▶ Watch: Detailed explanation of the 4-byte heap overflow (4:26)

The technical depth of this exploit chain is remarkable, spanning multiple layers of software and hardware interaction.

The initial vulnerability, the 4-byte heap overflow, is rooted in the stb_image library's GIF parsing. Specifically, in the out_gif_code function, the g_current_index variable is used to calculate indices for g_history and g_out buffers before a bounds check is performed. A maliciously crafted GIF with width set to zero causes g_current_index to start at maxX (which is width-1), resulting in an off-by-one error. This leads to an out-of-bounds write at g_history[index / 4] and, more critically, a controlled 4-byte write to an address P (derived from index into g_out) using data from the attacker-controlled color_table. This controlled 4-byte write is the primitive from which the entire exploit chain is built.

Minecraft Bedrock Edition on Windows, being a Universal Windows Platform (UWP) application, utilizes the Segment Heap. This modern heap implementation introduces complexities for exploitation. The two relevant subsegments are:

  1. Low Fragmentation Heap (LFH): Manages allocations up to 0x3F0 bytes. LFH chunks are randomly inserted into subsegments and lack userland chunk headers. This means chunk data is adjacent, allowing a 4-byte overflow to directly corrupt data in an adjacent chunk (e.g., a reference count or length field).
  2. Variable Size (VS) Subsegment: Handles allocations from 0x4000 to 0x20000 bytes. VS chunks include a 16-byte HEAP_VS_CHUNK_HEADER encoded with a secret random heap key, making deterministic corruption challenging. However, known attacks target the 16-bit unsafe size field at offset 2. By overflowing this field with 0xFFFF, the decoded size becomes random but typically much larger (over 90% of the time). Freeing such a corrupted chunk leads to a heap chunk overlap, a powerful primitive.

The heap spray was achieved by exploiting Minecraft's sign blocks. These blocks store text using std::string. The std::string implementation uses Small String Optimization (SSO), where strings up to 16 bytes are stored directly within the std::string object. For longer strings, a separate heap allocation is made, and a pointer to this buffer is stored in the std::string object. This mechanism allowed for precise control over chunk size and data. The critical hurdle was a server-side length check limiting sign text to 512 bytes (0x200). This was bypassed by patching the server executable's assembly: the instruction CMP RAX, 0x200 followed by a conditional jump (JBE) was modified to an unconditional JMP, effectively disabling the size check. The client, trusting the server, then happily allocated larger buffers (e.g., 1024 bytes), enabling a robust heap spray.

The ASLR bypass and arbitrary read/write leveraged Molang, Minecraft's custom scripting language. Molang variables are stored as MolangVariable objects. The in-memory layout of MolangVariable is crucial: FNV1 hash (4B), std::string name (24B), type (4B), padding (4B), value (8B), and std::vector for struct (24B). The value field, despite often holding 32-bit float numbers, is copied as a full 64-bit value during assignments, a key observation. Molang variables are managed per-entity via a MolangVariableMap (std::vector<MolangVariable*>) and a MolangIndexMap (std::vector<uint16_t>). A global hash map translates variable names to unique indices, which are then used to access the MolangIndexMap, which in turn provides the index into the MolangVariableMap.

The arbitrary read/write primitive was established by corrupting entries in the MolangIndexMap to point out-of-bounds. By overflowing an index map entry, it could be made to point to an adjacent MolangVariableMap's first pointer (which points to its backing buffer). If this pointer was then read as a MolangVariable*, subsequent writes through that Molang variable would target the MolangVariableMap's backing buffer, effectively allowing arbitrary writes to this crucial vector's internal state.

A major challenge was dealing with Molang's 32-bit float numbers. Directly writing a 64-bit address through a 32-bit float field was problematic:

  1. If the lower 32 bits of the address exceeded float_max, it became an invalid float.
  2. Writing a 32-bit float would erase the upper 32 bits of any 64-bit value.

This was ingeniously solved by a 64-bit pointer deconstruction and reconstruction strategy. The solution exploited the 4-byte padding between the type and value fields within a MolangVariable struct. By carefully corrupting two MolangVariable pointers (A and B) to point to specific offsets (e.g., A to an address, B to address + 2), the 64-bit pointer could be split. B would contain the upper 48 bits, which could be saved. Setting B to zero would erase those upper 48 bits, leaving A with only the lower 16 bits (always a valid float). A could then be adjusted. Finally, B could be used to restore the upper 48 bits.

To generalize this for full 64-bit pointer adjustment, the MolangIndexMap's internal last and end pointers were used. When the MolangIndexMap is filled to capacity - 1, last points to end - 2. These two pointers, being 2 bytes apart, provided a stable region to deconstruct a 64-bit pointer into three 16-bit float-safe parts. Each 16-bit part could then be adjusted independently, and the pointer reconstructed.

The ASLR leak was achieved by initially setting the MolangIndexMap.last pointer to end - 638. This caused a Molang variable (B) to read from the first field of an adjacent chunk. By ensuring this adjacent chunk was an entity object, its first field (a vtable pointer) was leaked. After the leak, 27 unique Molang variables were added to increment last back to end - 2, restoring the arbitrary read/write setup with known ASLR offsets.

The final step, Control Flow Guard (CFG) bypass, addressed the challenge of hijacking execution on a CFG-protected process. The researchers discovered that while the main Minecraft executable enforced CFG, statically linked OpenSSL code within the client executable was compiled without CFG. This provided an unhardened target for RIP control.

Two OpenSSL functions were considered:

  1. CRYPTO_set_mem_functions: Allows overwriting global malloc, free, and realloc callbacks. Overwriting the free callback could give RIP control. However, this method lacked controllable registers for a stack pivot.
  2. EC_KEY_METHOD_init: This function reads a global default_EC_KEY_METHOD pointer into RBX, then reads an init function pointer from RBX+0x10 into RDX, and finally calls RDX. Crucially, when RDX is called, RAX still contains the initial default_EC_KEY_METHOD pointer.

This second function was the key. By overwriting the free callback to point to EC_KEY_METHOD_init, and by setting the init function pointer (at default_EC_KEY_METHOD + 0x10) to a MOV RSP, RAX gadget found in the executable, a stack pivot was achieved. The default_EC_KEY_METHOD pointer was made to point to a controlled region of memory. When EC_KEY_METHOD_init was called, RAX would point to this controlled region. The MOV RSP, RAX gadget would then move the stack pointer to the controlled region. A subsequent RET instruction would pop the first value from this controlled stack into RIP, initiating the ROP chain. The ROP chain was simple: GetModuleHandleA("ucrtbase.dll"), GetProcAddress(ucrtbase, "system"), and finally system("cmd.exe") to execute an arbitrary command and pop a shell.

Demo / Proof of Concept

▶ Watch: Segment heap (LFH) and heap shaping challenges (6:53)

The talk concluded with a compelling demo, showcasing the successful execution of the exploit. The demonstration involved a Redox server running the malicious code. The Molang exploit script, a generated artifact, spanned an impressive 13,000 lines, primarily consisting of repetitive code for pointer adjustments.

The process unfolded as follows:

  1. The server initiated the process, and a pop-up appeared on the client, signifying the server's control over when the GIF string (containing the heap overflow trigger) was processed.
  2. Upon triggering the overflow, the client was observed to be "teleported downwards." This action was a deliberate part of the exploit, designed to bring the entity containing the Molang exploit script into the client's field of view, thereby ensuring its execution.
  3. Soon after the teleportation, the Molang script began its execution, systematically performing the ASLR leak, arbitrary read/write setup, and CFG bypass.
  4. Finally, a cmd.exe shell promptly appeared on the compromised client machine, confirming the successful remote code execution.

It was noted that the demo did not include the heap spraying aspect, as making it reliably demonstrateable was not a focus of the presentation. Despite this, the core RCE chain was clearly illustrated and validated.

Defensive Implications

▶ Watch: Variable size heap and encoded chunk headers (7:30)

This research provides several critical lessons for defenders operating in environments that involve complex applications like Minecraft, especially those using native code and third-party libraries.

  1. Vigilant Third-Party Library Management: The core vulnerability resided in stb_image, a library that was seven years out of date. This underscores the paramount importance of regularly updating all third-party dependencies to their latest, patched versions. Organizations should implement robust supply chain security practices, including automated vulnerability scanning of dependencies and a clear policy for timely updates.
  2. Comprehensive Input Validation: The exploit hinged on bypassing a server-side length check. This highlights that server-side validation, while necessary, is often insufficient. Clients must never implicitly trust data received from a server. Robust, multi-layered input validation (both server-side and client-side, and ideally at multiple points in the processing pipeline) is essential to prevent malformed data from reaching vulnerable parsing functions.
  3. Hardening Custom Scripting Languages: Molang, as a custom scripting language, offered a rich attack surface. Developers of such languages should prioritize security from design, implementing strict type checking, bounds checks, and memory safety measures in their runtime environments. The ability to corrupt internal state via Molang variables demonstrates a need for better isolation or sandboxing.
  4. Awareness of Static Linking Risks: The CFG bypass exploited OpenSSL code that was statically linked without CFG protections. When statically linking libraries, especially security-critical ones, it is crucial to ensure they are compiled with all relevant hardening features enabled, matching the security posture of the main executable. This may require auditing the build processes of third-party components.
  5. Heap Exploitation Mitigations: While Segment Heap offers significant improvements over older heap managers, it is not impenetrable. Defenders should understand the specific attack vectors against modern heap implementations (e.g., VS chunk header corruption, LFH adjacent chunk overflows) and consider additional runtime protections or custom allocators where extreme sensitivity is required.
  6. Maximizing ASLR and CFG Coverage: The effectiveness of ASLR and CFG can be undermined if there are significant regions of non-randomized or non-CFG-protected code. Ensuring maximum entropy for ASLR across all loaded modules and comprehensive CFG coverage for all indirect calls is vital to raise the bar for exploitation.

Key Takeaways

  • Outdated Third-Party Libraries are High-Risk: Even widely used, seemingly simple libraries like stb_image can harbor critical vulnerabilities, especially when not kept up-to-date. Regular auditing and patching of all dependencies are essential.
  • Client-Side Trust is an Attack Vector: Server-side input validation alone is insufficient. Client applications must never implicitly trust data from a server; robust client-side validation is crucial to prevent server-to-client exploits.
  • Modern Heap Mitigations Require Advanced Techniques: Segment Heap, while a strong mitigation, can be bypassed. Exploiting it requires understanding its internal structures and developing specific techniques like variable-size chunk header corruption for heap chunk overlaps.
  • Custom Scripting Languages Offer Rich Attack Surfaces: Proprietary scripting languages like Molang, especially when integrated into complex systems, can introduce unique vulnerabilities, particularly if their internal state can be corrupted to gain powerful primitives like arbitrary read/write.
  • Creative Pointer Manipulation Overcomes Type System Limitations: Overcoming challenges like 32-bit float limitations when manipulating 64-bit pointers requires ingenious techniques such as pointer deconstruction into float-safe parts and reconstruction, showcasing the depth required for modern exploitation.
  • CFG Bypass Often Found in Unhardened Components: Control Flow Guard can be circumvented by targeting statically linked third-party components (e.g., OpenSSL) that were compiled without CFG, highlighting the importance of consistent hardening across the entire software supply chain.

About the Speaker(s)

Hrvoje Misetic is an independent security researcher. At the time of this research and talk, he was working as a web3 auditor and security researcher at Adacta. His work demonstrates a deep expertise in memory corruption vulnerabilities and advanced exploitation techniques.

Reviews

Dr. Zero (Offensive Security Researcher) — STRONG ACCEPT

This is what OffensiveCon is for. A full server-to-client RCE chain against a massively popular game, starting from a 4-byte heap overflow in a dusty stbimage commit from 2018, all the way through Segment Heap shaping, Molang abuse for arb r/w, and a CFG bypass via unhardened OpenSSL. Real work, real skill, real demo.

Heather Calloway (CISO) — SOLID

A rigorous exploitation chain against one of the largest consumer software footprints in the world. The technical execution is excellent, and the strategic implications for anyone managing consumer-facing products or thinking about third-party library risk are real. Worth understanding at the executive level even if the heap mechanics aren't your lane.

→ Top-rated talks at OffensiveCon 2026

All talks from OffensiveCon 2026