本文作者:giantbranch(2019 年首次投稿作者)
因为 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 位 旗舰版
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 FALSE
Loading 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
kd> !analyze -v
*******************************************************************************
* *
* Bugcheck Analysis *
* *
*******************************************************************************
IRQL_NOT_LESS_OR_EQUAL (a)
An attempt was made to access a pageable (or completely invalid) address at an
interrupt request level (IRQL) that is too high. This is usually
caused by drivers using improper addresses.
If a kernel debugger is available get the stack backtrace.
Arguments:
Arg1: 00000000, memory referenced
Arg2: 00000002, IRQL
Arg3: 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+87
840ed940 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 = 00000002
eax=00000000 ebx=00000000 ecx=00000000 edx=00000000 esi=841b0280 edi=8a998884
eip=840ed940 esp=90cc6920 ebp=90cc6934 iopl=0 nv up ei pl zr na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010246
nt!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!RtlpBreakWithStatusInstruction
90cc64c4 8412496d 00000003 00000000 840ed940 nt!KiBugCheckDebugBreak+0x1c
90cc688c 8408d7eb 0000000a 00000000 00000002 nt!KeBugCheck2+0x68b
90cc688c 840ed940 0000000a 00000000 00000002 nt!KiTrap0E+0x2cf
90cc6934 90237da2 8a998884 868a7c98 8a998878 nt!ExDeleteResourceLite+0x87
90cc6948 90238060 8a998878 8a998884 868b5670 termdd!_IcaFreeChannel+0x44
90cc6964 9023895f 8a998878 868b5670 00000000 termdd!IcaDereferenceChannel+0x34
90cc69a0 90239354 868b5670 00000005 0000001f termdd!IcaChannelInputInternal+0x3a7
90cc69c8 a60c5dc9 88cc2e24 00000005 0000001f termdd!IcaChannelInput+0x3c
90cc6a00 a60c5e31 a6232008 88cc2e20 88cc2e10 RDPWD!SignalBrokenConnection+0x40
90cc6a18 9023937f a5f73008 00000004 00000000 RDPWD!MCSIcaChannelInput+0x55
90cc6a44 a609d436 88e14884 00000004 00000000 termdd!IcaChannelInput+0x67
90cc726c a609d090 88e14880 88c525a8 840137a0 tssecsrv!CDefaultDataManager::Disconnect+0x3c
90cc72a4 a609ca16 90cc72b4 88e14870 a60a0118 tssecsrv!CFilter::FilterIncomingData+0x222
90cc72d0 9023c772 88c525a8 00000000 868a1cb4 tssecsrv!ScrRawInput+0x60
90cc72f4 a60936a9 868195c4 00000000 868a1cb4 termdd!IcaRawInput+0x5a
90cc7b30 9023b56d 868a1b68 8911f330 8844dc58 tdtcp!TdInputThread+0x34d
90cc7b4c 9023b67c 89073800 00380173 8911f3a0 termdd!_IcaDriverThread+0x53
90cc7b74 9023c00c 8844dc58 8911f330 868b5670 termdd!_IcaStartInputThread+0x6c
90cc7bb4 90239e91 868b5670 8911f330 8911f3a0 termdd!IcaDeviceControlStack+0x50a
90cc7be4 9023a065 8911f330 8911f3a0 88f3ce68 termdd!IcaDeviceControl+0x59
90cc7bfc 840834bc 87c0cbb0 8911f330 8911f330 termdd!IcaDispatch+0x13f
90cc7c14 84284eee 88f3ce68 8911f330 8911f3a0 nt!IofCallDriver+0x63
90cc7c34 842a1cd1 87c0cbb0 88f3ce68 00000000 nt!IopSynchronousServiceTail+0x1f8
90cc7cd0 842a44ac 87c0cbb0 8911f330 00000000 nt!IopXxxControlFile+0x6aa
90cc7d04 8408a42a 0000096c 00000000 00000000 nt!NtDeviceIoControlFile+0x2a
90cc7d04 776b64f4 0000096c 00000000 00000000 nt!KiFastCallEntry+0x12a
031cfc4c 776b4cac 6f5b18a7 0000096c 00000000 ntdll!KiFastSystemCallRet
031cfc50 6f5b18a7 0000096c 00000000 00000000 ntdll!NtDeviceIoControlFile+0xc
031cfc8c 6f5b25e9 0000096c 00380173 0037f0e0 ICAAPI!IcaIoControl+0x29
031cfcbc 77811174 80000000 031cfd08 776cb3f5 ICAAPI!IcaInputThreadUserMode+0x37
031cfcc8 776cb3f5 0037f0d8 74694ddb 00000000 kernel32!BaseThreadInitThunk+0xe
031cfd08 776cb3c8 6f5b25b2 0037f0d8 00000000 ntdll!__RtlUserThreadStart+0x70
031cfd20 00000000 6f5b25b2 0037f0d8 00000000 ntdll!_RtlUserThreadStart+0x1b
STACK_COMMAND: kb
FOLLOWUP_IP:
termdd!_IcaFreeChannel+44
90237da2 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 了
在IcaRebindVirtualChannels
和IcaBindVirtualChannels
中我们都可以看到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);
else
LABEL_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 FALSE
Loading 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> g
AllocateChannel 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+0x63
a52c9b60 842681d7 87c0cbb0 a57ff6e0 87bf5858 nt!IopParseDevice+0xed7
a52c9bdc 8428e24d 00000000 a52c9c30 00000040 nt!ObpLookupObjectName+0x4fa
a52c9c38 842865ab 013ae714 867ff6e0 a52c9c01 nt!ObOpenObjectByName+0x159
a52c9cb4 84291eb6 03251114 c0100000 013ae714 nt!IopCreateFile+0x673
a52c9d00 8408a42a 03251114 c0100000 013ae714 nt!NtCreateFile+0x34
a52c9d00 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+0x63
a52c9c34 842a1cd1 87c0cbb0 89187758 00000000 nt!IopSynchronousServiceTail+0x1f8
a52c9cd0 842a44ac 87c0cbb0 88f17708 00000000 nt!IopXxxControlFile+0x6aa
a52c9d04 8408a42a 00000840 00000000 00000000 nt!NtDeviceIoControlFile+0x2a
a52c9d04 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+0x94
88fd57cc "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+0x63
a52c9b60 842681d7 87c0cbb0 a57ff6e0 87bf5858 nt!IopParseDevice+0xed7
a52c9bdc 8428e24d 00000000 a52c9c30 00000040 nt!ObpLookupObjectName+0x4fa
a52c9c38 842865ab 013ae714 867ff6e0 a52c9c01 nt!ObOpenObjectByName+0x159
a52c9cb4 84291eb6 03251114 c0100000 013ae714 nt!IopCreateFile+0x673
a52c9d00 8408a42a 03251114 c0100000 013ae714 nt!NtCreateFile+0x34
a52c9d00 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+0x63
a52c9c34 842a1cd1 87c0cbb0 89187758 00000000 nt!IopSynchronousServiceTail+0x1f8
a52c9cd0 842a44ac 87c0cbb0 88f17708 00000000 nt!IopXxxControlFile+0x6aa
a52c9d04 8408a42a 00000840 00000000 00000000 nt!NtDeviceIoControlFile+0x2a
a52c9d04 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+0x63
8e654c34 842a1cd1 87c0cbb0 890a1360 00000000 nt!IopSynchronousServiceTail+0x1f8
8e654cd0 842a44ac 87c0cbb0 88580158 00000000 nt!IopXxxControlFile+0x6aa
8e654d04 8408a42a 000007f4 00000000 00000000 nt!NtDeviceIoControlFile+0x2a
8e654d04 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+0x63
8e654c34 842a1cd1 87c0cbb0 890a1360 00000000 nt!IopSynchronousServiceTail+0x1f8
8e654cd0 842a44ac 87c0cbb0 88580158 00000000 nt!IopXxxControlFile+0x6aa
8e654d04 8408a42a 000007f4 00000000 00000000 nt!NtDeviceIoControlFile+0x2a
8e654d04 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 e8
8688d030 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/)
这是 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: 保留为, 必须为0
Bit 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 *) 0x7f9e0808882c
gdb-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/