BlueGate Internals

While I was on vacations, there was a patch on RD Gateway CVE-2020-0609 and CVE-2020-0610, I never listen about a Gateway on Remote Desktop so I found it interesting to analyze.

I stopped sunbathing on the beach, turned on the laptop and started. After doing a PoC that worked correctly triggering a DoS, some post began to apper about that. Then, I was in doubt if I should write about it, and here we are.

RD Gateway

RD Gateway, previously Terminal Services Gateway (TS Gateway), has a business focus, allows routing for a Remote Desktop out side of the enterprise. RD Gateway allows use several policies to users can authenticate, then the gateway will forward RDP traffic to the windows machine specified by the user, allowing only the gateway to be exposed to the internet and not access to RDP directly.

You can access to RD Gateway using two protocols, DTLS over UDP, and HTTP over TLS. Both make a socket, binding on the respective port, set the socket mode calling to WSAIoctl, and finally calls CreateThreadpoolIo to create i/o thread pool, setting CAAUdpServerTransport::IoCompletionCallback function as callback for UDP, and CAAHttpServerTransport::IoCompletionCallback for HTTP, .

RD Gateway HTTP protocol allows websocket and processing requests scheme (see the link for more information). For the last one, the API supplies a request HTTP_REQUEST_V2 structure to parse in CAAHttpServerTransport::ParseRequest, after that follows his path hentication based on the arguments passed.

RD Gateway UDP protocol allows large message, but when a large message is processed it will be split in multiple separate fragments which will be rebuilt later. To interact with the client for receiving and sending data only two main functions are used: CAAUdpServerTransport::HandleRecvComplete and CAAUdpServerTransport::HandleSendComplete.

BlueGate

Both vulnerabilities CVE-2020-0609 and CVE-2020-0610 exist in CAAUdpConnection::HandleReceiveDataWhenConxStateIsSecured. But, how do I get there?

The server change its status according to the operation being processed, for example when it is starting the DTLS handshake (state 2), authentication (state 3), finishing (state 5), calling CAAUdpConnection::ChangeServerState function. After DTLS handshake I can reach the CAAUdpConnection::HandleReceiveDataWhenConxStateIsSecured function (state 3).

0:024> k
 # Child-SP          RetAddr           Call Site
00 0000001e`1cb7e320 00007ffc`991c4daa aaedge!CAAUdpConnection::HandleReceiveDataWhenConxStateIsSecured+0x241
01 0000001e`1cb7f0f0 00007ffc`991cb490 aaedge!CAAUdpConnection::OnReceiveDataComplete+0x366
02 0000001e`1cb7f3c0 00007ffc`991cb0c8 aaedge!CAAUdpServerTransport::HandleRecvComplete+0x3a0
03 0000001e`1cb7f480 00007ffc`be5c6760 aaedge!CAAUdpServerTransport::IoCompletionCallback+0x108
04 0000001e`1cb7f4b0 00007ffc`c134ee19 KERNEL32!BasepTpIoCallback+0x50
05 0000001e`1cb7f500 00007ffc`c1350348 ntdll!TppIopExecuteCallback+0x129
06 0000001e`1cb7f580 00007ffc`be5c7974 ntdll!TppWorkerThread+0x3c8
07 0000001e`1cb7f870 00007ffc`c136a271 KERNEL32!BaseThreadInitThunk+0x14
08 0000001e`1cb7f8a0 00000000`00000000 ntdll!RtlUserThreadStart+0x21

 

To build a valid packet we can check the CAAUdpConnection::ValidatePacket(CAAUdpConnection *this, struct UDP_FRAGMENT *pkt, unsigned int pkt_len) function, then set a bp there and send data.

conn.send("A"*0xFF)
0:024> r rdx, r8, r13
rdx=0000020e74be206d r8=00000000000000ff r13=0000020e74be206d
0:024> dq rdx l6
0000020e`74be206d  41414141`41414141 41414141`41414141
0000020e`74be207d  41414141`41414141 41414141`41414141
0000020e`74be208d  41414141`41414141 41414141`41414141

For this function register rdx is the packet address, r13 for the caller, and r8 is the packet length.

.text:00000001800685D3                 movzx   ecx, word ptr [rdx]
.text:00000001800685D6                 sub     ecx, 1
.text:00000001800685D9                 jz      short loc_18006860A
.text:00000001800685DB                 sub     ecx, 2
.text:00000001800685DE                 jz      short loc_180068600
.text:00000001800685E0                 sub     ecx, 1
.text:00000001800685E3                 jz      short loc_1800685FA
.text:00000001800685E5                 cmp     ecx, 1
.text:00000001800685E8                 jnz     short loc_180068602
.text:00000001800685EA                 cmp     r8d, 0Ah
.text:00000001800685EE                 jb      short loc_180068602
.text:00000001800685F0                 movzx   ecx, word ptr [rdx+8]
.text:00000001800685F4                 lea     eax, [r8-0Ah]
...
.text:0000000180068618                 cmp     eax, ecx
.text:000000018006861A                 jnb     short loc_180068600

The first word is compared with the type of the fragment and if it is 5 then [rdx+8] is the length of the fragment. We reach that conclusion because it is compared directly with r8 that is length of the packet minus a constant 0x0A (this assembly is used to subtract the header length of the full length), if this value is lower than the header length then it returns an error code, else it returns 0. Knowing all of that, I can add that information to show the decompiled C code.

v3 = 0x8000FFFF;
/* ... */
if ( packet->type != 1 )
{
  if ( packet->type != 3 )
  {
    if ( packet->type != 4 )
    {
      if ( packet->type != 5 || packet_length < 0xA )
        return v3;
      v5 = (unsigned __int16)packet->fragment_length;
      v6 = packet_length - HDR_FRGMNT_LEN; //10
      goto LABEL_19;
    }
/* ... */
 LABEL_19:
    if ( v6 < v5 )
      return 0x80070005;
    return 0;

 

Now going back to the caller, to the vulnerable function. r13 is the packet address and r14 is the this object reference.

.text:0000000180065308 movzx   ecx, word ptr [r13+6]
.text:000000018006530D mov     [r14+668h], ecx
...
.text:0000000180065322 mov     edx, ecx
...
.text:0000000180065388 movzx   ecx, word ptr [r13+4]
.text:000000018006538D cmp     ecx, edx
.text:000000018006538F jbe     loc_180065426

That assembly construction is used to check an out of bounds of index, at the end of that snippet you can see how it is used as index cmp [r14+rdx*4+568h], ecx. Then, [r13+4] is an index or ID, remember that a large message is split in multiple fragments that must carry an ID to be reconstructed, and [r13+6] is the total number of fragments that will be processed, for that reason the comparison is made, to know if all the fragments were received, jbe loc_180065426.

Another bug

Following with the same idea, when a fragment is received it will check if any previous fragment has already been received.

.text:0000000180065426 mov     rdx, rcx
.text:0000000180065429 xor     ecx, ecx
.text:000000018006542B cmp     [r14+rdx*4+568h], ecx
.text:0000000180065433 jz      short loc_180065461

If the fragment was received, the result will be true and the execution flow jump to the function epilogue. But if it is false execution flow will continue. Now, rdx is packet->fragment_id, and I control it. So if I set packet->number_of_fragments = 0xFFFF and packet->fragment_id = 0xFFFE, a crash will be triggered.

(a24.bcc): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
aaedge!CAAUdpConnection::HandleReceiveDataWhenConxStateIsSecured+0x587:
00007ff9`6e05542b 41398c9668050000 cmp     dword ptr [r14+rdx*4+568h],ecx ds:000001a5`0a44c3d0=????????
0:023> r rdx
rdx=000000000000fffe

This Windbg output show how setting the fields previously mentioned triggering a DoS.

CVE-2020-0609/0610

I know that [r13+8] is packet->fragment_length, and is comparison directly with [r14+564h]. What idea comes to your mind? Maybe be the maximum size of the buffer? Yeah that’s what it is.

.text:0000000180065461 movzx   eax, word ptr [r13+8]
.text:0000000180065466 add     eax, [r14+560h]
.text:000000018006546D cmp     eax, [r14+564h]
.text:0000000180065474 jbe     short loc_1800654C1

Remember that the fragments will be rebuilt and put in a buffer, [r14+564h] or this->buffer_maximum_size is the maximum size of the buffer and if I look at a debugger I’ll see the value 0x1000. Next, [r14+560h] is adding with packet->fragment_length, which makes us think that it saves the count of bytes that have been written. In summary, packet->fragment_length + this->bytes_written can determine if it is exceeding the maximum buffer size jbe short loc_1800654C1: if ((packet->fragment_length + this->bytes_written) <= this->buffer_maximun_size){/*continue*/}else{return err;}. Is easy to pass the check? Yes, only send the field fragment_length with a lower value than 0x1000, because the key for trigger the vulnerabilities is fragment_id. Let’s see what follows after this:

  Here I found the CVE-2020-0609 vulnerability.

.text:00000001800654C1 loc_1800654C1:
.text:00000001800654C1 mov     [r14+rdx*4+568h], r8d

This bugs has no mistery, rdx is fragment_id, and setting it with a calculated value you can overwrite inside the object :D but only with (uint32_t) 1 value, only with high addresses and relative to the position where it starts :(. Another hazard is that fragment_id should not be a high value, e.g. 0xFFFE because the memory address [r14+rdx*4+568h] that will be accessed will result in unmapped memory (see “Another bug”).

aaedge!CAAUdpConnection::HandleReceiveDataWhenConxStateIsSecured+0x61d:
00007ff9`713354c1 4589849668050000 mov     dword ptr [r14+rdx*4+568h],r8d ds:0000026c`700dbf18=00000000
0:024> r rdx
rdx=0000000000000200
0:024> dq 0000026c`700dbf18 l1
0000026c`700dbf18  00000000`00000001

  After that, it’s found the CVE-2020-0610 vulnerability.

.text:00000001800654C9 lea     r8, [r13+UDP_FRAGMENT.fragment] ; Src
.text:00000001800654CD movzx   eax, [r13+UDP_FRAGMENT.fragment_id]
.text:00000001800654D2 mov     edx, 1000       ; DstSize
.text:00000001800654D7 movzx   r9d, [r13+UDP_FRAGMENT.fragment_length] ; MaxCount
.text:00000001800654DC imul    rcx, rax, 1000
.text:00000001800654E3 add     rcx, [r14+558h] ; Dst
.text:00000001800654EA call    cs:__imp_memcpy_s

How I said, the key is fragment_id because it is used as an index without a correct check to avoid out of bounds.

memcpy_s(&this->buffer[packet->fragment_id * 1000], 1000, &packet->fragment, packet->fragment_len);
this->bytes_written += packet->fragment_len; In this vulnerability multiply `packet->fragment_id * 1000`, and if I set the same value in it, 0x200 and considering that the buffer maximum size is 0x1000  I'll write out of the buffer.

0:024> 
rcx=0000026c702ddde0 rdx=00000000000003e8 
aaedge!CAAUdpConnection::HandleReceiveDataWhenConxStateIsSecured+0x646:
00007ff9`713354ea 48ff1597900300  call    qword ptr [aaedge!_imp_memcpy_s (00007ff9`7136e588)] ds:00007ff9`7136e588={msvcrt!memcpy_s (00007ff9`943ed300)}
0:024> dq r14+558 l1
0000026c`700db708  0000026c`70260de0
0:024> ? 026c`70260de0 + (0x200*0n1000)
Evaluate expression: 2664761777632 = 0000026c`702ddde0 The buffer is allocated by `kernel32!LocalAlloc`, that means that the buffer is in another "heap" allocated on top addresses than `CAAUdpConnection` objects :(.

On the patch of those vulnerabilities added checks for fragment_id and number_of_fragments :P. if they are greater than 64 it will return an error code of 0x8000FFFF, with that information is possible does a scanner sending fragment_id = 65 and if not is vulnerable it will return the error code previously mentioned.

Finally, the UDP_FRAGMENT packet could be rebuilt as seen in the image.

UDP Fragment Layout

Written by RoP Team