CVE-2020-17140 Windows SMB Information Disclosure Analysis
Author:k0shl of 360 Vulcan Team
Summary
Microsoft patched a SMB remote information disclosure vulnerability this patch tuesday I reported in September, it may affect over Windows 7 to Windows 10, more detail you can find in MSRC Acknowledgements: https://msrc.microsoft.com/update-guide/vulnerability/CVE-2020-17140 , I will public the vulnerability detail in this blog.
Background
Microsoft introduced a new SMB shared file caching mechanism called "LEASE" to improve read and write performance of shared file and directory since SMBv2 and later. We can add a lease to a shared file or directory by setting SMB packet structure SMB2_CREATE_CONTEXT->Buffer field value with SMB2_CREATE_REQEUST_LEASE_V2.
The structure of SMB2_CREATE_REQUEST_LEASE_V2 packet shown as following:
I use SMB2_CREATE_REQUEST_LEASE_V2 to add a lease to shared file, this structure only can be used in SMB 3.x dialects.(You can also try SMB2_CREATE_REQUEST_LEASE to trigger this vulnerability)
After adding lease name to shared file, we can change lease name by SMB2_SET_INFO packet, the structure of it like this:
There is a feild named InfoType in SMB2_SET_INFO request structure
If we set InfoType to SMB2_0_INFO_FILE, it will finally invoke NtSetInformationFile with Class FileRenameInformation to rename filename, and call Smb2ContinueSetInfo to update lease name buffer.
__int64 __fastcall Smb2ExecuteSetInfoReal(__int64 a1)
{
[...]
switch ( *(_BYTE *)(v1 + 160) ) //check InfoType
{
case 1:
[...]
LODWORD(v37) = *(_DWORD *)(v1 + 180);
v6 = NtSetInformationFile(v10, v7, v9, v8, v37);// <-------- rename
[...]
if ( v15 >= 0
|| (v27 = WPP_GLOBAL_Control, WPP_GLOBAL_Control == (PDEVICE_OBJECT)&WPP_GLOBAL_Control)
|| !(HIDWORD(WPP_GLOBAL_Control->Timer) & 0x10000000)
|| BYTE1(WPP_GLOBAL_Control->Timer) < 1u )
{
LABEL_20:
*(_DWORD *)(*(_QWORD *)(v2 + 104) + 48i64) = v15;
return Smb2ContinueSetInfo(v2);// <--------- update lease name buffer
}
[...]
}
[...]
}
CVE-2020-17140 Analysis
The root cause of CVE-2020-17140 is a code logic error that will cause use after free, vulnerability exists in srv2!Smb2UpdateLeaseFileName, the code trace like this:
Smb2ExecuteSetInfo
|
--- Smb2ExecuteSetInfoReal
|
--- Smb2ContinueSetInfo
|
--- Smb2UpdateLeaseFileName
srv2.sys driver will check if shared file have lease name after rename file name. If lease name existed, it will update lease name after invoke NtSetInformationFile.
__int64 __fastcall Smb2ContinueSetInfo(__int64 a1)
{
[...]
if ( *(_QWORD *)(*(_QWORD *)(v2 + 64) + 0x90i64) ) // <---------- Check there is lease name stored in buffer
{
v5 = *(_QWORD *)(v2 + 168);
v6 = Smb2ReferenceLeaseFromFile();
v7 = (_BYTE *)v6;
if ( v6 )
{
if ( (signed int)Smb2UpdateLeaseFileName(v6, (_WORD *)(v5 + 20), *(_DWORD *)(v5 + 16)) >= 0 )
v7[113] = 0;
Smb2DereferenceLease(v7);
}
}
[...]
}
signed __int64 __usercall Smb2LeaseAcquireOrUpgrade@<rax>(__int64 a1@<rcx>, __int64 a2@<rdx>, char a3@<r8b>, __int64 a4@<r9>, __int64 a5@<r13>)
{
[...]
*(_QWORD *)(v5 + 0x188) = v6 + 136;
*(_QWORD *)(v5 + 0x190) = v14;
*v14 = v5 + 0x188;
*(_QWORD *)(v6 + 0x90) = v5 + 0x188;
*(_QWORD *)(v5 + 0x90) = v6; // <---------- set pointer
[...]
}
"v5+0x90" store a pointer when client request lease name, if we set SMB2_CREATE_REQUEST_LEASE_V2 in SMB2_CREATE_CONTEXT->Buffer field, it will invoke Smb2LeaseAcquireOrUpgrade to set pointer.
//Set pointer
srv2!Smb2LeaseAcquireOrUpgrade+0x177:
fffff802`44a68d17 48899f90000000 mov qword ptr [rdi+90h],rbx ds:002b:ffffc70c`3f68eab0=0000000000000000
rdi=ffffc70c3f68ea20
rbx=ffffc70c3be41d70 <----------- set pointer
//Check pointer
srv2!Smb2ContinueSetInfo+0x8b:
fffff802`44ab8587 4883b99000000000 cmp qword ptr [rcx+90h],0 ds:002b:ffffc70c`3f68eab0=ffffc70c3be41d70
It will hit the vulnerability function, srv2!Smb2UpdateLeaseFileName will update lease name. A completely file name format like this: [FileName]:[LeaseName], if new file name is longer than old, it will invoke ExAllocatePoolWithTag to allocate a new NonPagedPoolNx pool to store the file name format, then copy full file name(old file name and new lease name) to new buffer.
But there is a obvious error, the function first free the old buffer, and copy free pool buffer content as file name to new buffer.
signed __int64 __fastcall Smb2UpdateLeaseFileName(__int64 a1, _WORD *a2, unsigned int a3)
{
[...]
v12 = (unsigned __int16)v4 + 2 * v11; // <------ get completely file name([filename]:[leasename]) length
[...]
if ( v12 > *(unsigned __int16 *)(v6 + 0x18A) )// <------ if file name is longer than old
{
v13 = ExAllocatePoolWithTag((POOL_TYPE)0x200, v12, 0x6C32534Cu); // <------ allocate a new buffer
if ( !v13 )
{
v3 = -1073741670;
goto LABEL_16;
}
if ( *(_BYTE *)(v6 + 114) ) // <------- [1]
ExFreePoolWithTag(*(PVOID *)(v6 + 400), 0);// <------ free old buffer
if ( v11 )
memmove(v13, *(const void **)(v6 + 400), 2i64 * v11); // <------- copy free buffer,trigger use after free.
*(_WORD *)(v6 + 394) = v12;
*(_QWORD *)(v6 + 400) = v13;
*(_BYTE *)(v6 + 114) = 1; // <------- [2]
}
memmove((void *)(*(_QWORD *)(v6 + 400) + 2i64 * v11), v5, (unsigned __int16)v4);// <------- copy new lease name
[...]
}
There is a boolean check[1] if there already exists a old file name buffer, if old buffer exists, "v6+114" is set to 1[2]. So, for triggering UaF, we need to send a SMB2_SET_INFO packet with a new lease name longer than old lease name which created by SMB2_CREATE_CONTEXT, and "v6+114" will be set to 1, then send another packet with a more longer lease name to trigger UaF.
Conclusion
This vulnerability can copy a free pool buffer or another pool buffer which occupy this free hole as a file name, it will cause kernel info leak. If this pool became a invalidate memory after free, it also will cause BSoD.
Crash Dump:
rax=ffffda0aa0488fa0 rbx=0000000000000000 rcx=ffffda0aa0488fa0
rdx=ffffda0aa0486fc0 rsi=0000000000000000 rdi=0000000000000000
rip=fffff80166a6fc8f rsp=ffffa601e6b2a918 rbp=0000000000000054
r8=000000000000000e r9=ffffda0aa0400e80 r10=ffffda0a9a41d160
r11=0000000000000000 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0 nv up ei ng nz na pe cy
srv2!memcpy+0xf:
fffff801`66a6fc8f 4c8b1a mov r11,qword ptr [rdx] ds:ffffda0a`a0486fc0=????????????????
Stack Trace:
ffffa601`e6b2a780 fffff801`66a6fc8f : fffff801`66a76812 00000000`00000007 ffffda0a`a0450e40 ffffda0a`a049ab00 : nt!KiPageFault+0x469
ffffa601`e6b2a918 fffff801`66a76812 : 00000000`00000007 ffffda0a`a0450e40 ffffda0a`a049ab00 00000000`00000100 : srv2!memcpy+0xf
ffffa601`e6b2a920 fffff801`66ab85b5 : 00000000`00000000 ffffda0a`a049a930 ffffda0a`a049af10 00000000`00000000 : srv2!Smb2UpdateLeaseFileName+0x8522
ffffa601`e6b2a970 fffff801`66ab7f03 : 00000000`00000000 ffffa601`e6b2aa69 ffffda0a`a049ac50 ffffda0a`a049a930 : srv2!Smb2ContinueSetInfo+0xb9
ffffa601`e6b2a9a0 fffff801`66ab87af : 00000000`00000000 ffffda0a`a049a940 fffff801`6815f601 ffffda0a`a0434f00 : srv2!Smb2ExecuteSetInfoReal+0x133
ffffa601`e6b2aad0 fffff801`66aba51b : 00000000`00000050 00000000`00000080 ffffffff`ee1e5d00 ffffda0a`a045afe0 : srv2!RfspThreadPoolNodeWorkerProcessWorkItems+0x13f
ffffa601`e6b2ab50 fffff801`6815f637 : ffffda0a`9abec000 ffffda0a`9eaf2040 00000000`00000151 00000000`00000000 : srv2!RfspThreadPoolNodeWorkerRun+0x11b
ffffa601`e6b2abb0 fffff801`67cad9a5 : ffffda0a`9eaf2040 fffff801`6815f600 ffffb88b`e1997d90 000fa46f`bd9bbdff : nt!IopThreadStart+0x37
ffffa601`e6b2ac10 fffff801`67e07868 : fffff801`6643f180 ffffda0a`9eaf2040 fffff801`67cad950 00000000`00000246 : nt!PspSystemThreadStartup+0x55
ffffa601`e6b2ac60 00000000`00000000 : ffffa601`e6b2b000 ffffa601`e6b25000 00000000`00000000 00000000`00000000 : nt!KiStartSystemThread+0x28
Patch
After patch, function will copy old file name to new buffer before free buffer:
signed __int64 __fastcall Smb2UpdateLeaseFileName(__int64 a1, _WORD *a2, unsigned int a3)
{
[...]
if ( v12 )
memmove(v15, *(const void **)(v6 + 0x190), 2i64 * v12);
if ( *(_BYTE *)(v6 + 0x72) )
ExFreePoolWithTag(*(PVOID *)(v6 + 0x190), 0);
[...]
}
Reference
https://msrc.microsoft.com/update-guide/vulnerability/CVE-2020-17140
Comments