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

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

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

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

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
CVE-2020-0796 — CVE-2020-0796(SMBGhost)에 대한 기술 분석 및 개념 증명(PoC)으로, Windows 10/Server에서 로컬 권한 상승으로 이어지는 SMBv3 압축의 정수 오버플로 취약점입니다. | Kitploit
도구/GitHubGitHub/datntsec/cve-2020-0796
Privilege EscalationVulnerability AnalysisExploitationBinary Exploitation
GitHubdatntsec/cve-2020-0796

CVE-2020-0796

CVE-2020-0796(SMBGhost)에 대한 기술 분석 및 개념 증명(PoC)으로, Windows 10/Server에서 로컬 권한 상승으로 이어지는 SMBv3 압축의 정수 오버플로 취약점입니다.

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

인기

모두 보기 →

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

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

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

먼저 `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; }

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)를 입력받아 `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) { // ...

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으로 설정되어 있어서 그리 중요하지 않은 것 같습니다.
도구 다운로드