CVE-2019-1489
Exploitable information leak vulnerabilities exists in the RDP7 implementation of Microsoft’s Remote Desktop Services on Windows XP. Various aspects of the T.128 protocol, such as capability negotiation, can cause an information leak, which can provide an attacker information about the target’s address-space. An attacker can simply negotiate capabilities with the target via T.128 in order to trigger these vulnerabilities.
Microsoft’s Remote Desktop Services – Windows XP (only): RDPWD.sys 5.1.2600.5512 termdd.sys 5.1.2600.5512
https://docs.microsoft.com/en-us/windows/win32/termserv/terminal-services-portal
5.3 - CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N
CWE-201: Information Exposure Through Sent Data
Remote Desktop Services allow a user or administrator to take control of a remote computer over a network connection. This allows the user to utilize a familiar graphical user interface to interact with said remote machine that provides a thin-client architecture on the Windows platform. These capabilities are accomplished using the Remote Desktop Protocol which is built on top of the X.224, T.124, T.125, T.128 protocols. Remote Desktop Services is a common service within the enterprise and is commonly used as a workaround on a network with otherwise minimalistic remote administration capabilities.
When a client initializes a connection via the RDP protocol, the client must first go through a number of stages in order to setup each of the individual layers that are used by the RDS platform. After performing all of the required negotiations, the client will then perform a security exchange which is required to complete the initialization of the T.128 protocol which will provide the Multipoint Application Sharing as mentioned in the T.128 specification. After performing this security exchange, the client will then need to negotiate capabilities with the remote server. This is done by first receiving a packet from the server containing the supported capabilities, followed by a response from the client with the capabilities that it will use. During the server’s construction of the individual capabilities, the server does not properly initialize the related structures prior to sending them to the client. Thus when announcing these capabilities to a client, the server will then mistakenly leak information with these packets.
After completing the requirements of the T.125 protocol, the service will begin to setup its state in order to communicate over the T.128 protocol. This is done by calling the WDWNewShareClass
function from the WDWConnect
function. This will then instantiate a ShareClass
object by first allocating memory for it at [1] and then calling its constructor at [2]. Upon returning to its caller, it will complete preparation of the object by calling the SM_Init
function [3]. Once these functions have properly setup the ShareClass
object, the driver may then send encrypted data to a client over the T.128 layer.
RDPWD!WDWConnect+0x272:
f1b2ad66 53 push ebx
f1b2ad67 e8861a0000 call RDPWD!WDWNewShareClass (f1b2c7f2) ; \
f1b2ad6c 8bf0 mov esi,eax
f1b2ad6e 85f6 test esi,esi
f1b2ad70 0f8cd4fdffff jl RDPWD!WDWConnect+0x56 (f1b2ab4a)
\
RDPWD!WDWNewShareClass:
f1b2c7f2 8bff mov edi,edi
f1b2c7f4 55 push ebp
f1b2c7f5 8bec mov ebp,esp
f1b2c7f7 56 push esi
f1b2c7f8 57 push edi
f1b2c7f9 68e0040000 push 4E0h ; size
f1b2c7fe 33ff xor edi,edi
f1b2c800 e8390a0000 call RDPWD!operator new (f1b2d23e) ; [1] Allocate 0x4e0 for ShareClass
f1b2c805 85c0 test eax,eax
f1b2c807 8b7508 mov esi,dword ptr [ebp+8]
f1b2c80a 59 pop ecx
f1b2c80b 7416 je RDPWD!WDWNewShareClass+0x31 (f1b2c823)
...
RDPWD!WDWNewShareClass+0x1b:
f1b2c80d ff765c push dword ptr [esi+5Ch]
f1b2c810 8bc8 mov ecx,eax
f1b2c812 ff7630 push dword ptr [esi+30h]
f1b2c815 ff762c push dword ptr [esi+2Ch]
f1b2c818 ff7628 push dword ptr [esi+28h]
f1b2c81b 56 push esi
f1b2c81c e811ffffff call RDPWD!ShareClass::ShareClass (f1b2c732) ; [2] Instantiate ShareClass object
/
RDPWD!WDWConnect+0x282:
f1b2ad76 ff7520 push dword ptr [ebp+20h]
f1b2ad79 53 push ebx
f1b2ad7a ff735c push dword ptr [ebx+5Ch]
f1b2ad7d e88edaffff call RDPWD!SM_Init (f1b28810) ; [3]
f1b2ad82 8bf0 mov esi,eax
f1b2ad84 85f6 test esi,esi
f1b2ad86 0f8cbefdffff jl RDPWD!WDWConnect+0x56 (f1b2ab4a)
f1b2ad8c 8b451c mov eax,dword ptr [ebp+1Ch]
After the ShareClass
object has been setup, any component of the driver can then use the ShareClass::SC_SendData
method from the ShareClass
object in order to send data over the T.128 protocol. Data that is sent over the T.128 protocol is wrapped in a type named SharePDU
. A SharePDU
is encrypted by Microsoft’s implementation of RDP and supports two layers of types. The first layer of the SharePDU
is prefixed with a structure, ShareControlHeader
which contains a 4-bit field named ShareControlHeader.PDUType
which informs the consumer of the type of the structure that the ShareControlHeader
contains. This header can then be used by a server and client to either notify the client that it should deactivate a particular session, or to exchange/negotiate what capabilities will be used by the current T.128 session. On top of this T.128 layer, a server or client can also exchange even more data by encompassing a packet within a ShareDataHeader
. The ShareClass
object is used to represent data that is to be sent using the ShareDataHeader
layer, whereas the SM_*
functions are used to represent data that as sent at the ShareControlHeader
layer.
As prior mentioned, the driver implements a method for the ShareClass
object called ShareClass::SC_SendData
. This method will take packet data provided by the caller, and prefix it with a ShareControlHeader
before handing the packet off to the SM_SendData
function for encrypting the packet. Upon entry into the ShareClass::SC_SendData
method, the method will first set the ShareControlHeader.PDUType
field to Data(7), and its
ShareControlHeader.ProtocolVersion field to 1 at [4]. Afterwards, the
ShareControlHeader.PDUSource will then be set to the server channel identifier which is always 1002 (0x3ea). After completing the
ShareControlHeader, the method will then initialize the
ShareDataHeader. The 16-bit length of the
SharePDU will be written to the beginning of the packet, and at [6] the rest of the fields will be initialized. The resulting buffer after initialization will then be passed to the
SM_SendData function with the
SEC_ENCRYPT flag set as one of its parameters. While initializing the
ShareDataHeader structure, two of the 8-bit fields at offset 0xa and offset 0xe are left uninitialized. The field at offset 0xe represents the
ShareDataHeader.PDUType2` field and is intended to be initialized by the caller. The field at offset 0xa, however, is left uninitialized and when the packet is sent will leak 8-bits to the client.
RDPWD!ShareClass::SC_SendData:
f1b34f46 8bff mov edi,edi
f1b34f48 55 push ebp
f1b34f49 8bec mov ebp,esp
f1b34f4b 8b550c mov edx,dword ptr [ebp+0Ch] ; Packet data
f1b34f4e 8b4508 mov eax,dword ptr [ebp+8] ; ShareClass*
f1b34f51 56 push esi
f1b34f52 8b7514 mov esi,dword ptr [ebp+14h] ; SharePDU length
f1b34f55 85f6 test esi,esi
f1b34f57 66c742021700 mov word ptr [edx+2],17h ; [4] ShareControlHeader.protocolVersion := 1 | ShareControlHeader.pduType := Data(7)
f1b34f5d 668b8874040000 mov cx,word ptr [eax+474h] ; Server Channel Id (1002)
f1b34f64 66894a04 mov word ptr [edx+4],cx ; ShareControlHeader.PDUSource
f1b34f68 7427 je RDPWD!ShareClass::SC_SendData+0x4b (f1b34f91))
RDPWD!ShareClass::SC_SendData+0x24:
f1b34f6a 668932 mov word ptr [edx],si ; [5] SharePDU.totalLength
f1b34f6d 8b8878040000 mov ecx,dword ptr [eax+478h]
f1b34f73 6683621000 and word ptr [edx+10h],0 ; [6] ShareDataHeader.compressedLength
f1b34f78 894a06 mov dword ptr [edx+6],ecx ; ShareDataHeader.ShareId
f1b34f7b 8a4d18 mov cl,byte ptr [ebp+18h]
f1b34f7e 884a0b mov byte ptr [edx+0Bh],cl ; ShareDataHeader.StreamId
f1b34f81 6689720c mov word ptr [edx+0Ch],si ; ShareDataHeader.uncompressedLength
f1b34f85 c6420f00 mov byte ptr [edx+0Fh],0 ; ShareDataHaeder.compressedType
...
RDPWD!ShareClass::SC_SendData+0x4b:
f1b34f91 8b8880040000 mov ecx,dword ptr [eax+480h]
f1b34f97 6a08 push 8 ; TS_SECURITY_PACKET.basicSecurityHeader.flags := SEC_ENCRYPT(8)
f1b34f99 6a00 push 0 ; NM_ packet type
f1b34f9b 6a00 push 0 ; Network Channel Id
f1b34f9d 6a01 push 1 ; SendData.dataPriority := high(1)
f1b34f9f ff7510 push dword ptr [ebp+10h] ; Packet data length
f1b34fa2 e8c73affff call RDPWD!SM_SendData (f1b28a6e) ; [7]
f1b34fa7 5e pop esi
f1b34fa8 5d pop ebp
f1b34fa9 c21800 ret 18h
The SharePDU
begins with a ShareControlHeader
which has the following definition. As prior mentioned, the first field in the header is a 16-bit length. When the ShareControlHeader.pduType
is set to Data(7)
, the header of the SharePDU
will then be followed by a ShareDataHeader
. In the implementation of ShareClass::SC_SendData
the byte at offset 0xa is not initialized which references the ShareDataHeader.pad1octet
field at [8]. Thus the 8-bits being leaked is via the ShareDataHeader.pad1octet
field.
UserID ::= Integer16
ShareControlHeader ::= SEQUENCE {
totalLength Integer16(0..32767), // 0x0 : +2
protocolVersion Integer4(1), // 0x2.0 : +0.5
pduType PDUType, // 0x2.5 : +1.5
pduSource UserID // 0x4 : +2
}
ShareID ::= Integer32
StreamID ::= INTEGER {
streamLowPriority(1), streamMediumPriority(2), streamHighPriority(4)
}(0..255)
ShareDataHeader ::= SEQUENCE {
shareControlHeader ShareControlHeader, // 0x0 : +6
shareID ShareID, // 0x6 : +4
pad1octet Integer8(0), // [8] 0xa : +1
streamID StreamID, // 0xb : +1
uncompressedLength Integer16, // 0xc : +2
pduType2 PDUType2, // 0xe : +1
generalCompressedType Integer8, // 0xf : +1
generalCompressedLength Integer16 // 0x10 : +2
}
As prior mentioned, all T.128 data is prefixed with a ShareControlHeader
by the ShareClass::SC_SendData
method before being passed to the SM_SendData
function which will then wrap the packet in a SharePDU
. The SM_SendData
method will then read flags from its parameter in order to send to the client. The ShareClass::SC_SendData
method thus expects the caller to properly allocate space for its data along with some space at the beginning for both the ShareControlHeader
and the ShareDataHeader
. When the server wishes to send a SynchronizePDU(31)
to a client, the following method ShareClass::SCInitiateSync
is called. At [9], the method will first allocate 0x16 bytes for the buffer which sets aside 0x12 for the SharePDU
headers, and leaves 4 for the rest of its packet data. After allocating 0x16 bytes and storing it over the first parameter on the stack, the length will be stored at ShareControlHeader.totalLength
offset 0x0 followed by synchronize(31)
being stored to ShareDataHeader.PDUType2
at offset 0xe. As only 0x4 extra bytes were allocated for the entire SynchronizePDU
(0x12 for headers), the 16-bit value of synchronize(1)
is stored at offset 0x12 for SynchronizePDU.messageType
. This only initializes the first 16-bits of the SynchronizePDU
at offset 0x12 which leaves the 16-bits at offset 0x14 uninitialized. Thus when sending this packet, 16-bits will be leaked to the client.
RDPWD!ShareClass::SCInitiateSync:
f1b3583c 8bff mov edi,edi
f1b3583e 55 push ebp
f1b3583f 8bec mov ebp,esp
f1b35841 56 push esi
f1b35842 8b7508 mov esi,dword ptr [ebp+8] ; ShareClass*
f1b35845 8b86bc030000 mov eax,dword ptr [esi+3BCh]
f1b3584b 8a80e012b4f1 mov al,byte ptr RDPWD!ShareClass::scStateTable+0x18 (f1b412e0)[eax]
f1b35851 84c0 test al,al
f1b35853 7404 je RDPWD!ShareClass::SCInitiateSync+0x1d (f1b35859)
f1b35855 3c03 cmp al,3
f1b35857 7558 jne RDPWD!ShareClass::SCInitiateSync+0x75 (f1b358b1)
RDPWD!ShareClass::SCInitiateSync+0x1d:
f1b35859 8b8e80040000 mov ecx,dword ptr [esi+480h]
f1b3585f 6a01 push 1
f1b35861 6a16 push 16h
f1b35863 8d5508 lea edx,[ebp+8] ; Result pointer
f1b35866 e8d923ffff call RDPWD!SM_AllocBuffer (f1b27c44) ; [9] Allocate 0x16 bytes of space
f1b3586b 85c0 test eax,eax
f1b3586d 7542 jne RDPWD!ShareClass::SCInitiateSync+0x75 (f1b358b1)
RDPWD!ShareClass::SCInitiateSync+0x33:
f1b3586f 8b4508 mov eax,dword ptr [ebp+8]
f1b35872 6a00 push 0
f1b35874 66c7001600 mov word ptr [eax],16h ; ShareControlHeader.totalLength
f1b35879 8b4508 mov eax,dword ptr [ebp+8]
f1b3587c 6a00 push 0 ; data stream id
f1b3587e c6400e1f mov byte ptr [eax+0Eh],1Fh ; ShareDataHeader.PDUType2 := synchronize(31)
f1b35882 8b4508 mov eax,dword ptr [ebp+8]
f1b35885 6a16 push 16h ; SharePDU length
f1b35887 6a16 push 16h ; packet length
f1b35889 66c740120100 mov word ptr [eax+12h],1 ; SynchronizePDU.messageType := synchronize(1)
f1b3588f ff7508 push dword ptr [ebp+8] ; packet data
f1b35892 56 push esi ; ShareClass*
f1b35893 e8aef6ffff call RDPWD!ShareClass::SC_SendData (f1b34f46) ; [10] Send packet with PDUType = Data(7)
f1b35898 85c0 test eax,eax
f1b3589a 7415 je RDPWD!ShareClass::SCInitiateSync+0x75 (f1b358b1)
The SynchronizePDU
has the following definition. In this structure the SynchronizePDU.messageType
field is at offset 0x12 which is initialized to synchronize(1)
by the ShareClass::SCInitiateSync
method. The field at offset 0x14, SynchronizePDU.targetUser
, is not initialized which is then leaked to the client when the packet is sent.
SynchronizeMessageType ::= INTEGER {synchronize(1)}(0..65535)
SynchronizePDU ::= SEQUENCE {
shareDataHeader ShareDataHeader, // 0x0 : +18
messageType SynchronizeMessageType, // 0x12 : +2
targetUser UserID // 0x14 : +2
}
The following code shows a disassembly of the ShareClass::SCDeactivateOther
method. This method is called to send the DeactivateOtherPDU
which is used to deactivate another client that is joined to a T.128 session. The DeactivateOtherPDU
is unique in that it uses a field with a variable size called DeactivateOtherPDU.sourceDescriptor
. Thus in order to allocate a buffer for the packet, some sizes will need to be determined before allocating. At [11], the method will calculate the string length of the field which will be added to the static length of the DeactivateOtherPDU
. After summing the size of the static part of the DeactivateOtherPDU
(0xe bytes) with the length of DeactivateOtherPDU.sourceDescriptor
, the method will pass the result to the SM_AllocBuffer
function at [12]. Once allocating a buffer with it, the method will continue initializing the ShareControlHeader
fields. As 0xe bytes was allocated for the header and fields belonging to the DeactivateOtherPDU
, only 0x6 bytes is allocated towards the ShareControlHeader
. This leaves 0x8 bytes for the DeactivateOtherPDU
. After initializing the fields for the DeactivateOtherPDU
, its sourceDescriptor
field is then copied into the buffer at offset 0xe [13]. The last field that is written to this buffer is the 16-bit deactivateId
at offset 0xa, which leaves a 16-bit field at offset 0xc uninitialized. After finishing initializing the packet, it is then sent at [14] with SM_SendData
.
RDPWD!ShareClass::SCDeactivateOther:
f1b358bc 8bff mov edi,edi
f1b358be 55 push ebp
f1b358bf 8bec mov ebp,esp
f1b358c1 51 push ecx
f1b358c2 53 push ebx
f1b358c3 8b5d08 mov ebx,dword ptr [ebp+8]
f1b358c6 56 push esi
...
f1b358c7 8d83c4030000 lea eax,[ebx+3C4h] ; [11] sourceDescriptor fetched to calculate its strlen()
f1b358cd 57 push edi
f1b358ce 8d7001 lea esi,[eax+1]
RDPWD!ShareClass::SCDeactivateOther+0x15:
f1b358d1 8a08 mov cl,byte ptr [eax]
f1b358d3 40 inc eax
f1b358d4 84c9 test cl,cl
f1b358d6 75f9 jne RDPWD!ShareClass::SCDeactivateOther+0x15 (f1b358d1)
...
f1b358d8 8b8b80040000 mov ecx,dword ptr [ebx+480h]
f1b358de 2bc6 sub eax,esi
...
f1b358e0 8d7803 lea edi,[eax+3] ; string length + 3
f1b358e3 83e7fc and edi,0FFFFFFFCh ; align string length to dword
f1b358e6 8d770e lea esi,[edi+0Eh] ; string length + 0xe
f1b358e9 6a01 push 1
f1b358eb 56 push esi ; buffer size
f1b358ec 8d5508 lea edx,[ebp+8] ; result pointer
f1b358ef 8975fc mov dword ptr [ebp-4],esi
f1b358f2 e84d23ffff call RDPWD!SM_AllocBuffer (f1b27c44) ; [12] allocate space for pdu using strlen() of sourceDescriptor
f1b358f7 85c0 test eax,eax
f1b358f9 7569 jne RDPWD!ShareClass::SCDeactivateOther+0xa8 (f1b35964)
...
f1b358fb 8b4508 mov eax,dword ptr [ebp+8]
f1b358fe 668930 mov word ptr [eax],si ; ShareControlHeader.totalLength
f1b35901 8b4508 mov eax,dword ptr [ebp+8]
f1b35904 66c740021400 mov word ptr [eax+2],14h ; ShareControlHeader.protocolVersion := 1 | ShareControlHeader.PDUType := deactivateOther(4)
f1b3590a 668b8374040000 mov ax,word ptr [ebx+474h] ; Server Channel Id (1002)
f1b35911 8b4d08 mov ecx,dword ptr [ebp+8]
f1b35914 66894104 mov word ptr [ecx+4],ax ; ShareControlHeader.PDUSource
f1b35918 8b4d08 mov ecx,dword ptr [ebp+8]
f1b3591b 8b8378040000 mov eax,dword ptr [ebx+478h]
f1b35921 894106 mov dword ptr [ecx+6],eax ; DeactivateOtherPDU.shareId
f1b35924 8b4508 mov eax,dword ptr [ebp+8]
f1b35927 668b4d0c mov cx,word ptr [ebp+0Ch]
f1b3592b 6689480a mov word ptr [eax+0Ah],cx ; DeactivateOtherPDU.deactivateId
...
f1b3592f 8bcf mov ecx,edi
f1b35931 8b7d08 mov edi,dword ptr [ebp+8]
f1b35934 8bc1 mov eax,ecx
f1b35936 c1e902 shr ecx,2
f1b35939 83c70e add edi,0Eh ; [13] copy
f1b3593c 8db3c4030000 lea esi,[ebx+3C4h] ; Source Descriptor
f1b35942 f3a5 rep movs dword ptr es:[edi],dword ptr [esi]
...
f1b35944 6a08 push 8 ; TS_SECURITY_PACKET.basicSecurityHeader.flags := SEC_ENCRYPT(8)
f1b35946 6a00 push 0 ; NM_ type
f1b35948 6a00 push 0 ; ChannelId key
f1b3594a 8bc8 mov ecx,eax
f1b3594c 83e103 and ecx,3
f1b3594f 6a01 push 1 ; SendData.dataPriority := high(1)
f1b35951 ff75fc push dword ptr [ebp-4] ; packet length
f1b35954 f3a4 rep movs byte ptr es:[edi],byte ptr [esi]
f1b35956 8b5508 mov edx,dword ptr [ebp+8]
f1b35959 8b8b80040000 mov ecx,dword ptr [ebx+480h]
f1b3595f e80a31ffff call RDPWD!SM_SendData (f1b28a6e) ; [14] send packet
The following structure is the definition of the DeactivateOtherPDU
. Comparing this with the previous code snippet, the lengthSourceDescriptor
field does not appear to be populated as the length that was calculated is only used for copying the sourceDescriptor
into the packet. The sending of the DeactivateOtherPDU
will thus leak the 16-bit ‘lengthSourceDescriptor` field to the client.
DeactivateOtherPDU ::= SEQUENCE {
shareControlHeader ShareControlHeader, ; 0x0 : +6
shareID ShareID, ; 0x6 : +4
deactivateID UserID, ; 0xa : +2
lengthSourceDescriptor Integer16 (1..maxSourceDescriptor), ; 0xc : +2
sourceDescriptor T50String (SIZE(1..maxSourceDescriptor)) ; 0xe : +lengthSourceDescriptor
}
In order for RDS to set up the T.128 layer of the RDP protocol and enable Multipoint Application Sharing, it was prior mentioned that the driver must negotiate capabilities with the client. This is done by the server preparing a DemandActivatePDU
to announce the available capabilities in order to allow the client to confirm them by responding with a ConfirmActivePDU
. In order to send this PDU, the following code is executed from the WDWDDConnect
function. Once clearing an event, the ShareClass::SC_CreateShare
method is called at [15]. This method performs a number of actions related to fetching the capabilities that are supported by the server and preparing them prior to submission to the client for negotiation. After preparations, the method will first allocate a buffer for the DemandActivePDU
at [16]. The DemandActivePDU
contains two variable-length fields and as a result of this, their lengths are summed in order to produce the total length that gets passed to the SM_AllocBuffer
function. The first variable-length field is named DemandActivePDU.sourceDescriptor
and is a null-terminated string. The next variable-length field is an array of all the supported capabilities for the client to confirm. After allocating space for the packet which includes the size of these two fields, the method will then initialize the ShareControlHeader
fields that are prefixed at the beginning of the packet. Similar to all packets that are sent with the SM_SendData
function, the ShareControlHeader.PDUType
field is set at [17]. As this PDU is a DemandActivePDU
, the ShareConotrolHeader.PDUType
field is set to demandActive(1)
. After assigning the server channel identifier (1002) to ShareControlHeader.PDUSource
, the length of the two variable-length fields are then assigned at [18]. Once that has been accomplished, the variable-length fields will then be copied into the allocated buffer. The first variable-length field in the packet is the DemandActivePDU.sourceDescriptor
which is copied at [19]. Immediately following this is the array of capabilities which are copied at [20]. After everything has been copied into the allocated buffer, the DemandActivePDU.SessionId
field is written before the entire packet is finally sent at [21].
RDPWD!WDWDDConnect+0x1fa:
f1b2d098 ff7644 push dword ptr [esi+44h]
f1b2d09b ff15fc0fb4f1 call dword ptr [RDPWD!_imp__KeClearEvent (f1b40ffc)]
f1b2d0a1 53 push ebx ; ShareClass*
f1b2d0a2 e89d700000 call RDPWD!ShareClass::SC_CreateShare (f1b34144) ; [15] \
f1b2d0a7 85c0 test eax,eax
f1b2d0a9 0f840ffeffff je RDPWD!WDWDDConnect+0x20 (f1b2cebe)
...
f1b2d0af 6860ea0000 push 0EA60h ; 60000ms (1 minute)
f1b2d0b4 ff7644 push dword ptr [esi+44h]
f1b2d0b7 56 push esi
f1b2d0b8 e8d1d6ffff call RDPWD!WDW_WaitForConnectionEvent (f1b2a78e) ; wait for event
f1b2d0bd 837e5800 cmp dword ptr [esi+58h],0
f1b2d0c1 0f84f7fdffff je RDPWD!WDWDDConnect+0x20 (f1b2cebe)
f1b2d0c7 3d02010000 cmp eax,102h
f1b2d0cc 7539 jne RDPWD!WDWDDConnect+0x269 (f1b2d107)
...
f1b2d0ce 8b4710 mov eax,dword ptr [edi+10h]
f1b2d0d1 6a00 push 0
f1b2d0d3 53 push ebx
f1b2d0d4 894314 mov dword ptr [ebx+14h],eax
f1b2d0d7 e8ac7d0000 call RDPWD!ShareClass::SC_EndShare (f1b34e88) ; send DeactivateAllPDU
\
RDPWD!ShareClass::SC_CreateShare+0x775:
f1b348b9 8b853cfeffff mov eax,dword ptr [ebp-1C4h] ; length of combined capabilities
f1b348bf 83e6fc and esi,0FFFFFFFCh
f1b348c2 8d440612 lea eax,[esi+eax+12h] ; sum source descriptor and combined capabilities
f1b348c6 6a01 push 1
f1b348c8 50 push eax
f1b348c9 8d9548feffff lea edx,[ebp-1B8h] ; result pointer
f1b348cf 89b538feffff mov dword ptr [ebp-1C8h],esi
f1b348d5 898544feffff mov dword ptr [ebp-1BCh],eax
f1b348db e86433ffff call RDPWD!SM_AllocBuffer (f1b27c44) ; [16] allocate buffer for DemandActivePDU
f1b348e0 85c0 test eax,eax
f1b348e2 0f85fe000000 jne RDPWD!ShareClass::SC_CreateShare+0x8a2 (f1b349e6)
...
f1b34904 6a08 push 8 ; TS_SECURITY_PACKET.basicSecurityHeader.flags := SEC_ENCRYPT(8)
f1b34906 898778040000 mov dword ptr [edi+478h],eax
f1b3490c 8b8548feffff mov eax,dword ptr [ebp-1B8h]
f1b34912 668910 mov word ptr [eax],dx ; SharePDU.totalLength
f1b34915 8b8548feffff mov eax,dword ptr [ebp-1B8h]
f1b3491b 66c740021100 mov word ptr [eax+2],11h ; [17] ShareControlHeader.protocolVersion := 1 | ShareControlHeader.PDUType := demandActive(1)
f1b34921 668b01 mov ax,word ptr [ecx] ; Server Channel Id (1002)
f1b34924 8b8d48feffff mov ecx,dword ptr [ebp-1B8h]
f1b3492a 66894104 mov word ptr [ecx+4],ax ; ShareControlHeader.PDUSource
f1b3492e 8b8778040000 mov eax,dword ptr [edi+478h]
f1b34934 8b8d48feffff mov ecx,dword ptr [ebp-1B8h]
f1b3493a 894106 mov dword ptr [ecx+6],eax ; DemandActivePDU.ShareId
f1b3493d 8b8548feffff mov eax,dword ptr [ebp-1B8h]
f1b34943 6689700a mov word ptr [eax+0Ah],si ; [18] DemandActivePDU.lengthSourceDescriptor
f1b34947 8b8548feffff mov eax,dword ptr [ebp-1B8h]
f1b3494d 668b8d3cfeffff mov cx,word ptr [ebp-1C4h]
f1b34954 6689480c mov word ptr [eax+0Ch],cx ; [18] DemandActivePDU.lengthCombinedCapabilities
...
f1b34958 8bbd48feffff mov edi,dword ptr [ebp-1B8h]
f1b3495e 8bce mov ecx,esi
f1b34960 8bb534feffff mov esi,dword ptr [ebp-1CCh] ; Source Descriptor
f1b34966 8bc1 mov eax,ecx
f1b34968 c1e902 shr ecx,2
f1b3496b 83c70e add edi,0Eh ; DemandActivePDU.sourceDescriptor
f1b3496e f3a5 rep movs dword ptr es:[edi],dword ptr [esi] ; [19] copy source descriptor
f1b34970 8bc8 mov ecx,eax
f1b34972 83e103 and ecx,3
f1b34975 f3a4 rep movs byte ptr es:[edi],byte ptr [esi]
...
f1b34977 8b9548feffff mov edx,dword ptr [ebp-1B8h]
f1b3497d 8b8d3cfeffff mov ecx,dword ptr [ebp-1C4h]
f1b34983 8bb530feffff mov esi,dword ptr [ebp-1D0h] ; Combined Capabilities
f1b34989 8d7c100e lea edi,[eax+edx+0Eh] ; DemandActivePDU.combinedCapabilities
f1b3498d 8bd1 mov edx,ecx
f1b3498f c1e902 shr ecx,2
f1b34992 f3a5 rep movs dword ptr es:[edi],dword ptr [esi] ; [20] copy capabilities
f1b34994 8bca mov ecx,edx
f1b34996 8b9540feffff mov edx,dword ptr [ebp-1C0h]
f1b3499c 83e103 and ecx,3
f1b3499f f3a4 rep movs byte ptr es:[edi],byte ptr [esi]
...
f1b349a1 8b8d3cfeffff mov ecx,dword ptr [ebp-1C4h] ; Combined Capabilites Length
f1b349a7 8bb52cfeffff mov esi,dword ptr [ebp-1D4h] ; ShareClass*
f1b349ad 53 push ebx ; NM_ type
f1b349ae 03c1 add eax,ecx
f1b349b0 8b8d48feffff mov ecx,dword ptr [ebp-1B8h]
f1b349b6 53 push ebx ; Network Channel Id
f1b349b7 6a01 push 1 ; SendData.dataPriority := high(1)
f1b349b9 ffb544feffff push dword ptr [ebp-1BCh] ; Packet length
f1b349bf 8954080e mov dword ptr [eax+ecx+0Eh],edx ; DemandActivePDU.SessionId
f1b349c3 8b9548feffff mov edx,dword ptr [ebp-1B8h] ; Packet data
f1b349c9 8b8e80040000 mov ecx,dword ptr [esi+480h]
f1b349cf e89a40ffff call RDPWD!SM_SendData (f1b28a6e) ; [21] send packet
f1b349d4 898544feffff mov dword ptr [ebp-1BCh],eax
In the prior disassembly, the capabilities and their length were copied from a pointer that was located within the function’s frame which is initialized by the ShareClass::CPC_GetCombinedCapabilities
method. This is shown in the following disassembly with the pointer being initialized near the beginning of the method at 1d0(%ebp)
with its length at 1c4(%ebp)
. Both of the described locations within the frame are passed to the ShareClass::CPC_GetCombinedCapabilities
method at [22]. Depending on its second parameter, this method will grab a property out of the ShareClass
instance beginning at 58(%eax)
[23]. Once this property containing the capabilities has been fetched, the capabilities and their length will then be written to the method’s parameters at [24] before returning to the caller.
RDPWD!ShareClass::SC_CreateShare+0x6f3:
f1b34837 8d8530feffff lea eax,[ebp-1D0h] ; Combined Capabilities
f1b3483d 50 push eax
f1b3483e 8d853cfeffff lea eax,[ebp-1C4h] ; Combined Capabilities Length
f1b34844 50 push eax
f1b34845 53 push ebx ; Index
f1b34846 57 push edi ; ShareClass*
f1b34847 e84cd4ffff call RDPWD!ShareClass::CPC_GetCombinedCapabilities (f1b31c98) ; [22] \
\
RDPWD!ShareClass::CPC_GetCombinedCapabilities:
f1b31c98 8bff mov edi,edi
f1b31c9a 55 push ebp
f1b31c9b 8bec mov ebp,esp
f1b31c9d 8b450c mov eax,dword ptr [ebp+0Ch] ; Index
f1b31ca0 56 push esi
f1b31ca1 33f6 xor esi,esi
f1b31ca3 3bc6 cmp eax,esi
f1b31ca5 7508 jne RDPWD!ShareClass::CPC_GetCombinedCapabilities+0x17 (f1b31caf)
...
RDPWD!ShareClass::CPC_GetCombinedCapabilities+0xf:
f1b31ca7 8b4508 mov eax,dword ptr [ebp+8] ; ShareClass*
f1b31caa 8b4058 mov eax,dword ptr [eax+58h] ; [23] pointer to capabilities
f1b31cad eb0d jmp RDPWD!ShareClass::CPC_GetCombinedCapabilities+0x24 (f1b31cbc)
...
RDPWD!ShareClass::CPC_GetCombinedCapabilities+0x17:
f1b31caf 8b4d08 mov ecx,dword ptr [ebp+8]
f1b31cb2 8d448158 lea eax,[ecx+eax*4+58h]
f1b31cb6 3930 cmp dword ptr [eax],esi
f1b31cb8 7423 je RDPWD!ShareClass::CPC_GetCombinedCapabilities+0x45 (f1b31cdd)
...
RDPWD!ShareClass::CPC_GetCombinedCapabilities+0x22:
f1b31cba 8b00 mov eax,dword ptr [eax]
...
RDPWD!ShareClass::CPC_GetCombinedCapabilities+0x24:
f1b31cbc 0fb710 movzx edx,word ptr [eax]
f1b31cbf 3bd6 cmp edx,esi
f1b31cc1 8d4804 lea ecx,[eax+4]
f1b31cc4 7609 jbe RDPWD!ShareClass::CPC_GetCombinedCapabilities+0x37 (f1b31ccf)
...
RDPWD!ShareClass::CPC_GetCombinedCapabilities+0x37:
f1b31ccf 8b5510 mov edx,dword ptr [ebp+10h] ; [24] result length
f1b31cd2 2bc8 sub ecx,eax
f1b31cd4 890a mov dword ptr [edx],ecx
f1b31cd6 8b4d14 mov ecx,dword ptr [ebp+14h] ; [24] result capabilities
f1b31cd9 8901 mov dword ptr [ecx],eax
f1b31cdb eb0a jmp RDPWD!ShareClass::CPC_GetCombinedCapabilities+0x4f (f1b31ce7)
RDPWD!ShareClass::CPC_GetCombinedCapabilities+0x45:
f1b31cdd 8b4510 mov eax,dword ptr [ebp+10h] ; [24] result length
f1b31ce0 8930 mov dword ptr [eax],esi
f1b31ce2 8b4514 mov eax,dword ptr [ebp+14h] ; [24] result capabilities
f1b31ce5 8930 mov dword ptr [eax],esi
The previous disassembly has shown that the ShareClass
object contains its capabilities at offset +0x58 of the instance. This property is initialized by the WDWDDConnect
function which is responsible for calling the ShareClass::SC_CreateShare
method as was described. In order to initialize the capabilities that are used later, the WDWDDConnect
function will call upon the ShareClass::DCS_Init
method after instantiating the ShareClass
at [25]. Firstly, the ShareClass::DCS_Init
method will call ShareClass::SC_Init
at [26] which will populate a number of fields that are used when sending some of the PDU types. At [27], the method will copy the `sourceDescriptor from a global and store it at offset +0x3c4 of the class.
RDPWD!WDWDDConnect+0xc3:
f1b2cf61 8b4710 mov eax,dword ptr [edi+10h]
f1b2cf64 894314 mov dword ptr [ebx+14h],eax
f1b2cf67 ff765c push dword ptr [esi+5Ch]
f1b2cf6a 56 push esi
f1b2cf6b 53 push ebx ; ShareClass*
f1b2cf6c e809500000 call RDPWD!ShareClass::DCS_Init (f1b31f7a) ; [25] \
f1b2cf71 83631400 and dword ptr [ebx+14h],0
f1b2cf75 85c0 test eax,eax
f1b2cf77 0f848f000000 je RDPWD!WDWDDConnect+0x16e (f1b2d00c)
\
RDPWD!ShareClass::DCS_Init+0x5b:
f1b31fd5 ff7510 push dword ptr [ebp+10h]
f1b31fd8 c7030068c461 mov dword ptr [ebx],61C46800h
f1b31fde 56 push esi
f1b31fdf 897b04 mov dword ptr [ebx+4],edi
f1b31fe2 e80d200000 call RDPWD!ShareClass::SC_Init (f1b33ff4) ; [26] \
f1b31fe7 85c0 test eax,eax
f1b31fe9 0f84a0000000 je RDPWD!ShareClass::DCS_Init+0x115 (f1b3208f)
\
RDPWD!ShareClass::SC_Init:
f1b33ff4 8bff mov edi,edi
f1b33ff6 55 push ebp
f1b33ff7 8bec mov ebp,esp
f1b33ff9 51 push ecx
f1b33ffa 53 push ebx
f1b33ffb 33db xor ebx,ebx
f1b33ffd 56 push esi
f1b33ffe 8b7508 mov esi,dword ptr [ebp+8] ; ShareClass*
...
f1b340a1 8b07 mov eax,dword ptr [edi]
f1b340a3 8986c0030000 mov dword ptr [esi+3C0h],eax
f1b340a9 8b450c mov eax,dword ptr [ebp+0Ch]
f1b340ac 898680040000 mov dword ptr [esi+480h],eax
f1b340b2 a1ea3fb3f1 mov eax,dword ptr [RDPWD!ShareClass::UP_UpdateHeaderSize+0x28 (f1b33fea)]
f1b340b7 6800200000 push 2000h
f1b340bc 8986c4030000 mov dword ptr [esi+3C4h],eax ; [27] Store source descriptor
f1b340c2 e873340000 call RDPWD!IcaBufferGetUsableSpace (f1b3753a)
f1b340c7 6800400000 push 4000h
f1b340cc 898698040000 mov dword ptr [esi+498h],eax
f1b340d2 e863340000 call RDPWD!IcaBufferGetUsableSpace (f1b3753a)
f1b340d7 89869c040000 mov dword ptr [esi+49Ch],eax
Returning from the ShareClass::SC_Init
method, the caller will then call two more methods one of which is displayed in the following assembly. The call to ShareClass::CPC_Init
at [28] shows that the address of the properties at 68(%ecx)
are written to 58(%ecx)
which is where the capabilities that are being written to the DemandActivePDU
are coming from. After writing the address at [29], the method returns and then begins to register the very first capability by calling the ShareClass::SC_SetCapabilities
method.
RDPWD!ShareClass::DCS_Init+0x75:
f1b31fef 56 push esi
f1b31ff0 e877fbffff call RDPWD!ShareClass::CPC_Init (f1b31b6c) ; [28] \
f1b31ff5 56 push esi
f1b31ff6 e8e1300000 call RDPWD!ShareClass::SC_SetCapabilities (f1b350dc)
f1b31ffb 33c0 xor eax,eax
\
RDPWD!ShareClass::CPC_Init:
f1b31b6c 8bff mov edi,edi
f1b31b6e 55 push ebp
f1b31b6f 8bec mov ebp,esp
f1b31b71 8b4d08 mov ecx,dword ptr [ebp+8]
f1b31b74 57 push edi
f1b31b75 33c0 xor eax,eax
f1b31b77 8d795c lea edi,[ecx+5Ch] ; initialize properties
f1b31b7a ab stos dword ptr es:[edi]
f1b31b7b ab stos dword ptr es:[edi]
f1b31b7c ab stos dword ptr es:[edi]
f1b31b7d 8d4168 lea eax,[ecx+68h] ; capabilities pointer
f1b31b80 894158 mov dword ptr [ecx+58h],eax ; [29] Write capabilities pointer to +58
f1b31b83 66832000 and word ptr [eax],0
f1b31b87 5f pop edi
f1b31b88 5d pop ebp
f1b31b89 c20400 ret 4
The first capability that is registered is done by ShareClass::SC_SetCapabilities
. The actual registration of a capability requires a caller to initialize a structure for the capability’s data and pass a pointer to said buffer along with its length to the ShareClass::CPC_RegisterCapabilities
method. The following disassembly shows a TS_SHARE_CAPABILITYSET
with a size of 0x8 bytes being initialized on the stack and then registered. The server’s channel identifier (1002) is first fetched from a property belonging to the ShareClass
, and then written to offset 0x4 of the stack buffer. The next value that is written is the capability set’s 16-bit type which is set to shareCapabilitySet(9)
. Once these actions have been performed, the buffer will then be passed to ShareClass::CPC_RegisterCapabilities
in order to register the capability. Prior to real capability registration, the ShareClass::CPC_RegisterCapabilities
method will take the length passed by the caller via one of its parameters at [32] and then write the length to offset 0x2 of the capability data. Thus this is the only field that the method expects the caller to leave uninitialized. Looking back at the capability data prior to being passed to the ShareClass::CPC_RegisterCapabilities
method, only two fields, the 16-bit TS_SHARE_CAPABILITYSET.capabilitySetType
at offset 0x0 and the 16-bit TS_SHARE_CAPABILITYSET.nodeId
at offset 0x4 are initialized. As the ShareClass::CPC_RegisterCapabilities
method initializes the 16-bit length at offset 0x2, this leaves the 16-bits at offset 0x6 uninitialized.
RDPWD!ShareClass::SC_SetCapabilities:
f1b350dc 8bff mov edi,edi
f1b350de 55 push ebp
f1b350df 8bec mov ebp,esp
f1b350e1 51 push ecx
f1b350e2 51 push ecx
f1b350e3 8b4508 mov eax,dword ptr [ebp+8] ; ShareClass*
f1b350e6 668b8874040000 mov cx,word ptr [eax+474h] ; Server Channel Id (1002)
f1b350ed 66894dfc mov word ptr [ebp-4],cx ; [30] TS_SHARE_CAPABILITYSET.nodeId
f1b350f1 6a08 push 8 ; capability data length
f1b350f3 8d4df8 lea ecx,[ebp-8] ; capability buffer to use
f1b350f6 51 push ecx ; capability data
f1b350f7 50 push eax
f1b350f8 66c745f80900 mov word ptr [ebp-8],9 ; [31] TS_SHARE_CAPABILITYSET.capabilitySetType := shareCapabilitySet(9)
f1b350fe e8bdcaffff call RDPWD!ShareClass::CPC_RegisterCapabilities (f1b31bc0)
f1b35103 c9 leave
f1b35104 c20400 ret 4
\
RDPWD!ShareClass::CPC_RegisterCapabilities:
f1b31bc0 8bff mov edi,edi
f1b31bc2 55 push ebp
f1b31bc3 8bec mov ebp,esp
f1b31bc5 53 push ebx
f1b31bc6 668b5d10 mov bx,word ptr [ebp+10h] ; [32] capability length
f1b31bca 6685db test bx,bx
f1b31bcd 744e je RDPWD!ShareClass::CPC_RegisterCapabilities+0x5d (f1b31c1d)
...
f1b31bcf 8b5508 mov edx,dword ptr [ebp+8] ; ShareClass*
f1b31bd2 56 push esi
f1b31bd3 8b750c mov esi,dword ptr [ebp+0Ch] ; capability data
f1b31bd6 66895e02 mov word ptr [esi+2],bx ; [32] CapabilitySet.lengthCapability
...
RDPWD!ShareClass::CPC_RegisterCapabilities+0x5d:
f1b31c1d 5b pop ebx
f1b31c1e 5d pop ebp
f1b31c1f c20c00 ret 0Ch
The TS_SHARE_CAPABILITYSET
has the following structure. As prior mentioned, it is the 16-bits at offset 0x6 of this structure that was mistakenly uninitialized by the caller. This offset references the TS_SHARE_CAPABILITYSET.pad2octets
field. As a result in the capabilities that are sent to the client via the DemandActivePDU
, the 16-bits in this field will reference uninitialized data which will leak these 16-bits to the remote client.
struct TS_SHARE_CAPABILITYSET {
uint16_t capabilitySetType; // 0x0 : +2
uint16_t lengthCapability; // 0x2 : +2
uint16_t nodeId; // 0x4 : +2
uint16_t pad2octets; // 0x6 : +2
}
After initializing a number of other capabilities, the ShareClass::DCS_Init
method will eventually execute the following code. This will call the ShareClass::IM_Init
method at [33]. This method is responsible for registering the TS_INPUT_CAPABILITYSET
capability into the ShareClass
. At [34], the method will pass the capability’s length (0x58) and its buffer for the capability data to the ShareClass::CPC_RegisterCapabilities
method. Prior to calling the method, a few fields in the buffer will be initialized. This includes the TS_INPUTCAPABILITYSET.capabilitySetType
being set to inputCapabilitySet(13)
, followed by its length and some flags. As the capability’s length is 0x58 and only 6 bytes are initialized, this can leak 0x52 bytes of data to the client.
RDPWD!ShareClass::DCS_Init+0x100:
f1b3207a 56 push esi
f1b3207b e8f8080000 call RDPWD!ShareClass::IM_Init (f1b32978) ; [33] \
\
RDPWD!ShareClass::IM_Init:
f1b32978 8bff mov edi,edi
f1b3297a 55 push ebp
f1b3297b 8bec mov ebp,esp
f1b3297d 83ec5c sub esp,5Ch
...
f1b329ac 6a58 push 58h ; [34] capability data length
f1b329ae 8d45a4 lea eax,[ebp-5Ch] ; capability data on stack
f1b329b1 50 push eax ; capability data
f1b329b2 52 push edx
f1b329b3 66c745a40d00 mov word ptr [ebp-5Ch],0Dh ; TS_INPUT_CAPABILITYSET.capabilitySetType := inputCapabilitySet(13)
f1b329b9 66c745a65800 mov word ptr [ebp-5Ah],58h ; TS_INPUT_CAPABILITYSET.lengthCapability
f1b329bf 66c745a83500 mov word ptr [ebp-58h],35h ; TS_INPUT_CAPABILITYSET.inputFlags := FASTPATH_INPUT2 | UNICODE | MOUSEX | SCANCODES
f1b329c5 e8f6f1ffff call RDPWD!ShareClass::CPC_RegisterCapabilities (f1b31bc0)
The TS_INPUT_CAPABILITYSET
can be defined with the following structure. In the prior disassembly, only the TS_INPUT_CAPABILITYSET.capabilitySetType
, TS_INPUT_CAPABILITYSET.lengthCapability
, and the TS_INPUT_CAPABILITYSET.inputFlags
fields are set. This leaves the other fields to be uninitialized which will leak 0x52 bytes of data to the client when the packet is sent.
struct TS_INPUT_CAPABILITYSET {
uint16_t capabilitySetType; // 0x0 : +2
uint16_t lengthCapability; // 0x2 : +2
uint16_t inputFlags; // 0x4 : +2
uint16_t pad2octetsA; // 0x6 : +2
uint32_t keyboardLayout; // 0x8 : +2
uint32_t keyboardType; // 0xc : +2
uint32_t keyboardSubType; // 0x10 : +2
uint32_t keyboardFunctionKey; // 0x14 : +2
char[64] imeFileName; // 0x18 : +64
}
Due to the expectations of the SM_SendData
function, and both the ShareClass::SC_SendData
and ShareClass::CPC_RegisterCapabilities
methods, each of these can allow leakage of stack information to a remote client. These functions/methods expect their caller to assign fields within a structure that they use which can make it easy for one to forget to clear the packet. As demonstrated, this can leak information to a client which can provide information to an attacker when trying to predict the state of the driver.
The base addresses of the functions described in the details are as follows:
kd> lm vm termdd
Browse full module list
start end module name
f350a000 f3513f00 termdd (pdb symbols) c:\mss\termdd.pdb\C04E4855F20641ECB654BB1AD575B8611\termdd.pdb
Loaded symbol image file: termdd.sys
Image path: termdd.sys
Image name: termdd.sys
Browse all global symbols functions data
Timestamp: Sun Apr 13 13:38:36 2008 (4802532C)
CheckSum: 0000DEB5
ImageSize: 00009F00
Translations: 0000.04b0 0000.04e4 0409.04b0 0409.04e4
Information from resource tables:
kd> lm vm rdpwd
Browse full module list
start end module name
f1b23000 f1b45100 RDPWD (pdb symbols) c:\mss\RDPWD.pdb\19F9DADE1E5E42CD8A2AA9DB4F26060B1\RDPWD.pdb
Loaded symbol image file: RDPWD.SYS
Image path: RDPWD.SYS
Image name: RDPWD.SYS
Browse all global symbols functions data
Timestamp: Sun Apr 13 13:38:40 2008 (48025330)
CheckSum: 0002585F
ImageSize: 00022100
Translations: 0000.04b0 0000.04e4 0409.04b0 0409.04e4
Information from resource tables:
To run the provided proof-of-concept, Python2 and the six
module must be installed. To install the six
module, one can simply use pip
as follows:
$ pip install six
After installing the required modules, the proof-of-concept can then be run with the following command against an XP machine. For furhter help, the --help
parameter has also been implemented.
$ python poc.zip $host
After running the proof-of-concept, a number of structures will then be emitted to the screen after negotiating capabilities. These structures contain information that was leaked with the vulnerabilities described in this document.
These information leaks exist only on the RDP implementation provided by the Windows XP platform. Other implementations for platforms such as Windows 7 (and newer) properly initialize these structures with memset()
and therefore it is recommended to use versions newer than Windows XP if one does not want to be exposed to these vulnerabilities.
2019-09-19 - Vendor Disclosure
2019-10-29 - 30 day follow up
2019-10-30 - Vendor advised no fix planned due to product end of life
2019-12-04 - CVE assigned
2019-12-10 - Public Release
Discovered by a member of Cisco Talos.