Nytro Posted August 18, 2019 Report Posted August 18, 2019 Low-level Reversing of BLUEKEEP vulnerability (CVE-2019-0708) Home Low-level Reversing of BLUEKEEP vulnerability (CVE-2019-0708) 06 Aug 2019 BY: Latest from CoreLabs This work was originally done on Windows 7 Ultimate SP1 64-bit. The versions of the libraries used in the tutorial are: termdd.sys version 6.1.7601.17514 rdpwsx.dll version 6.1.7601.17828 rdpwd.sys version 6.1.7601.17830 icaapi.dll version 6.1.7600.16385 rdpcorekmts.dll version 6.1.7601.17828 The Svchost.exe process In the Windows NT operating system family, svchost.exe ('Service Host) is a system process that serves or hosts multiple Windows services. It runs on multiple instances, each hosting one or more services. It's indispensable in the execution of so-called shared services processes, where a grouping of services can share processes in order to reduce the use of system resources. The tasklist /svc command on a console with administrator permission shows us the different svchost processes and their associated services. Image 1.png Also in PROCESS EXPLORER you can easily identify which of the SVChosts is the one that handles RDP connections.(Remote Desktop Services) image2019-7-1_8-54-22.png STEP 1) Initial reversing to find the point where the program starts to parse my data decrypted The first thing we'll do is try to see where the driver is called from, for that, once we're debugging the remote kernel with Windbg or IDA, we put a breakpoint in the driver dispatch i.e. in the IcaDispatch function of termdd.sys. image2019-7-1_9-1-57.png In windbg bar I type .reload /f !process 1 0 PROCESS fffffa8006598b30 SessionId: 0 Cid: 0594 Peb: 7fffffd7000 ParentCid: 01d4 DirBase: 108706000 ObjectTable: fffff8a000f119a0 HandleCount: 662. Image: svchost.exe The call stack is WINDBG>k Child-SP RetAddr Call Site fffff880`05c14728 fffff800`02b95b35 termdd!IcaDispatch fffff880`05c14730 fffff800`02b923d8 nt!IopParseDevice+0x5a5 fffff880`05c148c0 fffff800`02b935f6 nt!ObpLookupObjectName+0x588 fffff880`05c149b0 fffff800`02b94efc nt!ObOpenObjectByName+0x306 fffff880`05c14a80 fffff800`02b9fb54 nt!IopCreateFile+0x2bc fffff880`05c14b20 fffff800`0289b253 nt!NtCreateFile+0x78 fffff880`05c14bb0 00000000`7781186a nt!KiSystemServiceCopyEnd+0x13 00000000`06d0f6c8 000007fe`f95014b2 ntdll!NtCreateFile+0xa 00000000`06d0f6d0 000007fe`f95013f3 ICAAPI!IcaOpen+0xa6 00000000`06d0f790 000007fe`f7dbd2b6 ICAAPI!IcaOpen+0x13 00000000`06d0f7c0 000007fe`f7dc04bd rdpcorekmts!CKMRDPConnection::InitializeInstance+0x1da 00000000`06d0f830 000007fe`f7dbb58a rdpcorekmts!CKMRDPConnection::Listen+0xf9 00000000`06d0f8d0 000007fe`f7dba8ea rdpcorekmts!CKMRDPListener::ListenThreadWorker+0xae 00000000`06d0f910 00000000`7755652d rdpcorekmts!CKMRDPListener::staticListenThread+0x12 00000000`06d0f940 00000000`777ec521 kernel32!BaseThreadInitThunk+0xd 00000000`06d0f970 00000000`00000000 ntdll!RtlUserThreadStart+0x1d An instance of CKMRDPListener class is created. This thread is created, the start address of the thread is the method CKMRDPListener::staticListenThread image2019-7-1_10-17-43.png the execution continues here image2019-7-1_10-20-52.png here image2019-7-1_10-22-19.png here image2019-7-1_10-23-22.png IcaOpen is called image2019-7-1_10-24-34.png image2019-7-1_14-18-49.png We can see RDX (buffer) and r8d (size of buffer) both are equal to zero in this first call to IcaOpen. Next the driver termdd is opened using the call to ntCreateFile image2019-7-1_9-12-46.png We arrived to IcaDispatch when opening the driver. image2019-7-1_10-30-44.png Reversing we can see image2019-7-1_10-49-54.png image2019-7-1_11-37-10.png The MajorFunction value is read here image2019-7-1_11-40-31.png image2019-7-1_11-41-16.png As MajorFuncion equals 0 it takes us to IcaCreate image2019-7-1_11-43-26.png image2019-7-1_11-45-17.png Inside IcaCreate, SystemBuffer is equal to 0 image2019-7-1_12-51-8.png image2019-7-1_12-49-25.png image2019-7-1_12-52-42.png A chunk of size 0x298 and tag ciST is created, and I call it chunk_CONNECTION. image2019-7-1_13-10-53.png chunk_CONNECTION is stored in FILE_OBJECT.FsContext image2019-7-1_13-14-27.png I rename FsContext to FsContext_chunk_CONNECTION. image2019-7-1_13-16-44.png IcaDispatch is called for second time Child-SP RetAddr Call Site fffff880`05c146a0 fffff880`03c96748 termdd!IcaCreate+0x36 fffff880`05c146f0 fffff800`02b95b35 termdd!IcaDispatch+0x2d4 fffff880`05c14730 fffff800`02b923d8 nt!IopParseDevice+0x5a5 fffff880`05c148c0 fffff800`02b935f6 nt!ObpLookupObjectName+0x588 fffff880`05c149b0 fffff800`02b94efc nt!ObOpenObjectByName+0x306 fffff880`05c14a80 fffff800`02b9fb54 nt!IopCreateFile+0x2bc fffff880`05c14b20 fffff800`0289b253 nt!NtCreateFile+0x78 fffff880`05c14bb0 00000000`7781186a nt!KiSystemServiceCopyEnd+0x13 00000000`06d0f618 000007fe`f95014b2 ntdll!NtCreateFile+0xa 00000000`06d0f620 000007fe`f95018c9 ICAAPI!IcaOpen+0xa6 00000000`06d0f6e0 000007fe`f95017e8 ICAAPI!IcaStackOpen+0xa4 00000000`06d0f710 000007fe`f7dbc015 ICAAPI!IcaStackOpen+0x8300000000`06d0f760 000007fe`f7dbd2f9 rdpcorekmts!CStack::CStack+0x189 00000000`06d0f7c0 000007fe`f7dc04bd rdpcorekmts!CKMRDPConnection::InitializeInstance+0x21d 00000000`06d0f830 000007fe`f7dbb58a rdpcorekmts!CKMRDPConnection::Listen+0xf9 00000000`06d0f8d0 000007fe`f7dba8ea rdpcorekmts!CKMRDPListener::ListenThreadWorker+0xae 00000000`06d0f910 00000000`7755652d rdpcorekmts!CKMRDPListener::staticListenThread+0x12 00000000`06d0f940 00000000`777ec521 kernel32!BaseThreadInitThunk+0xd 00000000`06d0f970 00000000`00000000 ntdll!RtlUserThreadStart+0x1d We had seen that the previous call to the driver had been generated here image2019-7-1_13-36-4.png When that call ends an instance of the class Cstack is created image2019-7-1_13-37-32.png And the class constructor is called. image2019-7-1_13-38-2.png this matches the current call stack fffff880`05c146a0 fffff880`03c96748 termdd!IcaCreate+0x36 fffff880`05c146f0 fffff800`02b95b35 termdd!IcaDispatch+0x2d4 fffff880`05c14730 fffff800`02b923d8 nt!IopParseDevice+0x5a5 fffff880`05c148c0 fffff800`02b935f6 nt!ObpLookupObjectName+0x588 fffff880`05c149b0 fffff800`02b94efc nt!ObOpenObjectByName+0x306 fffff880`05c14a80 fffff800`02b9fb54 nt!IopCreateFile+0x2bc fffff880`05c14b20 fffff800`0289b253 nt!NtCreateFile+0x78 fffff880`05c14bb0 00000000`7781186a nt!KiSystemServiceCopyEnd+0x13 00000000`06d0f618 000007fe`f95014b2 ntdll!NtCreateFile+0xa 00000000`06d0f620 000007fe`f95018c9 ICAAPI!IcaOpen+0xa6 00000000`06d0f6e0 000007fe`f95017e8 ICAAPI!IcaStackOpen+0xa4 00000000`06d0f710 000007fe`f7dbc015 ICAAPI!IcaStackOpen+0x8300000000`06d0f760 000007fe`f7dbd2f9 rdpcorekmts!CStack::CStack+0x18900000000`06d0f7c0 000007fe`f7dc04bd rdpcorekmts!CKMRDPConnection::InitializeInstance+0x21d 00000000`06d0f830 000007fe`f7dbb58a rdpcorekmts!CKMRDPConnection::Listen+0xf9 00000000`06d0f8d0 000007fe`f7dba8ea rdpcorekmts!CKMRDPListener::ListenThreadWorker+0xae 00000000`06d0f910 00000000`7755652d rdpcorekmts!CKMRDPListener::staticListenThread+0x12 00000000`06d0f940 00000000`777ec521 kernel32!BaseThreadInitThunk+0xd 00000000`06d0f970 00000000`00000000 ntdll!RtlUserThreadStart+0x1d The highlighted text is the same for both calls, the difference is the red line and the upper lines 00000000`06d0f7c0 000007fe`f7dc04bd rdpcorekmts!CKMRDPConnection::InitializeInstance+0x1da image2019-7-1_13-41-16.png The second call returns to 00000000`06d0f7c0 000007fe`f7dc04bd rdpcorekmts!CKMRDPConnection::InitializeInstance+0x21d image2019-7-1_13-42-34.png And this second call continues to image2019-7-1_13-43-46.png Next image2019-7-1_13-45-3.png Next image2019-7-1_14-20-48.png We arrived to _IcaOpen, calling ntCreafile for the second time, but now Buffer is a chunk in user allocated with a size different than zero, its size is 0x36. image2019-7-1_13-46-58.png This second call reaches IcaDispath and IcaCreate in similar way to the first call. But now SystemBuffer is different than zero, I suppose that SystemBuffer is created, if the buffer size is different to zero.(in the first call buffer=0 → SystemBuffer=0 now buffer!=0 → SystemBuffer is !=0). SystemBuffer is stored in _IRP.AssociatedIrp.SystemBuffer here image2019-7-2_6-33-41.png in the decompiled code image2019-7-2_6-49-1.png Previously IRP is moved to r12 image2019-7-2_6-34-34.png = image2019-7-1_13-50-29.png That address is accessed many times over there, so the only way to stop when it is nonzero is to use a conditional breakpoint. image2019-7-2_7-50-54.png image2019-7-2_8-36-51.png The first time that RAX is different from zero it stops before the second call to CREATE, and if I continue executing, I reach IcaCreate with that new value of SystemBuffer. image2019-7-2_7-57-25.png We arrived at this code, the variable named "contador" is zero, for this reason, we landed in IcaCreateStack. image2019-7-2_8-18-25.png In IcaCreateStack a new fragment of size 0xBA8 is allocated, I call it chunk_stack_0xBA8. image2019-7-2_8-21-24.png I comment the conditional breakpoint part, to avoid stopping and only keep logging. image2019-7-2_8-44-4.png I repeat the process to get a new fresh log. image2019-7-2_8-43-25.png Summarizing by just executing this two lines of code to create a connection, and even without sending data, we have access to the driver The most relevent part of the log when connecting is this. image2019-7-5_7-41-1.png IcaCreate was called two times, with MajorFunction = 0x0. The first call allocates CHUNK_CONNECTION, the second call allocates chunk_stack_0xBA8. We will begin to reverse the data that it receives, for it would be convenient to be able to use Wireshark to analyze the data, although as the connection is encrypted with SSL, in Wireshark we could only see that the encrypted data which does not help us much. image2019-7-10_7-17-40.png The data travels encrypted and thus the Wireshark receives it, but we will try to use it all the same. For this purpose we need to detect the point where the program begins to parse data already decrypted. image2019-7-10_7-21-1.png The driver rdpwd.sys is in charge of starting to parse the data already decrypted. The important point for us is in the function MCSIcaRawInputWorker, where the program started to parse the decrypted code. image2019-7-10_7-23-32.png STEP 2) Put some conditional breakpoints in IDA PRO to dump to a file the data decrypted The idea is place a conditional breakpoint in that point, so that each time the execution passes there, it will save the data it has already decrypted in a file, then use that file and load it in Wireshark. image2019-7-10_7-40-29.png This will analyze the module rdpwd.sys and I can find its functions in IDA, debugging from my database of termdd.sys, when it stops at any breakpoint of this driver. image2019-7-10_7-44-21.png image2019-7-10_7-44-42.png I already found the important point: if the module rdpwd.sys changes its location by ASLR, I will have to repeat these steps to relocate the breakpoint correctly. address = 0xFFFFF880034675E8 filename=r"C:\Users\ricardo\Desktop\pepe.txt" size=0x40 out=open(filename, "wb" dbgr =True data = GetManyBytes(address, size, use_dbg=dbgr) out.write(data) out.close() This script saves in a file the bytes pointed by variable "address", the amount saved will be given by the variable "size", and saves it in a file on my desktop, I will adapt it to read the address and size from the registers at the point of breakpoint. address=cpu.r12 size=cpu.rbp filename=r"C:\Users\ricardo\Desktop\pepe.txt" out=open(filename, "ab") dbgr =True data = GetManyBytes(address, size, use_dbg=dbgr) out.write(data) image2019-7-10_8-21-26.png This will dump the bytes perfectly to the file. image2019-7-10_8-16-31.png I will use this script in the conditional breakpoint. image2019-7-10_8-25-48.png This script made a raw dump, but wireshark only imports in this format. image2019-7-10_8-52-4.png address=cpu.r12 size=cpu.rbp filename=r"C:\Users\ricardo\Desktop\pepe.txt" out=open(filename, "ab") dbgr =True data = GetManyBytes(address, size, use_dbg=dbgr) str=""for i in data: str+= "%02x "%ord(i) out.write(str) in Windows 7 32 bits version this is the important point where the decrypted code is parsed, and we can use this script to dump to a file. image2019-7-25_7-4-49.png Windows 7 32 script address=cpu.eax size=cpu.ebx filename=r"C:\Users\ricardo\Desktop\pepefff.txt" out=open(filename, "ab") dbgr =True data = GetManyBytes(address, size, use_dbg=dbgr) str="" for i in data: str+= "%02x "%ord(i) out.write(str) Windows XP 32 bits script address=cpu.eax size=cpu.edi filename=r"C:\Users\ricardo\Desktop\pepefff.txt" out=open(filename, "ab") dbgr =True data = GetManyBytes(address, size, use_dbg=dbgr) str="" for i in data: str+= "%02x "%ord(i) out.write(str) This is the similar point in Windows XP. image2019-7-29_9-7-42.png image2019-7-29_9-9-50.png STEP 3) Importing to Wireshark This script will save the bytes in the format that wireshark will understand. When I I"Import from hex dump", I will use the port "3389/tcp" : "msrdp" image2019-7-10_9-39-29.png We load our dump file and put the destination port as 3389, the source port is not important. I add a rule to decode port 3389 as TPKT image2019-7-10_10-5-44.png image2019-7-10_9-43-3.png That file will be decoded as TPKT in wireshark. That's the complete script. image2019-7-10_10-30-53.png Using that script, the dump file is created, when it is imported as a hexadecimal file in Wireshark, it is displayed perfectly. image2019-8-7_13-3-46.png This work can be done also by importing the SSL private key in wireshark, but I like to do it in the most manual way, old school type. STEP 4) More reversing We are ready to receive and analyze our first package, but first we must complete and analyze some more tasks that the program performs after what we saw before receiving the first package of our data. We can see that there are a few more calls before starting to receive data, a couple more calls to the driver. image2019-7-11_7-40-17.png The part marked in red is what we have left to analyze from the first connection without sending data. I will modify the conditional breakpoint to stop at the first MajorFunction = 0xE. image2019-7-11_7-50-23.png It will stop when MajorFunction = 0xE image2019-7-11_7-54-22.png We arrived at IcaDeviceControl. image2019-7-11_7-55-47.png We can see that this call is generated when the program accepts the connection, calling ZwDeviceIoControlFile next. image2019-7-11_8-16-31.png We can see that IRP and IO_STACK_LOCATION are maintained with the same value, fileobject has changed. image2019-7-11_8-34-49.png We will leave the previous structure called FILE_OBJECT for the previous call, and we will make a copy with the original fields called FILE_OBJECT_2, to be used in this call. image2019-7-11_8-40-35.png The previous FILE_OBJECT was an object that was obtained from ObReferenceObjectByHandle. image2019-7-11_8-51-20.png The new FILE_OBJECT has the same structure but is a different object, for that reason we create a new structure for this. image2019-7-11_11-16-45.png We continue reversing ProbeAndCaptureUserBuffers image2019-7-11_11-50-29.png A new chunk with the size (InputBufferLenght + OutputBufferLenght) is created. image2019-7-11_11-47-15.png Stores the pointers to the Input and Output buffers chunks. image2019-7-11_11-51-8.png We can see that IcaUserProbeAddress is similar to nt! MmUserProbeAddress value image2019-7-12_6-36-39.png That's used to verify whether a user-specified address resides within user-mode memory areas, or not. If the address is lower than IcaUserProbeAddress resides in User mode memory areas, and a second check is performed to ensure than the InputUserBuffer + InputBufferLenght address is bigger than InputUserBuffer address.(size not negative) image2019-7-12_7-9-41.png Then the data is copied from the InputUserBuffer to the chunk_Input_Buffer that has just allocated for this purpose. We can see the data that the program copies from InputUserBuffer, it's not data that we send yet. image2019-7-12_7-11-34.png Since the OutputBufferLength is zero, it will not copy from OutputUserBuffer to the chunk_OutputBuffer. image2019-7-12_7-13-47.png Clears chunk_OutputBuffer and return. image2019-7-12_7-17-41.png Returning from ProbeAndCaptureUserBuffers, we can see that this function copies the input and output buffer of the user mode memory to the new chunks allocated in the kernel memory, for the handling of said data by the driver image2019-7-12_7-21-55.png The variable "resource" points to IcaStackDispatchTable. image2019-7-12_7-37-14.png I frame the area of the table and create a structure from memory which I call _IcaStackDispatchTable. image2019-7-12_7-45-56.png image2019-7-12_7-43-49.png I entered and started to reverse this function. image2019-7-12_8-28-20.png The first time we arrived here, the IOCTL value is 38002b. image2019-7-12_8-44-11.png We arrived to a call to _IcaPushStack. image2019-7-12_9-10-25.png Inside two allocations are performed, i named them chunk_PUSH_STACK_0x488 and chunk_PUSH_STACK_0xA8 image2019-7-12_11-43-51.png When IOCTL value 0x38002b is used, we reach _IcaLoadSd image2019-7-12_11-57-33.png We can see the complete log of the calls to the driver with different IOCTL only in the connection without sending data yet. IO_STACK_LOCATION 0xfffffa80061bea90L IRP 0xfffffa80061be9c0L chunk_CONNECTION 0xfffffa8006223510L IO_STACK_LOCATION 0xfffffa80061bea90L IRP 0xfffffa80061be9c0L FILE_OBJECT 0xfffffa8004231860L chunk_stack_0xBA8 0xfffffa80068d63d0L FILE_OBJECT_2 0xfffffa80063307b0L IOCTL 0x380047L FILE_OBJECT_2 0xfffffa8006335ae0L IOCTL 0x38002bL chunk_PUSH_STACK_0x488 0xfffffa8006922a20L chunk_PUSH_STACK_0xa8 0xfffffa8005ce0570L FILE_OBJECT_2 0xfffffa8006335ae0L IOCTL 0x38002bL chunk_PUSH_STACK_0x488 0xfffffa8005f234e0L chunk_PUSH_STACK_0xa8 0xfffffa8006875ba0L FILE_OBJECT_2 0xfffffa8006335ae0L IOCTL 0x38002bL chunk_PUSH_STACK_0x488 0xfffffa8005daf010L chunk_PUSH_STACK_0xa8 0xfffffa8006324c40L FILE_OBJECT_2 0xfffffa8006335ae0L IOCTL 0x38003bL FILE_OBJECT_2 0xfffffa8006335ae0L IOCTL 0x3800c7L FILE_OBJECT_2 0xfffffa8006335ae0L IOCTL 0x38244fL FILE_OBJECT_2 0xfffffa8006335ae0L IOCTL 0x38016fL FILE_OBJECT_2 0xfffffa8006335ae0L IOCTL 0x380173L FILE_OBJECT_2 0xfffffa8006334c90L FILE_OBJECT_2 0xfffffa8006335ae0L IOCTL 0x38004bL IO_STACK_LOCATION 0xfffffa8004ceb9d0L IRP 0xfffffa8004ceb900L FILE_OBJECT 0xfffffa8006334c90L chunk_channel 0xfffffa8006923240L guarda RDI DESTINATION 0xfffffa8006923240L FILE_OBJECT_2 0xfffffa8006335ae0L IOCTL 0x381403L FILE_OBJECT_2 0xfffffa8006335ae0L IOCTL 0x380148L I will put conditional breakpoints in each different IOCTL, to list the functions where each one ends up. The IOCTLs 0x380047, 0x38003b, 0x3800c7, 0x38244f, 0x38016f, 0x38004b, 0x381403 end in _IcaCallStack image2019-7-15_8-12-31.png These IOCTLs also reach _IcaCallSd image2019-7-15_8-13-6.png IOCTL 0x380148 does nothing IOCTL 0x380173 reaches _IcaDriverThread image2019-7-15_8-28-38.png And this last one reaches tdtcp_TdInputThread also. image2019-7-15_8-30-27.png This function is used to receive the data sended by the user. STEP 5) Receiving data If we continue running to the point of data entry breakpoint, we can see in the call stack that it comes from tdtcp! TdInputThread. image2019-7-15_8-57-57.png The server is ready now, and waiting for our first send. We will analyze the packages and next we will return to the reversing. STEP 6) Analyzing Packets Negotiate Request package 03 00 00 13 0e e0 00 00 00 00 00 01 00 08 00 01 00 00 00 Step 6.png Requested Protocol image2019-7-15_10-28-28.png Negotiation Response package The Response package was similar only with Type=0x2 RDP Negotiation Response image2019-7-15_10-37-35.png Connect Initial Package The package starts with "\x03\x00\xFF\xFF\x02\xf0\x80" #\xFF\xFF are sizes to be calculated and smashed at the end Header 03 -> TPKT: TPKT version = 3 00 -> TPKT: Reserved = 0 FF -> TPKT: Packet length - high part FF -> TPKT: Packet length - low part X.224 02 -> X.224: Length indicator = 2 f0 -> X.224: Type = 0xf0 = Data TPDU 80 -> X.224: EOT PDU "7f 65" .. -- BER: Application-Defined Type = APPLICATION 101, "82 FF FF" .. -- BER: Type Length = will be calculated and smashed at the end in the Dos sample will be 0x1b2 "04 01 01" .. -- Connect-Initial::callingDomainSelector "04 01 01" .. -- Connect-Initial::calledDomainSelector "01 01 ff" .. -- Connect-Initial::upwardFlag = TRUE "30 19" .. -- Connect-Initial::targetParameters (25 bytes) "02 01 22" .. -- DomainParameters::maxChannelIds = 34 "02 01 02" .. -- DomainParameters::maxUserIds = 2 "02 01 00" .. -- DomainParameters::maxTokenIds = 0 "02 01 01" .. -- DomainParameters::numPriorities = 1 "02 01 00" .. -- DomainParameters::minThroughput = 0 "02 01 01" .. -- DomainParameters::maxHeight = 1 "02 02 ff ff" .. -- DomainParameters::maxMCSPDUsize = 65535 "02 01 02" .. -- DomainParameters::protocolVersion = 2 "30 19" .. -- Connect-Initial::minimumParameters (25 bytes) "02 01 01" .. -- DomainParameters::maxChannelIds = 1 "02 01 01" .. -- DomainParameters::maxUserIds = 1 "02 01 01" .. -- DomainParameters::maxTokenIds = 1 "02 01 01" .. -- DomainParameters::numPriorities = 1 "02 01 00" .. -- DomainParameters::minThroughput = 0 "02 01 01" .. -- DomainParameters::maxHeight = 1 "02 02 04 20" .. -- DomainParameters::maxMCSPDUsize = 1056 "02 01 02" .. -- DomainParameters::protocolVersion = 2 "30 1c" .. -- Connect-Initial::maximumParameters (28 bytes) "02 02 ff ff" .. -- DomainParameters::maxChannelIds = 65535 "02 02 fc 17" .. -- DomainParameters::maxUserIds = 64535 "02 02 ff ff" .. -- DomainParameters::maxTokenIds = 65535 "02 01 01" .. -- DomainParameters::numPriorities = 1 "02 01 00" .. -- DomainParameters::minThroughput = 0 "02 01 01" .. -- DomainParameters::maxHeight = 1 "02 02 ff ff" .. -- DomainParameters::maxMCSPDUsize = 65535 "02 01 02" .. -- DomainParameters::protocolVersion = 2 "04 82 FF FF" .. -- Connect-Initial::userData (calculated at the end in the DoS example will be 0x151 bytes) "00 05" .. -- object length = 5 bytes "00 14 7c 00 01" .. -- object "81 48" .. -- ConnectData::connectPDU length = 0x48 bytes "00 08 00 10 00 01 c0 00 44 75 63 61" .. -- PER encoded (ALIGNED variant of BASIC-PER) GCC Conference Create Request PDU "81 FF" .. -- UserData::value length (calculated at the end in the DoS example will be 0x13a bytes) #------------- "01 c0 ea 00" .. -- TS_UD_HEADER::type = CS_CORE (0xc001), length = 0xea bytes "04 00 08 00" .. -- TS_UD_CS_CORE::version = 0x0008004 "00 05" .. -- TS_UD_CS_CORE::desktopWidth = 1280 "20 03" .. -- TS_UD_CS_CORE::desktopHeight = 1024 "01 ca" .. -- TS_UD_CS_CORE::colorDepth = RNS_UD_COLOR_8BPP (0xca01) "03 aa" .. -- TS_UD_CS_CORE::SASSequence "09 04 00 00" .. -- TS_UD_CS_CORE::keyboardLayout = 0x409 = 1033 = English (US) "28 0a 00 00" .. -- TS_UD_CS_CORE::clientBuild = 2600 "45 00 4d 00 50 00 2d 00 4c 00 41 00 50 00 2d 00 " .. "30 00 30 00 31 00 34 00 00 00 00 00 00 00 00 00 " .. -- TS_UD_CS_CORE::clientName = EMP-LAP-0014 "04 00 00 00" .. -- TS_UD_CS_CORE::keyboardType "00 00 00 00" .. -- TS_UD_CS_CORE::keyboardSubtype "0c 00 00 00" .. -- TS_UD_CS_CORE::keyboardFunctionKey "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " .. "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " .. "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " .. "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " .. -- TS_UD_CS_CORE::imeFileName = "" "01 ca" .. -- TS_UD_CS_CORE::postBeta2ColorDepth = RNS_UD_COLOR_8BPP (0xca01) "01 00" .. -- TS_UD_CS_CORE::clientProductId "00 00 00 00" .. -- TS_UD_CS_CORE::serialNumber "18 00" .. -- TS_UD_CS_CORE::highColorDepth = 24 bpp "07 00" .. -- TS_UD_CS_CORE::supportedColorDepths = 24 bpp "01 00" .. -- TS_UD_CS_CORE::earlyCapabilityFlags "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " .. "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " .. "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " .. "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " .. -- TS_UD_CS_CORE::clientDigProductId 07 -> TS_UD_CS_CORE::connectionType = 7 00 -> TS_UD_CS_CORE::pad1octet 01 00 00 00 -> TS_UD_CS_CORE::serverSelectedProtocol #--------------- 04 c0 0c 00 -> TS_UD_HEADER::type = CS_CLUSTER (0xc004), length = 12 bytes "15 00 00 00" .. -- TS_UD_CS_CLUSTER::Flags = 0x15 f (REDIRECTION_SUPPORTED | REDIRECTION_VERSION3) "00 00 00 00" .. -- TS_UD_CS_CLUSTER::RedirectedSessionID #------------ "02 c0 0c 00" -- TS_UD_HEADER::type = CS_SECURITY (0xc002), length = 12 bytes "1b 00 00 00" .. -- TS_UD_CS_SEC::encryptionMethods "00 00 00 00" .. -- TS_UD_CS_SEC::extEncryptionMethods "03 c0 38 00" .. -- TS_UD_HEADER::type = CS_NET (0xc003), length = 0x38 bytes In this package we need to set the user channels, and a MS_T120 channel needs to be included in the list. Erect Domain Package domain package1.png image2019-7-15_11-25-10.png 0x04: type ErectDomainRequest 0x01: subHeight length = 1 byte 0x00 : subHeight = 0 0x01: subInterval length = 1 byte 0x00: subInterval = 0 User Attach Packet package image2019-7-15_13-17-26.png image2019-7-15_13-25-21.png We need to analyze the response. 03 00 00 0b 02 f0 80 2e 00 00 07 image2019-7-15_13-42-33.png The last byte is the initiator, we need to strip from the response to use in the next packet. Channel Join request package Building the package xv1 = (chan_num) / 256 val = (chan_num) % 256 '\x03\x00\x00\x0c\x02\xf0\x80\x38\x00' + initiator + chr(xv1) + chr(val) For channel 1003 by example xv1 = (1003) / 256 = 3 val = (1003) % 256 = 235 '\x03\x00\x00\x0c\x02\xf0\x80\x38\x00' + initiator + chr(3) + chr(235) image2019-7-15_14-26-0.png image2019-7-15_14-31-39.png 0x38: channelJoinRequest (14) image2019-7-16_7-39-52.png All channel join packages are similar, the only thing that changes are the last two bytes that correspond to the channel number. Channel Join Confirm Response package The response was 03 00 00 0f 02 f0 80 3e 00 00 07 03 eb 03 eb 0x3e:channelJoinConfirm (15) image2019-7-16_8-1-54.png result: rt_succesful (0x0) The packet has the same initiator and channelid values than the request to the same channel. When all the channels response the Join Request, the next package sended is send Data Request. image2019-7-22_11-46-44.png Client Info PDU or Send Data Request Package image2019-7-22_11-48-45.png The remaining packages are important for the exploitation, so for now we will not show them in this first delivery. STEP 7) The vulnerability The program allocate a channel MS_T120 by default, the user can set different channels in the packages. This is the diff of the function named IcabindVirtualChannels image2019-8-6_12-3-44.png This is the patch for the Windows XP version, which its logic is similar for every vulnerable windows version, when the program compares the string MS_T120 with the name of each channel, the pointer is forced to be stored in a fixed position of the table, forcing to use the value 0x1f to calculate the place to save it . In the vulnerable version, the pointer is stored using the channel number to calculate the position in the channel table, and we will have two pointers stored in different locations, pointing to the same chunk. If the user set a channel MS_T120 and send crafted data to that channel, the program will allocate a chunk for that, but will store two different pointers to that chunk, after that the program frees the chunk, but the data of the freed chunk is incorrectly accessed, performing a USE AFTER FREE vulnerability. The chunk is freed here image2019-8-6_13-50-59.png Then the chunk is accessed after the free here, EBX will point to the freed chunk. image2019-8-6_13-51-59.png If a perfect pool spray is performed, using the correct chunk size, we can control the execution flow, the value of EAX controlled by us, EBX point to our chunk, EAX =[EBX+0x8c] is controlled by us too. image2019-8-6_13-55-45.png STEP 😎 Pool spray There is a point in the code that let us allocate our data with size controlled, and the same type of pool. image2019-8-6_14-2-16.png We can send bunch of crafted packages to reach this point, if this packages have the right size can fill the freed chunk, with our data. In order to get the right size is necessary look at the function IcaAllocateChannel. In Windows 7 32 bits, the size of each chunk of the pool spray should be 0xc8. image2019-8-6_14-7-12.png For Windows XP 32 bits that size should be 0x8c. image2019-8-6_14-8-47.png This pool spray remain in this loop allocating with the right size, and we can fill the freed chunk with our own data to control the code execution in the CALL (IcaChannelInputInternal + 0x118) Be happy Ricardo Narvaja Sursa: https://www.coresecurity.com/blog/low-level-reversing-bluekeep-vulnerability-cve-2019-0708?code=CMP-0000001929&ls=100000000&utm_campaign=core-security-emails&utm_content=98375609&utm_medium=social&utm_source=twitter&hss_channel=tw-17157238 1 Quote