分享两个 CVE 漏洞的分析报告
收藏

本文作者:giantbranch(2019 年首次投稿作者)

CVE-2019-0708 微软远程桌面服务远程代码执行漏洞

因为 MS_T120 这个 channel 是内部 Channel,MS_T120 Channel 被绑定两次(内部绑定一次,然后我们又绑定一次——id 不是 31)。由于绑定的时候没有限制,所以绑定在两个不同的 ID 下,因此 MS_T120 Channel 就有两个引用,假如我们关闭 channel,就触发一次 free,而我们断开连接系统默认也会 free,那就变成了 Double Free 了(其实 Double Free 是 UAF 的特殊情况,因为这个 USE 是 free 而已)。

实验环境

win 7 32 位 旗舰版

漏洞分析

发送 POC

kd> g
*** Fatal System Error: 0x0000000a (0x00000000,0x00000002,0x00000001,0x840ED940)
Break instruction exception - code 80000003 (first chance)
A fatal system error has occurred.Debugger entered on first try; Bugcheck callbacks have not been invoked.
A fatal system error has occurred.
Connected to Windows 7 7600 x86 compatible target at (Sun Sep 29 16:59:07.622 2019 (UTC + 8:00)), ptr64 FALSELoading Kernel Symbols..............................................................................................................................................................Loading User Symbols....................................................................................Loading unloaded module list.........******************************************************************************** ** Bugcheck Analysis ** ********************************************************************************
Use !analyze -v to get detailed debugging information.
BugCheck A, {0, 2, 1, 840ed940}
Probably caused by : termdd.sys ( termdd!_IcaFreeChannel+44 )
Followup: MachineOwner---------
nt!RtlpBreakWithStatusInstruction:840b2394 cc int 3kd> !analyze -v******************************************************************************** ** Bugcheck Analysis ** ********************************************************************************
IRQL_NOT_LESS_OR_EQUAL (a)An attempt was made to access a pageable (or completely invalid) address at aninterrupt request level (IRQL) that is too high. This is usuallycaused by drivers using improper addresses.If a kernel debugger is available get the stack backtrace.Arguments:Arg1: 00000000, memory referencedArg2: 00000002, IRQLArg3: 00000001, bitfield : bit 0 : value 0 = read operation, 1 = write operation bit 3 : value 0 = not an execute operation, 1 = execute operation (only on chips which support this level of status)Arg4: 840ed940, address which referenced memory
Debugging Details:------------------

WRITE_ADDRESS: 00000000
CURRENT_IRQL: 2
FAULTING_IP: nt!ExDeleteResourceLite+87840ed940 8901 mov dword ptr [ecx],eax
DEFAULT_BUCKET_ID: VISTA_DRIVER_FAULT
BUGCHECK_STR: 0xA
PROCESS_NAME: svchost.exe
TRAP_FRAME: 90cc68ac -- (.trap 0xffffffff90cc68ac)ErrCode = 00000002eax=00000000 ebx=00000000 ecx=00000000 edx=00000000 esi=841b0280 edi=8a998884eip=840ed940 esp=90cc6920 ebp=90cc6934 iopl=0 nv up ei pl zr na pe nccs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010246nt!ExDeleteResourceLite+0x87:840ed940 8901 mov dword ptr [ecx],eax ds:0023:00000000=????????Resetting default scope
LAST_CONTROL_TRANSFER: from 84123e71 to 840b2394
STACK_TEXT: 90cc6474 84123e71 00000003 8fa1ca4b 00000065 nt!RtlpBreakWithStatusInstruction90cc64c4 8412496d 00000003 00000000 840ed940 nt!KiBugCheckDebugBreak+0x1c90cc688c 8408d7eb 0000000a 00000000 00000002 nt!KeBugCheck2+0x68b90cc688c 840ed940 0000000a 00000000 00000002 nt!KiTrap0E+0x2cf90cc6934 90237da2 8a998884 868a7c98 8a998878 nt!ExDeleteResourceLite+0x8790cc6948 90238060 8a998878 8a998884 868b5670 termdd!_IcaFreeChannel+0x4490cc6964 9023895f 8a998878 868b5670 00000000 termdd!IcaDereferenceChannel+0x3490cc69a0 90239354 868b5670 00000005 0000001f termdd!IcaChannelInputInternal+0x3a790cc69c8 a60c5dc9 88cc2e24 00000005 0000001f termdd!IcaChannelInput+0x3c90cc6a00 a60c5e31 a6232008 88cc2e20 88cc2e10 RDPWD!SignalBrokenConnection+0x4090cc6a18 9023937f a5f73008 00000004 00000000 RDPWD!MCSIcaChannelInput+0x5590cc6a44 a609d436 88e14884 00000004 00000000 termdd!IcaChannelInput+0x6790cc726c a609d090 88e14880 88c525a8 840137a0 tssecsrv!CDefaultDataManager::Disconnect+0x3c90cc72a4 a609ca16 90cc72b4 88e14870 a60a0118 tssecsrv!CFilter::FilterIncomingData+0x22290cc72d0 9023c772 88c525a8 00000000 868a1cb4 tssecsrv!ScrRawInput+0x6090cc72f4 a60936a9 868195c4 00000000 868a1cb4 termdd!IcaRawInput+0x5a90cc7b30 9023b56d 868a1b68 8911f330 8844dc58 tdtcp!TdInputThread+0x34d90cc7b4c 9023b67c 89073800 00380173 8911f3a0 termdd!_IcaDriverThread+0x5390cc7b74 9023c00c 8844dc58 8911f330 868b5670 termdd!_IcaStartInputThread+0x6c90cc7bb4 90239e91 868b5670 8911f330 8911f3a0 termdd!IcaDeviceControlStack+0x50a90cc7be4 9023a065 8911f330 8911f3a0 88f3ce68 termdd!IcaDeviceControl+0x5990cc7bfc 840834bc 87c0cbb0 8911f330 8911f330 termdd!IcaDispatch+0x13f90cc7c14 84284eee 88f3ce68 8911f330 8911f3a0 nt!IofCallDriver+0x6390cc7c34 842a1cd1 87c0cbb0 88f3ce68 00000000 nt!IopSynchronousServiceTail+0x1f890cc7cd0 842a44ac 87c0cbb0 8911f330 00000000 nt!IopXxxControlFile+0x6aa90cc7d04 8408a42a 0000096c 00000000 00000000 nt!NtDeviceIoControlFile+0x2a90cc7d04 776b64f4 0000096c 00000000 00000000 nt!KiFastCallEntry+0x12a031cfc4c 776b4cac 6f5b18a7 0000096c 00000000 ntdll!KiFastSystemCallRet031cfc50 6f5b18a7 0000096c 00000000 00000000 ntdll!NtDeviceIoControlFile+0xc031cfc8c 6f5b25e9 0000096c 00380173 0037f0e0 ICAAPI!IcaIoControl+0x29031cfcbc 77811174 80000000 031cfd08 776cb3f5 ICAAPI!IcaInputThreadUserMode+0x37031cfcc8 776cb3f5 0037f0d8 74694ddb 00000000 kernel32!BaseThreadInitThunk+0xe031cfd08 776cb3c8 6f5b25b2 0037f0d8 00000000 ntdll!__RtlUserThreadStart+0x70031cfd20 00000000 6f5b25b2 0037f0d8 00000000 ntdll!_RtlUserThreadStart+0x1b

STACK_COMMAND: kb
FOLLOWUP_IP: termdd!_IcaFreeChannel+4490237da2 8d4644 lea eax,[esi+44h]
SYMBOL_STACK_INDEX: 5
SYMBOL_NAME: termdd!_IcaFreeChannel+44
FOLLOWUP_NAME: MachineOwner
MODULE_NAME: termdd
IMAGE_NAME: termdd.sys
DEBUG_FLR_IMAGE_TIMESTAMP: 4a5bcadf
FAILURE_BUCKET_ID: 0xA_termdd!_IcaFreeChannel+44
BUCKET_ID: 0xA_termdd!_IcaFreeChannel+44
Followup: MachineOwner---------

可以看到是termdd!_IcaFreeChannel调用nt!ExDeleteResourceLite后崩溃了

在 free 的时候崩溃,那么很有可能就是 double free 了

IcaRebindVirtualChannelsIcaBindVirtualChannels中我们都可以看到IcaFindChannelByName函数,我们看看这个函数,通过这个我们知道 channelname 是在 0x94 偏移

int __stdcall IcaFindChannelByName(int a1, int a2, char *a3){  int v4; // ebx  _DWORD *v5; // esi  int v6; // edi
if ( a2 != 5 ) return IcaFindChannel(a1, a2, 0); IcaLockChannelTable(a1 + 272); v4 = a1 + 80; v5 = *(_DWORD **)(a1 + 80); if ( v5 == (_DWORD *)(a1 + 80) ) goto LABEL_14; do { v6 = (int)(v5 - 40); if ( *(v5 - 4) == 5 && !__stricmp((const char *)(v6 + 0x94), a3) ) //通过这个我们知道channelname是在0x94偏移 break; v5 = (_DWORD *)*v5; } while ( v5 != (_DWORD *)v4 ); if ( v5 != (_DWORD *)v4 ) _InterlockedExchangeAdd((volatile signed __int32 *)(v6 + 8), 1u); elseLABEL_14: v6 = 0; IcaUnlockChannelTable(a1 + 272); return v6;}

通过上面栈回溯的这一行,我们可以看到 free 的地址是8a998878

90cc6948 90238060 8a998878 8a998884 868b5670 termdd!_IcaFreeChannel+0x44

看看 channel name 是不是 MS_T120(0x94 这个便宜系统不同,应该不一样的)

kd> da 8a998878+0x94 8a99890c  "MS_T120"

可以看到确实是的,我们现在还不能完全确认是 MS_T120 channel 的 UAF。

要进入步确认我们就需要看看这个 MS_T120 channel 实在哪里创建,是不是释放了两次

现在 free 函数已经知道了,那么申请内存的函数呢?

我们看看有哪些函数调用了IcaFindChannelByName

可以看到有一个IcaCreateChannel函数,很可能就是创建 Channel,申请内存的函数,我们跟过去,又发现一个_IcaAllocateChannel

进去看看,看到申请的函数了,就是它了

那我们下两个记录断点(这个需要查看汇编,看看ExAllocatePoolWithTag的返回值,还有_IcaFreeChannel的参数

bu termdd!_IcaAllocateChannel+0x1c ".printf \"AllocateChannel Addresss: 0x%x\n\",@eax;.echo;gc"bu termdd!_IcaFreeChannel ".printf \"FreeChannel Addresss: 0x%x\n\",@esi;.echo;gc"

再发送 poc, 发送 payload

AllocateChannel Addresss: 0x88eecf38 AllocateChannel Addresss: 0x892b4df0 AllocateChannel Addresss: 0x86a10d08 AllocateChannel Addresss: 0x87c63688 AllocateChannel Addresss: 0x88f6e1a8 AllocateChannel Addresss: 0x892b4818 FreeChannel Addresss: 0x88eecf38 FreeChannel Addresss: 0x88f6e1a8 FreeChannel Addresss: 0x892b4818 FreeChannel Addresss: 0x87c63688 FreeChannel Addresss: 0x86a10d08 FreeChannel Addresss: 0x892b4df0 AllocateChannel Addresss: 0x88f6e1a8 AllocateChannel Addresss: 0x892bfc10 AllocateChannel Addresss: 0x88eecf38 AllocateChannel Addresss: 0x87c63688 AllocateChannel Addresss: 0x892b4df0 AllocateChannel Addresss: 0x86a95c50 FreeChannel Addresss: 0x88f6e1a8 FreeChannel Addresss: 0x88f6e1a8 
*** Fatal System Error: 0x0000000a (0x00000000,0x00000002,0x00000001,0x840ED940)
Break instruction exception - code 80000003 (first chance)
A fatal system error has occurred.Debugger entered on first try; Bugcheck callbacks have not been invoked.
A fatal system error has occurred.
Connected to Windows 7 7600 x86 compatible target at (Fri Sep 27 15:29:54.017 2019 (UTC + 8:00)), ptr64 FALSELoading Kernel Symbols.............................................................................................................................................................Loading User Symbols....................................................................................Loading unloaded module list......******************************************************************************** ** Bugcheck Analysis ** ********************************************************************************
Use !analyze -v to get detailed debugging information.
BugCheck A, {0, 2, 1, 840ed940}
Probably caused by : termdd.sys ( termdd!_IcaFreeChannel+44 )
Followup: MachineOwner---------
nt!RtlpBreakWithStatusInstruction:840b2394 cc int 3

我们看到,对 0x88f6e1a8 这个地址 free 了两次

FreeChannel Addresss: 0x88f6e1a8 FreeChannel Addresss: 0x88f6e1a8

那就完全确认这个是 UAF,也可以说是 DOUBLE FREE(2 free)

在上面的基础,我们再下一个断点,看看是否同一个 channel 绑定了两个 ID

bu termdd!_IcaBindChannel ".echo _IcaBindChannel ==================;kv;gc"

我已经把垃圾信息过滤了,日志如下:

kd> gAllocateChannel Addresss: 0x88fd5738 _IcaBindChannel ==================ChildEBP RetAddr  Args to Child              a52c99e0 90238be9 88fd5738 00000005 0000001f termdd!_IcaBindChannel (FPO: [Non-Fpo])a52c9a04 90238d5e 8b1788e9 00000005 891282a7 termdd!_IcaAllocateChannel+0xf1 (FPO: [Non-Fpo])a52c9a28 90240177 88f9b6b0 00000005 88b89648 termdd!IcaCreateChannel+0x6c (FPO: [Non-Fpo])a52c9a58 9023a019 88b89648 88b896b8 869454d0 termdd!IcaCreate+0x13d (FPO: [Non-Fpo])a52c9a70 840834bc 87c0cbb0 88b89648 8694552c termdd!IcaDispatch+0xf3 (FPO: [Non-Fpo])a52c9a88 8428762d ba4135ef a52c9c30 00000000 nt!IofCallDriver+0x63a52c9b60 842681d7 87c0cbb0 a57ff6e0 87bf5858 nt!IopParseDevice+0xed7a52c9bdc 8428e24d 00000000 a52c9c30 00000040 nt!ObpLookupObjectName+0x4faa52c9c38 842865ab 013ae714 867ff6e0 a52c9c01 nt!ObOpenObjectByName+0x159a52c9cb4 84291eb6 03251114 c0100000 013ae714 nt!IopCreateFile+0x673a52c9d00 8408a42a 03251114 c0100000 013ae714 nt!NtCreateFile+0x34a52c9d00 776b64f4 03251114 c0100000 013ae714 nt!KiFastCallEntry+0x12a (FPO: [0,3] TrapFrame @ a52c9d34).................._IcaBindChannel ==================ChildEBP RetAddr  Args to Child              a52c9960 9023949b 88fd5738 00000005 00000003 termdd!_IcaBindChannel (FPO: [Non-Fpo])a52c9b74 9023bf90 86818670 88f17708 86818670 termdd!IcaBindVirtualChannels+0x101 (FPO: [Non-Fpo])a52c9bb4 90239e91 86818670 88f17708 88f17778 termdd!IcaDeviceControlStack+0x48e (FPO: [Non-Fpo])a52c9be4 9023a065 88f17708 88f17778 89187758 termdd!IcaDeviceControl+0x59 (FPO: [Non-Fpo])a52c9bfc 840834bc 87c0cbb0 88f17708 88f17708 termdd!IcaDispatch+0x13f (FPO: [Non-Fpo])a52c9c14 84284eee 89187758 88f17708 88f17778 nt!IofCallDriver+0x63a52c9c34 842a1cd1 87c0cbb0 89187758 00000000 nt!IopSynchronousServiceTail+0x1f8a52c9cd0 842a44ac 87c0cbb0 88f17708 00000000 nt!IopXxxControlFile+0x6aaa52c9d04 8408a42a 00000840 00000000 00000000 nt!NtDeviceIoControlFile+0x2aa52c9d04 776b64f4 00000840 00000000 00000000 nt!KiFastCallEntry+0x12a (FPO: [0,3] TrapFrame @ a52c9d34)..................FreeChannel Addresss: 0x88fd5738 FreeChannel Addresss: 0x88fd5738

我们首先确定 0x88fd5738 这个地址的 channel 是不是 MS_T120

kd> da 0x88fd5738+0x9488fd57cc  "MS_T120"

再看看termdd!_IcaBindChannel 的栈,针对的都是88fd5738这个地址,但是我们看第 3 个参数第一次是 0x1f(其实就是十进制的 31),而第二次是 03,那就明显看到将同一个 channel 绑定了两个 ID,导致有了两个引用,所以修复的时候强制指定为 31,不管你绑定多少次,ID 都是 31

a52c99e0 90238be9 88fd5738 00000005 0000001f termdd!_IcaBindChannel (FPO: [Non-Fpo])
a52c9960 9023949b 88fd5738 00000005 00000003 termdd!_IcaBindChannel (FPO: [Non-Fpo])

最后我们再来看看他们调用栈的不同点

第一个调用栈,可以看到从 NtCreateFile 到 termdd!IcaDispatch 再到 termdd!IcaCreateChannel,就是系统创建的这个 channel,分配这个 channel 后进行了 _IcaBindChannel 操作

ChildEBP RetAddr  Args to Child         a52c99e0 90238be9 88fd5738 00000005 0000001f termdd!_IcaBindChannel (FPO: [Non-Fpo])a52c9a04 90238d5e 8b1788e9 00000005 891282a7 termdd!_IcaAllocateChannel+0xf1 (FPO: [Non-Fpo])a52c9a28 90240177 88f9b6b0 00000005 88b89648 termdd!IcaCreateChannel+0x6c (FPO: [Non-Fpo])a52c9a58 9023a019 88b89648 88b896b8 869454d0 termdd!IcaCreate+0x13d (FPO: [Non-Fpo])a52c9a70 840834bc 87c0cbb0 88b89648 8694552c termdd!IcaDispatch+0xf3 (FPO: [Non-Fpo])a52c9a88 8428762d ba4135ef a52c9c30 00000000 nt!IofCallDriver+0x63a52c9b60 842681d7 87c0cbb0 a57ff6e0 87bf5858 nt!IopParseDevice+0xed7a52c9bdc 8428e24d 00000000 a52c9c30 00000040 nt!ObpLookupObjectName+0x4faa52c9c38 842865ab 013ae714 867ff6e0 a52c9c01 nt!ObOpenObjectByName+0x159a52c9cb4 84291eb6 03251114 c0100000 013ae714 nt!IopCreateFile+0x673a52c9d00 8408a42a 03251114 c0100000 013ae714 nt!NtCreateFile+0x34a52c9d00 776b64f4 03251114 c0100000 013ae714 nt!KiFastCallEntry+0x12a (FPO: [0,3] TrapFrame @ a52c9d34)..................

而第二次明显是由我们触发的,termdd!IcaDeviceControl 到 termdd!IcaBindVirtualChannels

ChildEBP RetAddr  Args to Child              a52c9960 9023949b 88fd5738 00000005 00000003 termdd!_IcaBindChannel (FPO: [Non-Fpo])a52c9b74 9023bf90 86818670 88f17708 86818670 termdd!IcaBindVirtualChannels+0x101 (FPO: [Non-Fpo])a52c9bb4 90239e91 86818670 88f17708 88f17778 termdd!IcaDeviceControlStack+0x48e (FPO: [Non-Fpo])a52c9be4 9023a065 88f17708 88f17778 89187758 termdd!IcaDeviceControl+0x59 (FPO: [Non-Fpo])a52c9bfc 840834bc 87c0cbb0 88f17708 88f17708 termdd!IcaDispatch+0x13f (FPO: [Non-Fpo])a52c9c14 84284eee 89187758 88f17708 88f17778 nt!IofCallDriver+0x63a52c9c34 842a1cd1 87c0cbb0 89187758 00000000 nt!IopSynchronousServiceTail+0x1f8a52c9cd0 842a44ac 87c0cbb0 88f17708 00000000 nt!IopXxxControlFile+0x6aaa52c9d04 8408a42a 00000840 00000000 00000000 nt!NtDeviceIoControlFile+0x2aa52c9d04 776b64f4 00000840 00000000 00000000 nt!KiFastCallEntry+0x12a (FPO: [0,3] TrapFrame @ a52c9d34)..................

那么到最后我们我们关闭连接,我们绑定的 MS_T120 channel free 了一次,系统自己再 free一次,那就造成了 double free 了

可以看看最后两次 free 的调用栈,第一次我们主动释放了 ID 为 03 的 channel,第二次是我们关闭了连接导致的释放(ID 为 31),明显看到第二次的栈上有tssecsrv!CDefaultDataManager::Disconnect(由于多次调试,下面的跟上面的地址会不一样)

FreeChannel Addresss: 0x88ca9d48 ChildEBP RetAddr  Args to Child              8e653a10 90238060 88ca9d48 00000000 891e19a8 termdd!_IcaFreeChannel (FPO: [Non-Fpo])8e653a2c 90238949 88ca9d48 890a0670 00000000 termdd!IcaDereferenceChannel+0x34 (FPO: [Non-Fpo])8e653a68 90239354 890a0670 00000005 00000003 termdd!IcaChannelInputInternal+0x391 (FPO: [Non-Fpo])8e653a90 945be1cf 8a9fb0d4 00000005 00000003 termdd!IcaChannelInput+0x3c (FPO: [Non-Fpo])8e653ab0 945c1548 8a9fb0d4 00000005 00000003 RDPWD!WDICART_IcaChannelInputEx+0x1d (FPO: [Non-Fpo])8e654148 945bbe42 a41c3008 8a9e9682 00000014 RDPWD!WDW_OnDataReceived+0x240 (FPO: [Non-Fpo])8e654174 945bbbfd a41c38f0 a41e7134 00000000 RDPWD!SM_MCSSendDataCallback+0x19a (FPO: [Non-Fpo])8e6541cc 945bba64 00000027 8e654204 8a9e9674 RDPWD!HandleAllSendDataPDUs+0x115 (FPO: [Non-Fpo])8e6541e8 945d7958 00000027 8e654204 8a9fb0d0 RDPWD!RecognizeMCSFrame+0x32 (FPO: [Non-Fpo])8e654214 945be63f a41c3008 8a9e96a2 00000001 RDPWD!MCSIcaRawInputWorker+0x3b4 (FPO: [Non-Fpo])8e654228 9023c772 a41c3008 00000000 8a9e9674 RDPWD!WDLIB_MCSIcaRawInput+0x13 (FPO: [Non-Fpo])8e65424c 945af46d 8a95a0c4 00000000 8a9e9674 termdd!IcaRawInput+0x5a (FPO: [Non-Fpo])8e654264 945aef06 8a9e9674 0000002f 8a95a0c0 tssecsrv!CRawInputDM::PassDataToServer+0x2b (FPO: [Non-Fpo])8e6542a4 945aea16 8e6542b4 8a95a0b0 945b2118 tssecsrv!CFilter::FilterIncomingData+0x98 (FPO: [Non-Fpo])8e6542d0 9023c772 88c85050 00000000 8a9e9674 tssecsrv!ScrRawInput+0x60 (FPO: [Non-Fpo])8e6542f4 945a56a9 8918c284 00000000 8a9e9674 termdd!IcaRawInput+0x5a (FPO: [Non-Fpo])8e654b30 9023b56d 8a9e9528 88580158 8a981b68 tdtcp!TdInputThread+0x34d (FPO: [Non-Fpo])8e654b4c 9023b67c 8a9f5878 00380173 885801c8 termdd!_IcaDriverThread+0x53 (FPO: [Non-Fpo])8e654b74 9023c00c 8a981b68 88580158 890a0670 termdd!_IcaStartInputThread+0x6c (FPO: [Non-Fpo])8e654bb4 90239e91 890a0670 88580158 885801c8 termdd!IcaDeviceControlStack+0x50a (FPO: [Non-Fpo])8e654be4 9023a065 88580158 885801c8 890a1360 termdd!IcaDeviceControl+0x59 (FPO: [Non-Fpo])8e654bfc 840834bc 87c0cbb0 88580158 88580158 termdd!IcaDispatch+0x13f (FPO: [Non-Fpo])8e654c14 84284eee 890a1360 88580158 885801c8 nt!IofCallDriver+0x638e654c34 842a1cd1 87c0cbb0 890a1360 00000000 nt!IopSynchronousServiceTail+0x1f88e654cd0 842a44ac 87c0cbb0 88580158 00000000 nt!IopXxxControlFile+0x6aa8e654d04 8408a42a 000007f4 00000000 00000000 nt!NtDeviceIoControlFile+0x2a8e654d04 776b64f4 000007f4 00000000 00000000 nt!KiFastCallEntry+0x12a (FPO: [0,3] TrapFrame @ 8e654d34)037af99c 776b4cac 6f5b18a7 000007f4 00000000 ntdll!KiFastSystemCallRet (FPO: [0,0,0])037af9a0 6f5b18a7 000007f4 00000000 00000000 ntdll!NtDeviceIoControlFile+0xc (FPO: [10,0,0])037af9dc 6f5b25e9 000007f4 00380173 029916f8 ICAAPI!IcaIoControl+0x29 (FPO: [Non-Fpo])037afa0c 77811174 80000000 037afa58 776cb3f5 ICAAPI!IcaInputThreadUserMode+0x37 (FPO: [Non-Fpo])037afa18 776cb3f5 029916f0 740f4a8b 00000000 kernel32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo])037afa58 776cb3c8 6f5b25b2 029916f0 00000000 ntdll!__RtlUserThreadStart+0x70 (FPO: [Non-Fpo])037afa70 00000000 6f5b25b2 029916f0 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])FreeChannel Addresss: 0x88ca9d48 ChildEBP RetAddr  Args to Child              8e653948 90238060 88ca9d48 88ca9d54 890a0670 termdd!_IcaFreeChannel (FPO: [Non-Fpo])8e653964 9023895f 88ca9d48 890a0670 00000000 termdd!IcaDereferenceChannel+0x34 (FPO: [Non-Fpo])8e6539a0 90239354 890a0670 00000005 0000001f termdd!IcaChannelInputInternal+0x3a7 (FPO: [Non-Fpo])8e6539c8 945d7dc9 8a9fb0d4 00000005 0000001f termdd!IcaChannelInput+0x3c (FPO: [Non-Fpo])8e653a00 945d7e31 a41e7008 8a9fb0d0 8a9fb0c0 RDPWD!SignalBrokenConnection+0x40 (FPO: [Non-Fpo])8e653a18 9023937f a41c3008 00000004 00000000 RDPWD!MCSIcaChannelInput+0x55 (FPO: [Non-Fpo])8e653a44 945af436 8a95a0c4 00000004 00000000 termdd!IcaChannelInput+0x67 (FPO: [Non-Fpo])8e65426c 945af090 8a95a0c0 88c85050 840137a0 tssecsrv!CDefaultDataManager::Disconnect+0x3c (FPO: [Non-Fpo])8e6542a4 945aea16 8e6542b4 8a95a0b0 945b2118 tssecsrv!CFilter::FilterIncomingData+0x222 (FPO: [Non-Fpo])8e6542d0 9023c772 88c85050 00000000 8a9e9674 tssecsrv!ScrRawInput+0x60 (FPO: [Non-Fpo])8e6542f4 945a56a9 8918c284 00000000 8a9e9674 termdd!IcaRawInput+0x5a (FPO: [Non-Fpo])8e654b30 9023b56d 8a9e9528 88580158 8a981b68 tdtcp!TdInputThread+0x34d (FPO: [Non-Fpo])8e654b4c 9023b67c 8a9f5878 00380173 885801c8 termdd!_IcaDriverThread+0x53 (FPO: [Non-Fpo])8e654b74 9023c00c 8a981b68 88580158 890a0670 termdd!_IcaStartInputThread+0x6c (FPO: [Non-Fpo])8e654bb4 90239e91 890a0670 88580158 885801c8 termdd!IcaDeviceControlStack+0x50a (FPO: [Non-Fpo])8e654be4 9023a065 88580158 885801c8 890a1360 termdd!IcaDeviceControl+0x59 (FPO: [Non-Fpo])8e654bfc 840834bc 87c0cbb0 88580158 88580158 termdd!IcaDispatch+0x13f (FPO: [Non-Fpo])8e654c14 84284eee 890a1360 88580158 885801c8 nt!IofCallDriver+0x638e654c34 842a1cd1 87c0cbb0 890a1360 00000000 nt!IopSynchronousServiceTail+0x1f88e654cd0 842a44ac 87c0cbb0 88580158 00000000 nt!IopXxxControlFile+0x6aa8e654d04 8408a42a 000007f4 00000000 00000000 nt!NtDeviceIoControlFile+0x2a8e654d04 776b64f4 000007f4 00000000 00000000 nt!KiFastCallEntry+0x12a (FPO: [0,3] TrapFrame @ 8e654d34)037af99c 776b4cac 6f5b18a7 000007f4 00000000 ntdll!KiFastSystemCallRet (FPO: [0,0,0])037af9a0 6f5b18a7 000007f4 00000000 00000000 ntdll!NtDeviceIoControlFile+0xc (FPO: [10,0,0])037af9dc 6f5b25e9 000007f4 00380173 029916f8 ICAAPI!IcaIoControl+0x29 (FPO: [Non-Fpo])037afa0c 77811174 80000000 037afa58 776cb3f5 ICAAPI!IcaInputThreadUserMode+0x37 (FPO: [Non-Fpo])037afa18 776cb3f5 029916f0 740f4a8b 00000000 kernel32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo])037afa58 776cb3c8 6f5b25b2 029916f0 00000000 ntdll!__RtlUserThreadStart+0x70 (FPO: [Non-Fpo])037afa70 00000000 6f5b25b2 029916f0 00000000 ntdll!_RtlUserThreadStart+0x1b (FPO: [Non-Fpo])

漏洞利用简介

由于是 double free,其实就是 uaf 利用思路,我们在第二次 free 的时候向上回溯

ChildEBP RetAddr  Args to Child              8e653948 90238060 88ca9d48 88ca9d54 890a0670 termdd!_IcaFreeChannel (FPO: [Non-Fpo])8e653964 9023895f 88ca9d48 890a0670 00000000 termdd!IcaDereferenceChannel+0x34 (FPO: [Non-Fpo])8e6539a0 90239354 890a0670 00000005 0000001f termdd!IcaChannelInputInternal+0x3a7 (FPO: [Non-Fpo])

发现 IcaChannelInputInternal 有虚函数调用,可以从这劫持控制流

看汇编也就是这里劫持控制流

要控制 channel 的数据,必须得在其第一次 free 了之后占位,我们申请同样大小的内存

我们看看申请的大小是 0xc8

那么只要控制 channel 内存的 0x8C 偏移,劫持 v12 虚函数指针

但是我们要劫持到哪呢,没有信息泄露啊

现在 exp 的一般的做法是内核堆喷射,在 Non-paged Pool 进行堆喷,win7 在这个地址上面是没有 DEP 的,所以直接喷内核 shellcode 就好了,而且 win7 的 Non-paged Pool 的起始地址比较固定,那还好命中一些

一切就绪就可以劫持控制流了

我们也可以看到堆喷射出的 shellcode 有很多

kd> s 86000000 L 2000000 60 e8 00 00 00 00 5b e88688d030  60 e8 00 00 00 00 5b e8-23 00 00 00 b9 76 01 00  `.....[.#....v..8688d088  60 e8 00 00 00 00 5b e8-cb ff ff ff 8b 45 00 83  `.....[......E..8688d430  60 e8 00 00 00 00 5b e8-23 00 00 00 b9 76 01 00  `.....[.#....v..8688d488  60 e8 00 00 00 00 5b e8-cb ff ff ff 8b 45 00 83  `.....[......E..8688d830  60 e8 00 00 00 00 5b e8-23 00 00 00 b9 76 01 00  `.....[.#....v..8688d888  60 e8 00 00 00 00 5b e8-cb ff ff ff 8b 45 00 83  `.....[......E..8688dc30  60 e8 00 00 00 00 5b e8-23 00 00 00 b9 76 01 00  `.....[.#....v..8688dc88  60 e8 00 00 00 00 5b e8-cb ff ff ff 8b 45 00 83  `.....[......E..86892030  60 e8 00 00 00 00 5b e8-23 00 00 00 b9 76 01 00  `.....[.#....v..86892088  60 e8 00 00 00 00 5b e8-cb ff ff ff 8b 45 00 83  `.....[......E..86892430  60 e8 00 00 00 00 5b e8-23 00 00 00 b9 76 01 00  `.....[.#....v..86892488  60 e8 00 00 00 00 5b e8-cb ff ff ff 8b 45 00 83  `.....[......E..86892830  60 e8 00 00 00 00 5b e8-23 00 00 00 b9 76 01 00  `.....[.#....v..86892888  60 e8 00 00 00 00 5b e8-cb ff ff ff 8b 45 00 83  `.....[......E..86892c30  60 e8 00 00 00 00 5b e8-23 00 00 00 b9 76 01 00  `.....[.#....v..86892c88  60 e8 00 00 00 00 5b e8-cb ff ff ff 8b 45 00 83  `.....[......E..868c4030  60 e8 00 00 00 00 5b e8-23 00 00 00 b9 76 01 00  `.....[.#....v..868c4088  60 e8 00 00 00 00 5b e8-cb ff ff ff 8b 45 00 83  `.....[......E..868c4430  60 e8 00 00 00 00 5b e8-23 00 00 00 b9 76 01 00  `.....[.#....v..868c4488  60 e8 00 00 00 00 5b e8-cb ff ff ff 8b 45 00 83  `.....[......E..868c4830  60 e8 00 00 00 00 5b e8-23 00 00 00 b9 76 01 00  `.....[.#....v..

参考

https://github.com/n1xbyte/CVE-2019-0708

https://www.malwaretech.com/2019/05/analysis-of-cve-2019-0708-bluekeep.html

https://www.malwaretech.com/2019/09/bluekeep-a-journey-from-dos-to-rce-cve-2019-0708.html

补充资料

CVE-2019-0708 微软远程桌面服务远程代码执行漏洞分析之补丁分析:

http://www.giantbranch.cn/2019/05/15/CVE-2019-0708%20%E5%BE%AE%E8%BD%AF%E8%BF%9C%E7%A8%8B%E6%A1%8C%E9%9D%A2%E6%9C%8D%E5%8A%A1%E8%BF%9C%E7%A8%8B%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90%E4%B9%8B%E8%A1%A5%E4%B8%81%E5%88%86%E6%9E%90/)

CVE-2019-14378 QEMU 虚拟机逃逸漏洞漏洞

这是 qemu 在网络实现的时候的一个指针错误,当重组大量的 ipv4 分段数据包时会触发错误,漏洞发现者是通过代码审计发现的。

漏洞细节

qemu 的网络有两部分 

1、为虚拟机提供的虚拟网卡(比如 PCI 网卡)

2、与网络接口控制器交互的网络后端(就是将网络数据包给到主机网络)

默认情况下,QEMU 将为 guest 虚拟机创建 SLiRP 用户网络后端和适当的虚拟网卡(例如 e1000 PCI 网卡)

而本漏洞是在 SLiRP 中的数据包重组中出现的错误。

数据包重组那就需要了解 ip 分片,ip 层的结构如下:

 0                   1                   2                   3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+|Version|  IHL  |Type of Service|          Total Length         |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+|         Identification        |Flags|      Fragment Offset    |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+|  Time to Live |    Protocol   |         Header Checksum       |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+|                       Source Address                          |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+|                    Destination Address                        |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+|                    Options                    |    Padding    |+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

分片在 Flags 那里,主要是低 3 个 bit

Bit 0: 保留为, 必须为0Bit 1: (DF) 0 = 分片, 1 = 不分片.Bit 2: (MF) 0 =最后一个ip包, 1 = 后面还有分片Fragment Offset: 13 bits

下面看下相关的结构体

struct mbuf {    /* header at beginning of each mbuf: */    struct mbuf *m_next; /* Linked list of mbufs */    struct mbuf *m_prev;    struct mbuf *m_nextpkt; /* Next packet in queue/record */    struct mbuf *m_prevpkt; /* Flags aren't used in the output queue */    int m_flags; /* Misc flags */
int m_size; /* Size of mbuf, from m_dat or m_ext */ struct socket *m_so;
char *m_data; /* Current location of data */ int m_len; /* Amount of data in this mbuf, from m_data */
...
char *m_ext; /* start of dynamic buffer area, must be last element */ char m_dat[];};

mbuf 是储存接收到的 ip 层的信息。有两个 buffer,一个是 m_dat ,另一个是 m_ext,他是 m_dat 不足以储存的时候,通过在堆上分配内存解决不足的问题

在 nat 转换的时候,如果传入的数据包是分片的,则应在编辑和重新传输之前重新组装它们。这个重组由ip_reass(Slirp *slirp, struct ip *ip, struct ipq *fp)函数完成。ip 包含当前的 IP 数据包数据,fp 是包含分段数据包的链表。

ip_reass 执行以下操作:

1、如果第一个分配的 fp 为 NULL,创建重组队列并将 ip 插入此队列。

2、检查片段是否与先前收到的片段重复,然后丢弃它。

3、如果收到所有分段的数据包,则重新组装它。通过修改第一个数据包的头部为新的 ip header。

/* * Take incoming datagram fragment and try to * reassemble it into whole datagram.  If a chain for * reassembly of this datagram already exists, then it * is given as fp; otherwise have to make a chain. */static struct ip *ip_reass(Slirp *slirp, struct ip *ip, struct ipq *fp){
... ...
/* * Reassembly is complete; concatenate fragments. */ q = fp->frag_link.next; m = dtom(slirp, q);
q = (struct ipasfrag *)q->ipf_next; while (q != (struct ipasfrag *)&fp->frag_link) { struct mbuf *t = dtom(slirp, q); q = (struct ipasfrag *)q->ipf_next; m_cat(m, t); }
/* * Create header for new ip packet by * modifying header of first packet; * dequeue and discard fragment reassembly header. * Make header visible. */ q = fp->frag_link.next;
/* * If the fragments concatenated to an mbuf that's * bigger than the total size of the fragment, then and * m_ext buffer was alloced. But fp->ipq_next points to * the old buffer (in the mbuf), so we must point ip * into the new buffer. */ if (m->m_flags & M_EXT) { int delta = (char *)q - m->m_dat; q = (struct ipasfrag *)(m->m_ext + delta); }

这个漏洞在于计算变量 delta 的值有问题,而上面这个代码假定了第一个分片数据包不会在 external buffer 中分配(m_ext)。 当分片数据在 mbuf-> m_dat(q 将在 m_dat 内)时,计算 q-m-> dat 有效(q 是包含分片链表和数据包数据的结构)。 

否则,如果分配了 m_ext 缓冲区,则 q 将位于 external buffer ,那么 delta 的计算就是错误的。

q 的数据结构是 ipasfrag:就是有一个前向指针跟后向指针,以及包含了一个 ip 头

/* * Ip header, when holding a fragment. * * Note: ipf_link must be at same offset as frag_link above */struct ipasfrag {    struct qlink ipf_link;    struct ip ipf_ip;};
struct qlink { void *next, *prev;};

我们可以调试看看 q 的某个时刻的状态是怎样的

gdb-peda$ p *q$30 = {  ipf_link = {    next = 0x7f9e08084ed0,    prev = 0x7f9e0808487c  },  ipf_ip = {    ip_hl = 0x5,    ip_v = 0x4,    ip_tos = 0x0,    ip_len = 0x8,    ip_id = 0x7f3a,    ip_off = 0x8,    ip_ttl = 0x40,    ip_p = 0x1,    ip_sum = 0x95e3,    ip_src = {      s_addr = 0xf02000a    },    ip_dst = {      s_addr = 0x202000a    }  }}

可以看到确实是两个 ipasfrag 前后指针还有 ip 头信息

我们继续调试运行到下面地方

我们查看下数据结构,可以看到确实此时的 q 在 m_ext 的后面,而 m_dat 在老前面了,那么 q - m->m_dat 就是负数了

gdb-peda$ p q$41 = (struct ipasfrag *) 0x7f9e0808882cgdb-peda$ p *m$42 = {  m_next = 0x7f9e080881a0,  m_prev = 0x7f9e080874c0,  m_nextpkt = 0x0,  m_prevpkt = 0x0,  m_flags = 0xd,  m_size = 0xcde,  m_so = 0x0,  m_data = 0x7f9e08088850 "",  m_len = 0xc98,  slirp = 0x563aa67a6380,  resolution_requested = 0x0,  expiration_date = 0xffffffffffffffff,  m_ext = 0x7f9e08088810 "",  m_dat = 0x7f9e08086eb0 ""}gdb-peda$ p *q$43 = {  ipf_link = {    next = 0x7f9e0808487c,    prev = 0x7f9e08087520  },  ipf_ip = {    ip_hl = 0x5,    ip_v = 0x4,    ip_tos = 0x1,    ip_len = 0xc90,    ip_id = 0x7f3e,    ip_off = 0x0,    ip_ttl = 0x40,    ip_p = 0x1,    ip_sum = 0x1c43,    ip_src = {      s_addr = 0xffffff8b    },    ip_dst = {      s_addr = 0x0    }  }}

简单的示意图如下:(忽略了分配了 m_ext 缓冲区,则 q 将位于 external buffer)


+------------------------------+| || || || || m_dat 0x7f9e08086eb0 || || |+------------------------------+| ||m->m_ext 0x7f9e08088810 || || ||q 0x7f9e0808882c || || || |+------------------------------+

之后,新计算的指针 q 被转换为 ip 结构并且修改部分字段。由于错误地计算了 delta,ip 将指向不正确的位置,并且 ip_src 和 ip_dst 可用于将我们可控的数据写入错误计算的 ip 的位置。如果计算出的 ip 位于没有映射的内存区域,这就会使 qemu 崩溃。

slirp/src/ip_input.c:ip_reass    ip = fragtoip(q);   //转换    ip->ip_len = next;    ip->ip_tos &= ~1;    ip->ip_src = fp->ipq_src;    ip->ip_dst = fp->ipq_dst;

参考

https://blog.bi0s.in/2019/08/24/Pwn/VM-Escape/2019-07-29-qemu-vm-escape-cve-2019-14378/