关于该漏洞,网上并没有什么详细的分析文章,大多所述都非常浅显,说root cause是HandleAttachUserReq函数处理错误,但是经过我的分析,发现他们的结论是错误的……

1. overview

CVE-2012-0002是windows下RDP协议的UAF漏洞,内核在处理T.125 ConnectMCSPDU包的maxChannelIds字段的时候,如果该字段被设置为小于等于5,则会触发该漏洞

2. Analyze

该报告所使用的环境为:windows 7 sp1 x86,rdpwd.sys的版本为:6.1.7601.17514,调试工具为:WinDbg Preview

a. Poc

poc详见cve-2012-0002.py,于linux下使用python2运行

该poc只发送了一个 MCS Attach User Request PDU包,并设置了其中的maxChannelIds的值为5,正是该值引发了漏洞

b. Root Cause

以下分析全部发生于RDPWD模块中

WDLIB_MCSIcaRawInput函数处理了poc发送的包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 # ChildEBP RetAddr      Args to Child              
00 a2951174 9066995a 967c7008 00000000 87b376a4 RDPWD!WDLIB_MCSIcaRawInput
01 a2951198 99df3a31 87b74154 00000000 87b376a4 termdd!IcaRawInput+0x5a
02 a29511d0 9066995a 87ba3300 00000000 87b376a4 tssecsrv!ScrRawInput+0x7b
03 a29511f4 99de96a9 87b75e7c 00000000 87b376a4 termdd!IcaRawInput+0x5a
04 a2951a30 90668671 87b37558 00000008 87b8b420 tdtcp!TdInputThread+0x34d
05 a2951a4c 90668780 879ce008 00380173 87938a40 termdd!_IcaDriverThread+0x53
06 a2951a74 9066922f 87b8b420 879389d0 8696a668 termdd!_IcaStartInputThread+0x6c
07 a2951ab4 90666f9f 8696a668 879389d0 87938a40 termdd!IcaDeviceControlStack+0x629
08 a2951ae4 90667173 879389d0 87938a40 87b46ba0 termdd!IcaDeviceControl+0x59
09 a2951afc 83c48593 86a356a8 879389d0 879389d0 termdd!IcaDispatch+0x13f
0a a2951b14 83e3c99f 87b46ba0 879389d0 87938a40 nt!IofCallDriver+0x63
0b a2951b34 83e3fb71 86a356a8 87b46ba0 00000000 nt!IopSynchronousServiceTail+0x1f8
0c a2951bd0 83e863f4 86a356a8 879389d0 00000000 nt!IopXxxControlFile+0x6aa
0d a2951c04 83c4f1ea 000003dc 00000000 00000000 nt!NtDeviceIoControlFile+0x2a
0e a2951c04 779070b4 (T) 000003dc 00000000 00000000 nt!KiFastCallEntry+0x12a
0f 0236f968 77905864 (T) 6edc1948 000003dc 00000000 ntdll!KiFastSystemCallRet
10 0236f96c 6edc1948 000003dc 00000000 00000000 ntdll!ZwDeviceIoControlFile+0xc
WARNING: Frame IP not in any known module. Following frames may be wrong.
11 0236f9a8 6edc25f1 000003dc 00380173 001cfd80 0x6edc1948
12 0236f9d8 77a63c45 80000000 0236fa24 779237f5 0x6edc25f1
13 0236f9e4 779237f5 001cfd78 75afa2ce 00000000 kernel32!BaseThreadInitThunk+0xe
14 0236fa24 779237c8 6edc25ba 001cfd78 00000000 ntdll!__RtlUserThreadStart+0x70
15 0236fa3c 00000000 6edc25ba 001cfd78 00000000 ntdll!_RtlUserThreadStart+0x1b

maxChannelIds字段,按照字面意思来看,意为最大通道ID值,或者最多通道ID数目,在poc的包中其被定义为5,那么就意味着只能创建5个通道

WDLIB_MCSIcaRawInput函数会将包中的配置存入一个全局的结构体中,该结构体的部分字段:

offset size origin value description
34 4 0 已分配的ID数目
A8 4 5 maxChannelIds
C8 4 3ea ID值

WDLIB_MCSIcaRawInput中相调用了MCSIcaRawInputWorker之后,在MCSIcaRawInputWorker中会调用RecognizeMCSFrame来识别当前的包的类别,然后根据类别调用相应的处理函数

而在本次调用中,其对应的处理函数为HandleAttachUserReq,用来处理Attach User Request

该函数再调用MCSAttachUserRequest函数,来对该请求做实际的处理

MCSAttachUserRequest函数中,有GetNewDynamicChannel函数:

1
2
3
4
5
6
int __stdcall GetNewDynamicChannel(_DWORD *a1)
{
if ( a1[0xD] >= a1[0x2A] )
return 0;
return ++a1[0x32] - 1;
}

其中0xD,0x2A,0x32分别对应偏移0x34,0xA8,0xC8

这就可以看出,该函数的作用实际上就是根据当前存在的Channel ID的数目来返回ID值,但是这里要注意一点,如果已分配的ID数目大于等于maxChannelIds,那么该函数就会返回0

就实际上来说,偏移0x34的位置是一个SList结构体,将其作为第一个参数调用SListAppend,如果函数执行成功,偏移0x34会增1

在这次的MCSAttachUserRequest中,偏移0x34增1,代表已经分出了一个通道ID

在之后的处理流程中,WDSYS_Ioctl的调用号为382403的时候,会调用WDLIB_TShareConfConnect函数,在该函数中便会一路执行到NM_Connect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
 # ChildEBP RetAddr      Args to Child              
00 a283797c 8cbbc26e 83247ae0 8684c134 832475ce RDPWD!NM_Connect
01 a283799c 8cbba296 832477f4 8684c128 8684c134 RDPWD!SM_Connect+0x11d
02 a28379d8 8cbbaa96 832471d8 8684c044 8684c128 RDPWD!WDWConnect+0x557
03 a2837a14 8cbb5af2 832471d8 00000000 86a642fc RDPWD!WDLIB_TShareConfConnect+0xa0
04 a2837a30 906686f5 832471d8 a2837a8c 86a642f8 RDPWD!WDSYS_Ioctl+0x79c
05 a2837a4c 90668bad 87b9fc38 00000005 a2837a8c termdd!_IcaCallSd+0x37
06 a2837a6c 90669109 86a642f0 00000005 a2837a8c termdd!_IcaCallStack+0x57
07 a2837ab4 90666f9f 86a642f0 876f17e0 876f1850 termdd!IcaDeviceControlStack+0x503
08 a2837ae4 90667173 876f17e0 876f1850 879a42b0 termdd!IcaDeviceControl+0x59
09 a2837afc 83c48593 86a356a8 876f17e0 876f17e0 termdd!IcaDispatch+0x13f
0a a2837b14 83e3c99f 879a42b0 876f17e0 876f1850 nt!IofCallDriver+0x63
0b a2837b34 83e3fb71 86a356a8 879a42b0 00000000 nt!IopSynchronousServiceTail+0x1f8
0c a2837bd0 83e863f4 86a356a8 876f17e0 00000000 nt!IopXxxControlFile+0x6aa
0d a2837c04 83c4f1ea 00000860 00000000 00000000 nt!NtDeviceIoControlFile+0x2a
0e a2837c04 779070b4 (T) 00000860 00000000 00000000 nt!KiFastCallEntry+0x12a
0f 0147e4f8 77905864 (T) 6edc1948 00000860 00000000 ntdll!KiFastSystemCallRet
10 0147e4fc 6edc1948 00000860 00000000 00000000 ntdll!ZwDeviceIoControlFile+0xc
11 0147e538 6edc1bc6 00000860 00382403 03591928 ICAAPI!IcaIoControl+0x29
12 0147e564 6edc1b76 00191330 00382403 03591928 ICAAPI!_IcaStackIoControlWorker+0x64
13 0147e58c 6e9d50fe 00191330 00382403 03591928 ICAAPI!IcaStackIoControl+0x29
14 0147e5c4 6e9d532c 00191330 00000000 0147e614 rdpwsx!TSrvInitWDConnectInfo+0x6a
15 0147e5ec 6e9d53c1 035907d8 0147e614 00000000 rdpwsx!TSrvInitWD+0x23
16 0147e60c 6e9d54b1 00000000 00000000 035907d8 rdpwsx!TSrvConfCreateResp+0x26
17 0147e620 6e9d559d 035907d8 0147e680 00000000 rdpwsx!TSrvDoConnectResponse+0x10
18 0147e64c 6e9d55fe 035907d8 0000028c 01f50c60 rdpwsx!TSrvDoConnect+0xb6
19 0147e660 6e9d44da 0000028c 00191330 0147e680 rdpwsx!TSrvStackConnect+0x33
1a 0147e684 6e9ffaf7 01f50c60 0000028c 00191330 rdpwsx!WsxIcaStackIoControl+0x19b
1b 0147e6b0 6e9fac07 0000028c 00191330 0038004b rdpcorekmts!CWsx::StackIoControl+0x2f
1c 0147e6d8 6edc1f28 011bf480 00191330 0038004b rdpcorekmts!CStack::staticExtensionIoControl+0x32
1d 0147e708 6edc28f5 00191330 0038004b 00000000 ICAAPI!_IcaStackIoControl+0x33
1e 0147ecf0 6edc3264 00191330 011b9c00 0147ed37 ICAAPI!_IcaStackWaitForIca+0x40
1f 0147f2f8 6e9fe28e 0000028c 00191330 011b9bbc ICAAPI!IcaStackConnectionAccept+0x19d
20 0147f5cc 6e9ff482 011b9bbc 011b9c00 011bdd64 rdpcorekmts!CStack::Accept+0x69
21 0147f600 6ee30997 011b9b80 011afdb0 6ee31af0 rdpcorekmts!CKMRDPConnection::AcceptConnection+0xbf
22 0147f770 6ee31ab7 0119ed94 0119ed88 0119ed98 termsrv!CConnectionEx::Accept+0x20e
23 0147f784 6ee31def 011afdb0 00000000 00000000 termsrv!CListenerEx::TransferWorkItem+0x21
24 0147f7a0 77a63c45 0119ed88 0147f7ec 779237f5 termsrv!CListenerEx::staticTransferWorkItem+0x1c
25 0147f7ac 779237f5 0119ed88 76deaf06 00000000 kernel32!BaseThreadInitThunk+0xe
26 0147f7ec 779237c8 6ee31dd3 0119ed88 00000000 ntdll!__RtlUserThreadStart+0x70
27 0147f804 00000000 6ee31dd3 0119ed88 00000000 ntdll!_RtlUserThreadStart+0x1b

该函数目测是建立连接的相关函数,在此函数中会依次按顺序调用这几个函数(只显示关键函数):

1
2
3
MCSAttachUserRequest
MCSChannelJoinRequest
MCSChannelJoinRequest

MCSAttachUserRequest函数中,在这段代码处:

1
2
3
4
5
6
7
8
if ( !v9 )
{
v12 = WDLIBRT_MemAlloc(0x4Cu, 0x636D5354u); // 分配
v9 = v12;
if ( !v12 )
return 11;
v12[5] = 0;
}

此时,偏移0x34所存的值是1,而且这里分配出来的内存就是UAF所发生在的堆块

MCSAttachUserRequestMCSChannelJoinRequest都对前面所述的结构体的偏移0x34调用了SListAppend,所以执行完这三个函数,偏移0x34所存的值加了3,现在为4

然后会进入一个循环:

1
2
3
4
5
6
7
8
9
10
11
12
13
do
{
if ( Src != (struct tagRNS_UD_CS_NET *)7 ) //Src最开始是1
{
if ( MCSChannelJoinRequest(*((_DWORD *)v3 + 3), 0, (int)&v24, (int)&a1 + 3) )
goto LABEL_5;
v17 = MCSGetChannelIDFromHandle(v24);
*(_WORD *)v25 = v17;
}
Src = (struct tagRNS_UD_CS_NET *)((char *)Src + 1);
v25 += 36;
}
while ( (unsigned int)Src < *((_DWORD *)v3 + 12) ); //最开始是3

而在MCSChannelJoinRequest中,会按顺序调用这些函数(只显示关键函数):

1
2
GetNewDynamicChannel	//参数为上述结构体
SListAppend //参数为上述结构体偏移0x34

那么这就意味着,每执行一次MCSChannelJoinRequest,偏移0x34所存的值就会加1

而这个循环只会执行两轮,第一轮执行完之后偏移0x34的值变为5,第二轮执行的时候,由于GetNewDynamicChannel中的if判断成立了,导致GetNewDynamicChannel返回了0

MCSChannelJoinRequest中,GetNewDynamicChannel返回0会导致该函数直接返回15

1
2
3
v5 = (PVOID)GetNewDynamicChannel(*v4);
if ( !v5 )
return 15;

那么就会执行后面的goto LABEL_5

1
2
3
4
5
6
7
8
9
10
11
LABEL_5:
NMAbortConnect((struct tagNM_HANDLE_DATA *)v3);
goto LABEL_6;

......

LABEL_6:
if ( v4 )
WDLIBRT_MemFree(v4);
return v23;
}

然后执行NMAbortConnect函数,最关键的问题也出在这里

1
2
3
4
5
6
void __stdcall NMAbortConnect(struct tagNM_HANDLE_DATA *a1)
{
if ( *((_BYTE *)a1 + 0x1C) & 1 )
NMDetachUserReq(a1);
SM_OnConnected(*(_DWORD *)a1, 0, 1, 0, 0);
}

这里取了a1+0x1c的一个字节,看其最后一位是不是1,如果是的话,则调用NMDetachUserReq

而a1+0x1c的赋值就位于NM_Connect中:

1
2
3
4
5
6
7
8
9
10
11
*((_DWORD *)v3 + 7) |= 1u;			//这里
*((_DWORD *)v3 + 8) = v15;
if ( MCSChannelJoinRequest(*((_DWORD *)v3 + 3), 0, (int)&v24, (int)&a1 + 3) )
goto LABEL_5;
v16 = MCSGetChannelIDFromHandle(v24);
*((_DWORD *)v3 + 7) |= 4u; //这里
*((_DWORD *)v3 + 4) = v16;
*((_DWORD *)v3 + 5) = v24;
if ( MCSChannelJoinRequest(*((_DWORD *)v3 + 3), *((PVOID *)v3 + 8), (int)&v24, (int)&a1 + 3) )
goto LABEL_5;
*((_DWORD *)v3 + 7) |= 2u; //这里

所以执行NMAbortConnect的时候,其最后一个字节是1,因此这里会调用NMDetachUserReq函数,且该函数的参数为上文提到的UAF的堆块

进入执行流程后,会执行到这样的路径:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
NMDetachUserReq -> MCSDetachUserRequest -> DetachUser

# ChildEBP RetAddr Args to Child
00 959218f4 8cbd5762 967ac3f0 909db960 00000003 RDPWD!DetachUser
01 95921918 8cbc1a68 909db960 00000010 83247ae0 RDPWD!MCSDetachUserRequest+0x6a
02 9592192c 8cbc1a9a 83247ae0 967ef8c0 9592197c RDPWD!NMDetachUserReq+0x14
03 9592193c 8cbbc805 83247ae0 00000002 832477f4 RDPWD!NMAbortConnect+0x15
04 9592197c 8cbbc26e 00247ae0 00000002 832475ce RDPWD!NM_Connect+0x68
05 9592199c 8cbba296 832477f4 86b5ac38 86b5ac44 RDPWD!SM_Connect+0x11d
06 959219d8 8cbbaa96 832471d8 86b5ab54 86b5ac38 RDPWD!WDWConnect+0x557
07 95921a14 8cbb5af2 832471d8 00000000 86967674 RDPWD!WDLIB_TShareConfConnect+0xa0
08 95921a30 906686f5 832471d8 95921a8c 86967670 RDPWD!WDSYS_Ioctl+0x79c
09 95921a4c 90668bad 87c23928 00000005 95921a8c termdd!_IcaCallSd+0x37
0a 95921a6c 90669109 86967668 00000005 95921a8c termdd!_IcaCallStack+0x57
0b 95921ab4 90666f9f 86967668 863120a8 86312118 termdd!IcaDeviceControlStack+0x503
0c 95921ae4 90667173 863120a8 86312118 87b9e288 termdd!IcaDeviceControl+0x59
0d 95921afc 83c48593 86a356a8 863120a8 863120a8 termdd!IcaDispatch+0x13f
0e 95921b14 83e3c99f 87b9e288 863120a8 86312118 nt!IofCallDriver+0x63
0f 95921b34 83e3fb71 86a356a8 87b9e288 00000000 nt!IopSynchronousServiceTail+0x1f8
10 95921bd0 83e863f4 86a356a8 863120a8 00000000 nt!IopXxxControlFile+0x6aa
11 95921c04 83c4f1ea 00000670 00000000 00000000 nt!NtDeviceIoControlFile+0x2a
12 95921c04 779070b4 (T) 00000670 00000000 00000000 nt!KiFastCallEntry+0x12a
13 0188ebf0 77905864 (T) 6edc1948 00000670 00000000 ntdll!KiFastSystemCallRet
14 0188ebf4 6edc1948 00000670 00000000 00000000 ntdll!ZwDeviceIoControlFile+0xc
15 0188ec30 6edc1bc6 00000670 00382403 03591928 ICAAPI!IcaIoControl+0x29
16 0188ec5c 6edc1b76 001910b0 00382403 03591928 ICAAPI!_IcaStackIoControlWorker+0x64
17 0188ec84 6e9d50fe 001910b0 00382403 03591928 ICAAPI!IcaStackIoControl+0x29
18 0188ecbc 6e9d532c 001910b0 00000000 0188ed0c rdpwsx!TSrvInitWDConnectInfo+0x6a
19 0188ece4 6e9d53c1 035907d8 0188ed0c 00000000 rdpwsx!TSrvInitWD+0x23
1a 0188ed04 6e9d54b1 00000000 00000000 035907d8 rdpwsx!TSrvConfCreateResp+0x26
1b 0188ed18 6e9d559d 035907d8 0188ed78 00000000 rdpwsx!TSrvDoConnectResponse+0x10
1c 0188ed44 6e9d55fe 035907d8 0000080c 01f50e60 rdpwsx!TSrvDoConnect+0xb6
1d 0188ed58 6e9d44da 0000080c 001910b0 0188ed78 rdpwsx!TSrvStackConnect+0x33
1e 0188ed7c 6e9ffaf7 01f50e60 0000080c 001910b0 rdpwsx!WsxIcaStackIoControl+0x19b
1f 0188eda8 6e9fac07 0000080c 001910b0 0038004b rdpcorekmts!CWsx::StackIoControl+0x2f
20 0188edd0 6edc1f28 011be760 001910b0 0038004b rdpcorekmts!CStack::staticExtensionIoControl+0x32
21 0188ee00 6edc28f5 001910b0 0038004b 00000000 ICAAPI!_IcaStackIoControl+0x33
22 0188f3e8 6edc3264 001910b0 011afe30 0188f42f ICAAPI!_IcaStackWaitForIca+0x40
23 0188f9f0 6e9fe28e 0000080c 001910b0 011afdec ICAAPI!IcaStackConnectionAccept+0x19d
24 0188fcc4 6e9ff482 011afdec 011afe30 011b3f94 rdpcorekmts!CStack::Accept+0x69
25 0188fcf8 6ee30997 011afdb0 011b42f0 6ee31af0 rdpcorekmts!CKMRDPConnection::AcceptConnection+0xbf
26 0188fe68 6ee31ab7 0119ed94 0119ed88 0119ed98 termsrv!CConnectionEx::Accept+0x20e
27 0188fe7c 6ee31def 011b42f0 00000000 00000000 termsrv!CListenerEx::TransferWorkItem+0x21
28 0188fe98 77a63c45 0119ed88 0188fee4 779237f5 termsrv!CListenerEx::staticTransferWorkItem+0x1c
29 0188fea4 779237f5 0119ed88 7611a60e 00000000 kernel32!BaseThreadInitThunk+0xe
2a 0188fee4 779237c8 6ee31dd3 0119ed88 00000000 ntdll!__RtlUserThreadStart+0x70
2b 0188fefc 00000000 6ee31dd3 0119ed88 00000000 ntdll!_RtlUserThreadStart+0x1b

DetachUser的第二个参数就是上文提到的分配出来的出现UAF的堆块,在该函数的最后几句代码被释放:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
LABEL_14:
SListRemove(a1 + 13, v5[3], &P);
if ( P )
{
SListDestroy((int)P);
if ( *((_BYTE *)P + 56) )
*((_BYTE *)P + 57) = 0;
else
WDLIBRT_MemFree(P);
}
SListDestroy((int)v11 + 0x10);
if ( *((_BYTE *)v11 + 5) )
*((_BYTE *)v11 + 6) = 0;
else
WDLIBRT_MemFree(v11); // 释放
return 0;
}

在执行完NMDetachUserReq之后,紧接着便会执行SM_OnConnected函数,进入执行流程后,会执行到这样的路径:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
SM_OnConnected -> SM_Disconnect -> NM_Disconnect

# ChildEBP RetAddr Args to Child
00 95921900 8cbbb99e 83247ae0 9592191c 8cbc1b67 RDPWD!NM_Disconnect
01 9592190c 8cbc1b67 832477f4 83247ae0 9592193c RDPWD!SM_Disconnect+0x27
02 9592191c 8cbc1aa8 832477f4 00000000 00000001 RDPWD!SM_OnConnected+0x70
03 9592193c 8cbbc805 83247ae0 00000002 832477f4 RDPWD!NMAbortConnect+0x23
04 9592197c 8cbbc26e 00247ae0 00000002 832475ce RDPWD!NM_Connect+0x68
05 9592199c 8cbba296 832477f4 86b5ac38 86b5ac44 RDPWD!SM_Connect+0x11d
06 959219d8 8cbbaa96 832471d8 86b5ab54 86b5ac38 RDPWD!WDWConnect+0x557
07 95921a14 8cbb5af2 832471d8 00000000 86967674 RDPWD!WDLIB_TShareConfConnect+0xa0
08 95921a30 906686f5 832471d8 95921a8c 86967670 RDPWD!WDSYS_Ioctl+0x79c
09 95921a4c 90668bad 87c23928 00000005 95921a8c termdd!_IcaCallSd+0x37
0a 95921a6c 90669109 86967668 00000005 95921a8c termdd!_IcaCallStack+0x57
0b 95921ab4 90666f9f 86967668 863120a8 86312118 termdd!IcaDeviceControlStack+0x503
0c 95921ae4 90667173 863120a8 86312118 87b9e288 termdd!IcaDeviceControl+0x59
0d 95921afc 83c48593 86a356a8 863120a8 863120a8 termdd!IcaDispatch+0x13f
0e 95921b14 83e3c99f 87b9e288 863120a8 86312118 nt!IofCallDriver+0x63
0f 95921b34 83e3fb71 86a356a8 87b9e288 00000000 nt!IopSynchronousServiceTail+0x1f8
10 95921bd0 83e863f4 86a356a8 863120a8 00000000 nt!IopXxxControlFile+0x6aa
11 95921c04 83c4f1ea 00000670 00000000 00000000 nt!NtDeviceIoControlFile+0x2a
12 95921c04 779070b4 (T) 00000670 00000000 00000000 nt!KiFastCallEntry+0x12a
13 0188ebf0 77905864 (T) 6edc1948 00000670 00000000 ntdll!KiFastSystemCallRet
14 0188ebf4 6edc1948 00000670 00000000 00000000 ntdll!ZwDeviceIoControlFile+0xc
15 0188ec30 6edc1bc6 00000670 00382403 03591928 ICAAPI!IcaIoControl+0x29
16 0188ec5c 6edc1b76 001910b0 00382403 03591928 ICAAPI!_IcaStackIoControlWorker+0x64
17 0188ec84 6e9d50fe 001910b0 00382403 03591928 ICAAPI!IcaStackIoControl+0x29
18 0188ecbc 6e9d532c 001910b0 00000000 0188ed0c rdpwsx!TSrvInitWDConnectInfo+0x6a
19 0188ece4 6e9d53c1 035907d8 0188ed0c 00000000 rdpwsx!TSrvInitWD+0x23
1a 0188ed04 6e9d54b1 00000000 00000000 035907d8 rdpwsx!TSrvConfCreateResp+0x26
1b 0188ed18 6e9d559d 035907d8 0188ed78 00000000 rdpwsx!TSrvDoConnectResponse+0x10
1c 0188ed44 6e9d55fe 035907d8 0000080c 01f50e60 rdpwsx!TSrvDoConnect+0xb6
1d 0188ed58 6e9d44da 0000080c 001910b0 0188ed78 rdpwsx!TSrvStackConnect+0x33
1e 0188ed7c 6e9ffaf7 01f50e60 0000080c 001910b0 rdpwsx!WsxIcaStackIoControl+0x19b
1f 0188eda8 6e9fac07 0000080c 001910b0 0038004b rdpcorekmts!CWsx::StackIoControl+0x2f
20 0188edd0 6edc1f28 011be760 001910b0 0038004b rdpcorekmts!CStack::staticExtensionIoControl+0x32
21 0188ee00 6edc28f5 001910b0 0038004b 00000000 ICAAPI!_IcaStackIoControl+0x33
22 0188f3e8 6edc3264 001910b0 011afe30 0188f42f ICAAPI!_IcaStackWaitForIca+0x40
23 0188f9f0 6e9fe28e 0000080c 001910b0 011afdec ICAAPI!IcaStackConnectionAccept+0x19d
24 0188fcc4 6e9ff482 011afdec 011afe30 011b3f94 rdpcorekmts!CStack::Accept+0x69
25 0188fcf8 6ee30997 011afdb0 011b42f0 6ee31af0 rdpcorekmts!CKMRDPConnection::AcceptConnection+0xbf
26 0188fe68 6ee31ab7 0119ed94 0119ed88 0119ed98 termsrv!CConnectionEx::Accept+0x20e
27 0188fe7c 6ee31def 011b42f0 00000000 00000000 termsrv!CListenerEx::TransferWorkItem+0x21
28 0188fe98 77a63c45 0119ed88 0188fee4 779237f5 termsrv!CListenerEx::staticTransferWorkItem+0x1c
29 0188fea4 779237f5 0119ed88 7611a60e 00000000 kernel32!BaseThreadInitThunk+0xe
2a 0188fee4 779237c8 6ee31dd3 0119ed88 00000000 ntdll!__RtlUserThreadStart+0x70
2b 0188fefc 00000000 6ee31dd3 0119ed88 00000000 ntdll!_RtlUserThreadStart+0x1b

NM_Disconnect函数是这样的:

1
2
3
4
5
6
7
8
9
int __stdcall NM_Disconnect(struct tagNM_HANDLE_DATA *a1)
{
int result; // eax

result = 1;
if ( *((_BYTE *)a1 + 0x1C) & 1 )
result = NMDetachUserReq(a1);
return result;
}

NMAbortConnect非常相似,而且,在这里a1+0x1c依旧是7,即这一个字节的最后一位依旧是1

从调用栈中我们可以看到,NMAbortConnect的第一个参数是83247ae0,同样也是它里面的NMDetachUserReq的参数,而NM_Disconnect的第一个参数也是83247ae0,其同样也是它里面的NMDetachUserReq的参数

这里其实就可以发现问题了:在MCSAttachUserRequest分配出来的堆,在DetachUser被释放了之后,又在NM_Disconnect被使用了!典型的UAF

再继续运行下去的话,便会崩溃(这里是另一次调试的结果,call stack中参数与上不符):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
Access violation - code c0000005 (!!! second chance !!!)
RDPWD!MCSDetachUserRequest+0x14:
8cbd570c 8b11 mov edx,dword ptr [ecx]
0: kd> kb
# ChildEBP RetAddr Args to Child
00 8bff78e0 8cbc1a68 a2095f50 00000010 8326c624 RDPWD!MCSDetachUserRequest+0x14
01 8bff78f4 8cbbc431 8326c910 8bff790c 8cbbb99e RDPWD!NMDetachUserReq+0x14
02 8bff7900 8cbbb99e 8326c910 8bff791c 8cbc1b67 RDPWD!NM_Disconnect+0x16
03 8bff790c 8cbc1b67 8326c624 8326c910 8bff793c RDPWD!SM_Disconnect+0x27
04 8bff791c 8cbc1aa8 8326c624 00000000 00000001 RDPWD!SM_OnConnected+0x70
05 8bff793c 8cbbc805 8326c910 00000002 8326c624 RDPWD!NMAbortConnect+0x23
06 8bff797c 8cbbc26e 0026c910 00000002 8326c3fe RDPWD!NM_Connect+0x68
07 8bff799c 8cbba296 8326c624 864b1128 864b1134 RDPWD!SM_Connect+0x11d
08 8bff79d8 8cbbaa96 8326c008 864b1044 864b1128 RDPWD!WDWConnect+0x557
09 8bff7a14 8cbb5af2 8326c008 00000000 86967674 RDPWD!WDLIB_TShareConfConnect+0xa0
0a 8bff7a30 906686f5 8326c008 8bff7a8c 86967670 RDPWD!WDSYS_Ioctl+0x79c
0b 8bff7a4c 90668bad 86b67f58 00000005 8bff7a8c termdd!_IcaCallSd+0x37
0c 8bff7a6c 90669109 86967668 00000005 8bff7a8c termdd!_IcaCallStack+0x57
0d 8bff7ab4 90666f9f 86967668 87b9f088 87b9f0f8 termdd!IcaDeviceControlStack+0x503
0e 8bff7ae4 90667173 87b9f088 87b9f0f8 87bc8a00 termdd!IcaDeviceControl+0x59
0f 8bff7afc 83c48593 86a356a8 87b9f088 87b9f088 termdd!IcaDispatch+0x13f
10 8bff7b14 83e3c99f 87bc8a00 87b9f088 87b9f0f8 nt!IofCallDriver+0x63
11 8bff7b34 83e3fb71 86a356a8 87bc8a00 00000000 nt!IopSynchronousServiceTail+0x1f8
12 8bff7bd0 83e863f4 86a356a8 87b9f088 00000000 nt!IopXxxControlFile+0x6aa
13 8bff7c04 83c4f1ea 000003e0 00000000 00000000 nt!NtDeviceIoControlFile+0x2a
14 8bff7c04 779070b4 (T) 000003e0 00000000 00000000 nt!KiFastCallEntry+0x12a
15 0193ea08 77905864 (T) 6edc1948 000003e0 00000000 ntdll!KiFastSystemCallRet
16 0193ea0c 6edc1948 000003e0 00000000 00000000 ntdll!ZwDeviceIoControlFile+0xc
17 0193ea48 6edc1bc6 000003e0 00382403 03591bb8 ICAAPI!IcaIoControl+0x29
18 0193ea74 6edc1b76 001910b0 00382403 03591bb8 ICAAPI!_IcaStackIoControlWorker+0x64
19 0193ea9c 6e9d50fe 001910b0 00382403 03591bb8 ICAAPI!IcaStackIoControl+0x29
1a 0193ead4 6e9d532c 001910b0 00000000 0193eb24 rdpwsx!TSrvInitWDConnectInfo+0x6a
1b 0193eafc 6e9d53c1 03591a88 0193eb24 00000000 rdpwsx!TSrvInitWD+0x23
1c 0193eb1c 6e9d54b1 00000000 00000000 03591a88 rdpwsx!TSrvConfCreateResp+0x26
1d 0193eb30 6e9d559d 03591a88 0193eb90 00000000 rdpwsx!TSrvDoConnectResponse+0x10
1e 0193eb5c 6e9d55fe 03591a88 00000544 01f50e60 rdpwsx!TSrvDoConnect+0xb6
1f 0193eb70 6e9d44da 00000544 001910b0 0193eb90 rdpwsx!TSrvStackConnect+0x33
20 0193eb94 6e9ffaf7 01f50e60 00000544 001910b0 rdpwsx!WsxIcaStackIoControl+0x19b
21 0193ebc0 6e9fac07 00000544 001910b0 0038004b rdpcorekmts!CWsx::StackIoControl+0x2f
22 0193ebe8 6edc1f28 011bedf0 001910b0 0038004b rdpcorekmts!CStack::staticExtensionIoControl+0x32
23 0193ec18 6edc28f5 001910b0 0038004b 00000000 ICAAPI!_IcaStackIoControl+0x33
24 0193f200 6edc3264 001910b0 011b56c0 0193f247 ICAAPI!_IcaStackWaitForIca+0x40
25 0193f808 6e9fe28e 00000544 001910b0 011b567c ICAAPI!IcaStackConnectionAccept+0x19d
26 0193fadc 6e9ff482 011b567c 011b56c0 011b9824 rdpcorekmts!CStack::Accept+0x69
27 0193fb10 6ee30997 011b5640 011afdb0 6ee31af0 rdpcorekmts!CKMRDPConnection::AcceptConnection+0xbf
28 0193fc80 6ee31ab7 0119ed94 0119ed88 0119ed98 termsrv!CConnectionEx::Accept+0x20e
29 0193fc94 6ee31def 011afdb0 00000000 00000000 termsrv!CListenerEx::TransferWorkItem+0x21
2a 0193fcb0 77a63c45 0119ed88 0193fcfc 779237f5 termsrv!CListenerEx::staticTransferWorkItem+0x1c
2b 0193fcbc 779237f5 0119ed88 760aa416 00000000 kernel32!BaseThreadInitThunk+0xe
2c 0193fcfc 779237c8 6ee31dd3 0119ed88 00000000 ntdll!__RtlUserThreadStart+0x70
2d 0193fd14 00000000 6ee31dd3 0119ed88 00000000 ntdll!_RtlUserThreadStart+0x1b

崩溃的原因是,在MCSDetachUserRequest函数中,由于其第一个参数是已经被释放过的块,此时可能已经被其他地方分配出去了,结果就被填充了其他数据,这些数据并不符合原本该结构体的结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
signed int __stdcall MCSDetachUserRequest(int *a1)
{
int v1; // eax
int v4; // [esp+8h] [ebp-4h]

v1 = GetTotalLengthDeterminantEncodingSize(1);
if ( StackBufferAllocEx(*a1, *(_DWORD *)*a1, 0, v1 + 13, 25, (int)&v4) ) //*a1不一定是合法地址,何况**a1呢
return 11;
CreateDetachUserInd(3, 1, a1 + 3, *(_DWORD *)(v4 + 16));
*(_DWORD *)(v4 + 20) = GetTotalLengthDeterminantEncodingSize(1) + 13;
if ( SendOutBuf((_DWORD *)*a1, v4) >= 0 )
return DetachUser((_DWORD *)*a1, a1, 3, 0);
return 20;
}

c. verify

写如下windbg脚本:

1
2
3
4
5
6
7
8
bp rdpwd!GetNewDynamicChannel+5 ".printf \"GetNewDynamicChannel(%x) \", poi(ebp+8);gc;"
bp rdpwd!GetNewDynamicChannel+21 ".printf \"index=%x maxChannelIds=%x ret_ID=%x\\n\", poi(poi(ebp+8)+34), poi(poi(ebp+8)+a8), eax;kb;.echo;gc;"
bp rdpwd!MCSAttachUserRequest+50 ".printf \"vul heap allocated: %x\\n\", eax;kb;.echo;gc;"
bp rdpwd!MCSDetachUserRequest+5 ".printf \"MCSDetachUserRequest(%x)\\n\", poi(ebp+8);kb;.echo;gc;"
bp rdpwd!NMAbortConnect+9 ".printf \"offset 0x1c is %x\\n\", by(esi+1c);kb;.echo;gc;"
bp rdpwd!DetachUser+10e ".printf \"heap %x freed!\\n\", eax;kb;.echo;gc;"
bp rdpwd!NM_Disconnect++b ".printf \"offset 0x1c is %x\\n\", by(ecx+1c);kb;.echo;gc;"
bp rdpwd!NM_Connect+63 ".echo goto NMAbortConnect!;.echo;gc;"

3. microsoft official patch

根据漏洞发生的原因,可以确定两个必须patch的位置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void __stdcall NMAbortConnect(struct tagNM_HANDLE_DATA *a1)
{
if ( *((_BYTE *)a1 + 0x1C) & 1 )
NMDetachUserReq(a1);
SM_OnConnected(*(_DWORD *)a1, 0, 1, 0, 0);
}

int __stdcall NM_Disconnect(struct tagNM_HANDLE_DATA *a1)
{
int result; // eax

result = 1;
if ( *((_BYTE *)a1 + 0x1C) & 1 )
result = NMDetachUserReq(a1);
return result;
}

这两处都检测了a1+0x1C的最后一位是否为1,但是NMAbortConnect中在执行完NMDetachUserReq释放了堆之后,没有将该位置0,导致SM_OnConnected中的NM_Disconnect又对相同的参数执行了一次NMDetachUserReq,这就是导致UAF发生的根本原因

因此,最应该的patch就是在这两个NMDetachUserReq之后加一个*((_BYTE *)a1 + 0x1C) &= 0xFFFFFFFE来将该位置0

微软的官方patch总共有三处(上方为未patch,下方为patch):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void __stdcall NMAbortConnect(struct tagNM_HANDLE_DATA *a1)
{
if ( *((_BYTE *)a1 + 0x1C) & 1 )
NMDetachUserReq(a1);
SM_OnConnected(*(_DWORD *)a1, 0, 1, 0, 0);
}

void __stdcall NMAbortConnect(struct tagNM_HANDLE_DATA *a1)
{
if ( *((_BYTE *)a1 + 28) & 1 )
{
NMDetachUserReq(a1);
*((_DWORD *)a1 + 7) &= 0xFFFFFFFE;
}
SM_OnConnected(*(_DWORD *)a1, 0, 1, 0, 0);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int __stdcall NM_Disconnect(struct tagNM_HANDLE_DATA *a1)
{
int result; // eax

result = 1;
if ( *((_BYTE *)a1 + 0x1C) & 1 )
result = NMDetachUserReq(a1);
return result;
}

int __stdcall NM_Disconnect(struct tagNM_HANDLE_DATA *a1)
{
int result; // eax

result = 1;
if ( *((_BYTE *)a1 + 0x1C) & 1 )
{
result = NMDetachUserReq(a1);
*((_DWORD *)a1 + 7) &= 0xFFFFFFFE;
}
return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
char __thiscall HandleAttachUserReq(_BYTE *this, int a2, _DWORD *a3)
{

......

if ( SendOutBuf(v3, v10) < 0 )
SListRemove(v3 + 0x1D, P, &P);
}
return 1;
}

char __thiscall HandleAttachUserReq(_BYTE *this, int a2, _DWORD *a3)
{

......

if ( SendOutBuf(v3, v9) < 0 )
{
SListRemove(v3 + 29, (int)P, &P);
if ( P )
{
if ( !*((_BYTE *)P + 5) )
WDLIBRT_MemFree(P);
}
}
}
return 1;
}

前两处patch不多说,但是第三处patch似乎并没有什么用处,至少对于这个漏洞是如此