CVE-2021-24086漏洞分析
漏洞信息
2021年,Microsoft發布了一個安全補丁程序,修復了一個拒絕服務漏洞,編號為CVE-2021-24086,該漏洞影響每個Windows版本的IPv6堆棧,此問題是由于IPv6分片處理不當引起的。微軟為了這個漏洞特地升級了一次補丁,同時該漏洞的效果和影響也是非常巨大和廣泛,可直接遠程觸發目標機器藍屏死機。
漏洞復現
去年互聯網上發布了該漏洞的攻擊代碼,筆者從該地址得到了payload:
https://github.com/0vercl0k/CVE-2021-24086
我們需要在該main函數方法中設置攻擊機Linux的網卡名稱。

目標機器是需要填寫IPV6的地址。
python3 cve-2021-24086.py --target fe80::69eb:90bf:3f91:deae
觸發BSOD


漏洞分析
觸發BSOD后的棧回溯
0: kd> rrax=0000000000000000 rbx=0000000000000003 rcx=0000000000000003rdx=000000000000008a rsi=fffff800042531c0 rdi=0000000000000000rip=fffff800040f9720 rsp=fffff800054d5218 rbp=0000000000000000 r8=0000000000000065 r9=0000000000000000 r10=0000000000000000r11=fffff800054d4ea0 r12=000000000000000a r13=0000000000000001r14=0000000040000082 r15=0000000000000000iopl=0 nv up ei ng nz na pe nccs=0010 ss=0018 ds=002b es=002b fs=0053 gs=002b efl=00000282nt!RtlpBreakWithStatusInstruction:fffff800`040f9720 cc int 30: kd> kb # RetAddr : Args to Child : Call Site00 fffff800`041adc22 : 00000000`00000000 fffff800`042531c0 00000000`00000065 fffff800`040ca378 : nt!RtlpBreakWithStatusInstruction01 fffff800`041aea12 : 00000000`00000003 00000000`00000000 fffff800`041025d0 00000000`000000d1 : nt!KiBugCheckDebugBreak+0x1202 fffff800`040f2fa4 : 00000000`00000000 fffff800`04259888 fffff800`04259a02 fffff800`0408e00a : nt!KeBugCheck2+0x72203 fffff800`041012e9 : 00000000`0000000a 00000000`00000000 00000000`00000002 00000000`00000001 : nt!KeBugCheckEx+0x10404 fffff800`040ff0ce : 00000000`00000001 00000000`00000000 fffffa80`32dc6500 00000000`00010010 : nt!KiBugCheckDispatch+0x6905 fffff880`018884bd : fffff880`01920bff 00000000`00010010 fffffa80`00000060 fffffa80`339b15e0 : nt!KiPageFault+0x44e06 fffff880`01920bff : 00000000`00010010 fffffa80`00000060 fffffa80`339b15e0 00000000`00000000 : tcpip!memmove+0xbd07 fffff880`01938d18 : fffffa80`31fb1000 00000000`00000000 fffffa80`32dc6702 00000000`0000fffa : tcpip!Ipv6pReassembleDatagram+0x17f08 fffff880`01938e03 : fffffa80`00000008 fffff880`0196b738 fffffa80`32dfe8d0 fffffa80`31e96900 : tcpip!Ipv6pReceiveFragment+0xb5809 fffff880`01858964 : 0000057f`cd9e03a8 fffffa80`334a81b0 fffffa80`334a81b0 fffff880`01003e8b : tcpip!Ipv6pReceiveFragmentList+0x430a fffff880`0185642f : 00000000`00000000 00000000`0199c801 00000000`00000000 fffff880`01966870 : tcpip!IppReceiveHeaderBatch+0x4850b fffff880`01855a4c : fffffa80`32e0b960 00000000`00000000 fffff880`0199c801 fffffa80`00000001 : tcpip!IpFlcReceivePackets+0x64f0c fffff880`0185443a : fffffa80`32e13ba0 fffff800`054d6250 fffffa80`32e13ba0 00000003`e9110001 : tcpip!FlpReceiveNonPreValidatedNetBufferListChain+0xcec0d fffff800`040a8dd9 : fffffa80`32dc65e0 00000000`00000000 fffffa80`31e9c830 00000000`00000000 : tcpip!FlReceiveNetBufferListChainCalloutRoutine+0xda0e fffff880`01854b32 : fffff880`01854360 fffff800`054d6370 fffffa80`00000000 ffff0092`174ff900 : nt!KeExpandKernelStackAndCalloutEx+0x2c90f fffff880`017a70eb : fffffa80`32e148d0 00000000`00000000 fffffa80`3aef21a0 00000000`00000000 : tcpip!FlReceiveNetBufferListChain+0xb210 fffff880`01770ad6 : fffff800`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ndis!ndisMIndicateNetBufferListsToOpen+0xdb11 fffff880`016fd599 : fffffa80`3aef21a0 00000000`00000002 00000000`00000001 00000000`00000001 : ndis!ndisMDispatchReceiveNetBufferLists+0x1d612 fffff880`016f37a4 : 00000000`00010303 00000000`00000000 00000000`00000001 fffff880`016f2900 : ndis!ndisMDispatchReceiveNetBufferListsWithLock+0x8913 fffff880`016f3719 : 00000000`00000000 fffff880`05885759 fffffa80`32e02010 00000000`00000000 : ndis!ndisMTopReceiveNetBufferLists+0x2414 fffff880`016f36b0 : 00000000`00000000 00000000`00000009 00000000`00000000 00000000`00000001 : ndis!ndisFilterIndicateReceiveNetBufferLists+0x2915 fffff880`058858e1 : fffffa80`32e02010 00000000`00000001 00000000`00000001 fffffa80`32dc65e0 : ndis!NdisFIndicateReceiveNetBufferLists+0x5016 fffff880`0170bc24 : fffffa80`3aef21a0 fffffa80`32dc65e0 00000000`00000001 fffffa80`32dc65e0 : npcap+0x58e117 fffff880`0627c778 : 00000000`00000000 00000000`00000001 00000000`00000000 00000000`00000000 : ndis! ?? ::FNODOBFM::`string'+0xc72f18 fffff880`0627c3d1 : fffffa80`325ad001 00000000`00000000 00000000`00000000 fffffa80`32611000 : E1G6032E+0x77819 fffff880`016ea9b6 : fffffa80`32dd1670 00000000`00000000 fffffa80`3aef21a0 00000000`00000018 : E1G6032E+0x3d11a fffff800`0409fcbc : fffffa80`32dd1698 fffff800`00000000 00000000`00000000 fffff800`04243180 : ndis!ndisInterruptDpc+0x1b61b fffff800`040f642a : fffff800`04243180 fffff800`042531c0 00000000`00000000 fffff880`016ea800 : nt!KiRetireDpcList+0x1bc1c 00000000`00000000 : fffff800`054d7000 fffff800`054d1000 fffff800`054d6c00 00000000`00000000 : nt!KiIdleLoop+0x5a0: kd> .trap 0xfffff880`01938d18NOTE: The trap frame does not contain all registers.Some register values may be zeroed or incorrect.Unable to get program counterrax=0000000000000000 rbx=0000000000000000 rcx=0000000000000000rdx=0000000000000000 rsi=0000000000000000 rdi=0000000000000000rip=c328c48348000001 rsp=5718738949106b89 rbp=3301b04128ec8348 r8=0000000000000000 r9=0000000000000000 r10=0000000000000000r11=0000000000000000 r12=0000000000000000 r13=0000000000000000r14=0000000000000000 r15=0000000000000000iopl=0 vip vif ov up ei pl zr na po nc9090:0001 ?? ???rax=402cfaff00000060 rbx=0000000000000000 rcx=0000000000000020rdx=fffffa8032c2f198 rsi=0000000000000000 rdi=0000000000000000rip=fffff880018884bd rsp=fffff800054d5c68 rbp=fffffa8032c2f110 r8=0000000000000028 r9=0000000000000001 r10=00000000000080fer11=0000000000000000 r12=0000000000000000 r13=0000000000000000r14=0000000000000000 r15=0000000000000000iopl=0 nv up ei pl nz na pe nctcpip!memmove+0xbd:fffff880`018884bd 488941e0 mov qword ptr [rcx-20h],rax ds:00000000`00000000=????????????????Resetting default scope
漏洞是在tcpip!Ipv6pReassembleDatagram函數中發生了NULL解引用,該操作系統的函數調用鏈可以看到是在tcpip!memmove+0xbd發生0地址寫入奔潰,我們通過IDA逆向分析。
BytesNeededa = (char *)NdisGetDataBuffer((PNET_BUFFER)v14, BytesNeeded, 0i64, 1u, 0); v16 = IppCopyPacket(v7, a1); if ( !v16 ) goto LABEL_8; *(_WORD *)(a2 + 140) = __ROR2__(v29, 8); memmove(BytesNeededa, (const void *)(a2 + 136), 0x28ui64); memmove(BytesNeededa + 40, *(const void **)(a2 + 96), *(unsigned __int16 *)(a2 + 104)); BytesNeededa[*(unsigned __int16 *)(a2 + 184)] = *(_BYTE *)(a2 + 188);
一共有兩處使用了tcpip!memmove,而BytesNeededa來自(char *)NdisGetDataBuffer((PNET_BUFFER)v14, BytesNeeded, 0i64, 1u, 0)
NDIS_EXPORTED_ROUTINE PVOID NdisGetDataBuffer( [in] NET_BUFFER *NetBuffer, [in] ULONG BytesNeeded, [in, optional] PVOID Storage, [in] ULONG AlignMultiple, [in] ULONG AlignOffset);

從官方的MSDN可以的知,該值可以是指針也可能是NULL,而在下面沒有經過判斷就往內存寫入,如果當內存的地址為0時,就觸發了BSOD。
通過逆向分析發現NdisGetDataBuffer有幾處if判斷可能會將其寫入0。

NdisGetDataBuffer在NetBuffer->CurrentMdlOffset賦值后if判斷的時條件語句沒有為真從而執行else語句塊時返回0。

經過調試發現NdisRetreatNetBufferDataStart函數NetBuffer->DataLength賦值為0x28。
NDIS_STATUS __stdcall NdisRetreatNetBufferDataStart(PNET_BUFFER NetBuffer, ULONG DataOffsetDelta, ULONG DataBackFill, NET_BUFFER_ALLOCATE_MDL_HANDLER AllocateMdlHandler){ unsigned int v5; // ecx _MDL *v7; // rax unsigned int v8; // ecx unsigned int v9; // edx _MDL *v11; // rax ULONG v12; // ecx ULONG v13; // [rsp+38h] [rbp+10h] BYREF v5 = NetBuffer->DataOffset; // v5=0 if ( v5 >= DataOffsetDelta ) // v5=0;dataoffsetdelta=28 { v7 = NetBuffer->MdlChain; NetBuffer->DataLength += DataOffsetDelta; v8 = v5 - DataOffsetDelta; for ( NetBuffer->DataOffset = v8; v7; v8 -= v9 ) { v9 = v7->ByteCount; if ( v8 < v9 ) break; v7 = v7->Next; } NetBuffer->Link.Region = (unsigned __int64)v7; goto LABEL_5; } v13 = DataBackFill + DataOffsetDelta - v5; if ( !AllocateMdlHandler ) // AllocateMdlHandler=true AllocateMdlHandler = ndisAllocateMdl; v11 = (_MDL *)((__int64 (__fastcall *)(ULONG *))AllocateMdlHandler)(&v13); if ( v11 ) // v11=true { v11->Next = NetBuffer->MdlChain; v12 = v13; NetBuffer->MdlChain = v11; NetBuffer->Link.Region = (unsigned __int64)v11; NetBuffer->DataOffset += v12 - DataOffsetDelta; v8 = NetBuffer->DataOffset; NetBuffer->DataLength += DataOffsetDelta; // NetBuffer->DataLength=28LABEL_5: NetBuffer->CurrentMdlOffset = v8; return 0; } return -1073741670;}



Netbuffer在IDA中是沒有這個Struct的這里也說一下怎么添加一個Struct。
Shift+F1,右鍵Insert,粘貼添加即可。

這樣IDA就可以識別了。

看看簡化后的邏輯代碼。
if ( v15 < (unsigned __int16)BytesNeeded ) { if ( NdisRetreatNetBufferDataStart(netbuffer, (unsigned __int16)BytesNeeded, 0, NetioAllocateMdl_0) < 0 ) {LABEL_8: IppRemoveFromReassemblySet((PKSPIN_LOCK)(v7 + 20168)); NetioDereferenceNetBufferList_0(v13, 0i64); goto LABEL_24; } } else { netbuffer->DataOffset -= (unsigned __int16)BytesNeeded; netbuffer->DataLength += (unsigned __int16)BytesNeeded;// ((a2+104)+40) 2個字節 netbuffer->CurrentMdlOffset = v15 - (unsigned __int16)BytesNeeded; } BytesNeededa = (char *)NdisGetDataBuffer(netbuffer, BytesNeeded, 0i64, 1u, 0);
如果第一個if條件為真第二個不成立的時候就直接到NdisGetDataBuffer,下斷點到tcpip!Ipv6pReassembleDatagram并單步調試。
0: kd> ptcpip!Ipv6pReassembleDatagram+0xa:fffff880`01923a8a 53 push rbx0: kd> ptcpip!Ipv6pReassembleDatagram+0xb:fffff880`01923a8b 55 push rbp0: kd> ptcpip!Ipv6pReassembleDatagram+0xc:fffff880`01923a8c 56 push rsi0: kd> ptcpip!Ipv6pReassembleDatagram+0xd:fffff880`01923a8d 57 push rdi0: kd> ptcpip!Ipv6pReassembleDatagram+0xe:fffff880`01923a8e 4154 push r120: kd> ptcpip!Ipv6pReassembleDatagram+0x10:fffff880`01923a90 4155 push r130: kd> ptcpip!Ipv6pReassembleDatagram+0x12:fffff880`01923a92 4156 push r140: kd> ptcpip!Ipv6pReassembleDatagram+0x14:fffff880`01923a94 4157 push r150: kd> ptcpip!Ipv6pReassembleDatagram+0x16:fffff880`01923a96 4883ec58 sub rsp,58h0: kd> ptcpip!Ipv6pReassembleDatagram+0x1a:fffff880`01923a9a 440fb74a68 movzx r9d,word ptr [rdx+68h]0: kd> ptcpip!Ipv6pReassembleDatagram+0x1f:fffff880`01923a9f 8b426c mov eax,dword ptr [rdx+6Ch]0: kd> ptcpip!Ipv6pReassembleDatagram+0x22:fffff880`01923aa2 418af8 mov dil,r8b0: kd> ptcpip!Ipv6pReassembleDatagram+0x25:fffff880`01923aa5 4103c1 add eax,r9d0: kd> ptcpip!Ipv6pReassembleDatagram+0x28:fffff880`01923aa8 488bea mov rbp,rdx0: kd> ptcpip!Ipv6pReassembleDatagram+0x2b:fffff880`01923aab 898424b8000000 mov dword ptr [rsp+0B8h],eax0: kd>tcpip!Ipv6pReassembleDatagram+0x32:fffff880`01923ab2 83c028 add eax,28h0: kd> ptcpip!Ipv6pReassembleDatagram+0x35:fffff880`01923ab5 89442430 mov dword ptr [rsp+30h],eax0: kd> ptcpip!Ipv6pReassembleDatagram+0x39:fffff880`01923ab9 418d4128 lea eax,[r9+28h]0: kd> ptcpip!Ipv6pReassembleDatagram+0x3d:fffff880`01923abd 898424a8000000 mov dword ptr [rsp+0A8h],eax0: kd> ptcpip!Ipv6pReassembleDatagram+0x44:fffff880`01923ac4 488b81d0000000 mov rax,qword ptr [rcx+0D0h]0: kd>tcpip!Ipv6pReassembleDatagram+0x4b:fffff880`01923acb 33c9 xor ecx,ecx0: kd>tcpip!Ipv6pReassembleDatagram+0x4d:fffff880`01923acd 488b5808 mov rbx,qword ptr [rax+8]0: kd>tcpip!Ipv6pReassembleDatagram+0x51:fffff880`01923ad1 488b03 mov rax,qword ptr [rbx]0: kd>tcpip!Ipv6pReassembleDatagram+0x54:fffff880`01923ad4 4c8bb088020000 mov r14,qword ptr [rax+288h]0: kd>tcpip!Ipv6pReassembleDatagram+0x5b:fffff880`01923adb ff152ff80100 call qword ptr [tcpip!_imp_KeGetCurrentProcessorNumberEx (fffff880`01943310)]0: kd>tcpip!Ipv6pReassembleDatagram+0x61:fffff880`01923ae1 488d0d48edefff lea rcx,[tcpip!IppReassemblyNetBufferListsComplete (fffff880`01822830)]0: kd>tcpip!Ipv6pReassembleDatagram+0x68:fffff880`01923ae8 448be8 mov r13d,eax0: kd>tcpip!Ipv6pReassembleDatagram+0x6b:fffff880`01923aeb 488b8330030000 mov rax,qword ptr [rbx+330h]0: kd>tcpip!Ipv6pReassembleDatagram+0x72:fffff880`01923af2 4533c9 xor r9d,r9d0: kd>tcpip!Ipv6pReassembleDatagram+0x75:fffff880`01923af5 4e8b3ce8 mov r15,qword ptr [rax+r13*8]0: kd>tcpip!Ipv6pReassembleDatagram+0x79:fffff880`01923af9 49c1e508 shl r13,80: kd>tcpip!Ipv6pReassembleDatagram+0x7d:fffff880`01923afd 4533c0 xor r8d,r8d0: kd>tcpip!Ipv6pReassembleDatagram+0x80:fffff880`01923b00 4d03ae284e0000 add r13,qword ptr [r14+4E28h]0: kd>tcpip!Ipv6pReassembleDatagram+0x87:fffff880`01923b07 488bd5 mov rdx,rbp0: kd>tcpip!Ipv6pReassembleDatagram+0x8a:fffff880`01923b0a c644242800 mov byte ptr [rsp+28h],00: kd>tcpip!Ipv6pReassembleDatagram+0x8f:fffff880`01923b0f 8364242000 and dword ptr [rsp+20h],00: kd>tcpip!Ipv6pReassembleDatagram+0x94:fffff880`01923b14 e8f3d7f2ff call tcpip!NetioAllocateAndReferenceNetBufferAndNetBufferList (fffff880`0185130c)0: kd>tcpip!Ipv6pReassembleDatagram+0x99:fffff880`01923b19 4c8be0 mov r12,rax0: kd>tcpip!Ipv6pReassembleDatagram+0x9c:fffff880`01923b1c 4885c0 test rax,rax0: kd>tcpip!Ipv6pReassembleDatagram+0x9f:fffff880`01923b1f 7517 jne tcpip!Ipv6pReassembleDatagram+0xb8 (fffff880`01923b38)0: kd>tcpip!Ipv6pReassembleDatagram+0xb8:fffff880`01923b38 488b7008 mov rsi,qword ptr [rax+8]0: kd>tcpip!Ipv6pReassembleDatagram+0xbc:fffff880`01923b3c 8b9c24a8000000 mov ebx,dword ptr [rsp+0A8h]0: kd>tcpip!Ipv6pReassembleDatagram+0xc3:fffff880`01923b43 8b4610 mov eax,dword ptr [rsi+10h]0: kd>tcpip!Ipv6pReassembleDatagram+0xc6:fffff880`01923b46 0fb7d3 movzx edx,bx0: kd>tcpip!Ipv6pReassembleDatagram+0xc9:fffff880`01923b49 3bc2 cmp eax,edx0: kd>tcpip!Ipv6pReassembleDatagram+0xcb:fffff880`01923b4b 724e jb tcpip!Ipv6pReassembleDatagram+0x11b (fffff880`01923b9b)0: kd>tcpip!Ipv6pReassembleDatagram+0x11b:fffff880`01923b9b 4c8d0dbeaff2ff lea r9,[tcpip!NetioAllocateMdl (fffff880`0184eb60)]0: kd>tcpip!Ipv6pReassembleDatagram+0x122:fffff880`01923ba2 4533c0 xor r8d,r8d0: kd>tcpip!Ipv6pReassembleDatagram+0x125:fffff880`01923ba5 488bce mov rcx,rsi0: kd>tcpip!Ipv6pReassembleDatagram+0x128:fffff880`01923ba8 ff15c2020200 call qword ptr [tcpip!_imp_NdisRetreatNetBufferDataStart (fffff880`01943e70)]0: kd>tcpip!Ipv6pReassembleDatagram+0x12e:fffff880`01923bae 85c0 test eax,eax0: kd>tcpip!Ipv6pReassembleDatagram+0x130:fffff880`01923bb0 79a6 jns tcpip!Ipv6pReassembleDatagram+0xd8 (fffff880`01923b58)0: kd>tcpip!Ipv6pReassembleDatagram+0xd8:fffff880`01923b58 8364242000 and dword ptr [rsp+20h],00: kd> rrax=0000000000000000 rbx=0000000000000028 rcx=0000000000000038rdx=fffffa8033468541 rsi=fffffa8032587400 rdi=fffffa80322db002rip=fffff88001923b58 rsp=fffff80000b9dcd0 rbp=fffffa8033f5c6e0 r8=fffffa8033468540 r9=fffff8800184eb60 r10=fffffa8033468020r11=fffffa8033468540 r12=fffffa80325872d0 r13=fffffa80321ba900r14=fffff88001969870 r15=fffffa80334308d0iopl=0 nv up ei pl zr na po nccs=0010 ss=0018 ds=002b es=002b fs=0053 gs=002b efl=00000246
沒有進入到else語句而直接到了NdisGetDataBuffer處理,初步明白了原因,繼續分析tcpip!Ipv6pReceiveFragment函數tcpip!Ipv6pReceiveFragment是處理包分片,它將數據排列后處理對最后一個分片包到來時進行重組。
包分片其實在上學的時候就應該說過了,如果數據包的大小超過最大傳輸單元(MTU)的時候就會重組,從而還原一條完整的流。

Ipv6pReassembleDatagram是主要的問題函數,用來處理嵌套分片包時NET_BUFFER結構中讀取擴展頭的長度+ sizeof(IPv6_header)字節。IPv6頭的大小從上面的動態調試得知為0x28。


上面提到過MSDN指出NdisGetDataBuffer是會返回NULL的情況,可以使NET_BUFFER為NULL,NdisGetDataBuffer則返回NULL,而當在上下文被引用時則會觸發BSOD空指針引用。

POC中可以看到通過特制的擴展頭identification來使程序觸發異常,關于包重組的細節在蝶澈的文章(https://bbs.pediy.com/thread-266955.htm)中闡述的我認為已經算是全網最詳細的了,這里不再復述。
總結
協議棧的漏洞近幾年MSRC也是極其少見,而該漏洞問題出現在空指針引用所以目前只能觸發拒絕服務。關于TCP/IP的漏洞本人也是第一次分析,自己動手調試做下來遇到的問題也不少但也是一步步解決,漏洞挖掘和分析著實是一個拼細心和耐心的一份工作,所以文中如有錯誤的地方不吝賜教。