Talos Vulnerability Report

TALOS-2019-0883

LEADTOOLS libltdic.so LDicomAssociate::SetBinary denial-of-service vulnerability

December 10, 2019
CVE Number

CVE-2019-5091

Summary

An exploitable denial-of-service vulnerability exists in the Dicom-packet parsing functionality of LEADTOOLS libltdic.so version 20. A specially crafted packet can cause an infinite loop, resulting in a denial of service. An attacker can send a packet to trigger this vulnerability.

Tested Versions

LEADTOOLS libltdic.so 20.0.2019.3.15

Product URLs

https://www.leadtools.com/

CVSSv3 Score

7.5 - CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

CWE

CWE-835: Loop with Unreachable Exit Condition ('Infinite Loop')

Details

LEADTOOLS, according to the website, "is a collection of comprehensive toolkits to integrate document, medical, multimedia, and imaging technologies into desktop, server, tablet, and mobile applications." It offers pre-built and portable libraries with an SDK for most platforms (Windows, Linux, Android, etc.), that are all geared toward building applications for medical systems. For the purposes of this post, we will be discussing LEADTOOLS' DICOM network protocol code, which is mainly used for transferring and viewing medical image information remotely.

When dealing with the LEADTOOLS DICOM code, the main function that actually leads to network library code is L_LTDIC_API L_INT L_DicomListen(hNet, pszHostAddress, nHostPort, nNbPeers). This function will take an initialized piece of memory (hNet), an IP address and port to listen on (pszHostAddress/nHostPort), a max amount of peers to listen to (nNbPeers), and then spin up a network server that implements the actual network logic. For dealing with certain predefined function types, callbacks are created and then assigned to predefined hooks within the hNet object, for example, when looking through the SDK's example code:

void OnReceiveAssociateRequest(HDICOMNET dicomNet, HDICOMPDU dicomPDU, void *userData)
void OnReceiveCEchoRequest(HDICOMNET dicomNet, L_UCHAR presentationID, L_UINT16 messageID, L_TCHAR *className, void *userData)
[..]

Since these functions are, in the end, up to the developers to implement, they are not really valid targets, but in order to get to these callbacks, there is still a bit of code connecting the packet read to the callback hooks found in the libltdic.so library. Since LEADTOOLS' middleware libraries are not opensource, we must examine the disassembly of a singular function to proceed. After passing the first six bytes of the packet, which are the opcode and size of the input, we end up hitting a jumptable of the opcodes inside LDicomNet::Receive(int flag?,uchar opcode,uchar *buffer,uint buffer_len), which looks like such:

PDU_UNKNOWN    [0x00] Unknown data sent.
PDU_ASSOCIATE_REQUEST  [0x01] Associate Request message sent
PDU_ASSOCIATE_ACCEPT   [0x02] Associate Accept message sent
PDU_ASSOCIATE_REJECT   [0x03] Associate Reject message sent
PDU_DATA_TRANSFER  [0x04] Data transfer made.
PDU_RELEASE_REQUEST    [0x05] Release Request message sent
PDU_RELEASE_RESPONSE   [0x06] Release Response message sent
PDU_ABORT  [0x07] Abort message sent.

When dealing with the PDUASSOCIATEREQUEST, the server parses out all the options that are needed for the conversation, the server name, the client name, the presentation conext, and a bunch of other high level structures of the Dicom protocol. For a more visual representation of the packet that will be discussed, please refer to: https://www.leadtools.com/help/leadtools/v20/dicom/api/associate-request-pdu.html.

In the function that parses all of these fields, LDicomAssociate::SetBinary(char *,uint *,LDicomAssociate*), when dealing with the one or more "Presentation Context", each may contain one "Abstract Syntax"(tag:0x30) and one or more "Transfer Syntax"(tag:0x40). The code for examining and parsing these starts at the following disassembly:

// LDicomAssociate::SetBinary(char *,uint *,LDicomAssociate*)+228
.text:00007FFFF62FB57E loop_top:
.text:00007FFFF62FB57E                 cmp     cl, 30h ; '0'
.text:00007FFFF62FB581                 jz      short loc_7FFFF62FB5D0
.text:00007FFFF62FB583                 cmp     cl, 40h ; '@'
.text:00007FFFF62FB586                 jnz     short loc_7FFFF62FB57E  ; loop_top

Unfortunately, cl is a byte of user input, and as one might discern from a brief glance of this code chunk, if cl is not 0x30 or 0x40, the code runs in an infinite loop, rendering the server disabled.

Crash Information

-------------------------------------------------------------------------[ registers ]---- $rax : 0x0000000000000000 $rbx : 0x0000000000000073 $rcx : 0x0000000000000000 $rdx : 0x0000000000000000 $rsp : 0x00007ffff093bc00 -> 0x0000000000000000 $rbp : 0x000062700000aa20 -> 0x454c000000000001 $rsi : 0x0000000000000073 $rdi : 0x000060d00015ec5a -> 0x0000000000000000 $rip : 0x00007ffff62fb57e -> <LDicomAssociate::SetBinary(char,+0> cmp cl, 0x30 $r8 : 0x0000000000000000 $r9 : 0x00000c1a80023d98 -> 0xfafafafafafafafa $r10 : 0x00000000000004e7 $r11 : 0x00007ffff4ef5fa8 -> 0xfff37538fff37528 $r12 : 0x0000000000000000 $r13 : 0x00007ffff093bc30 -> 0x0000000000000000 $r14 : 0x000061600027d580 -> 0x00002b0200000001 $r15 : 0x0000000000000095 $eflags: [CARRY PARITY adjust zero SIGN trap INTERRUPT direction overflow resume virtualx86 identification] -----------------------------------------------------------------------------[ stack ]---- 0x00007ffff093bc00|+0x00: 0x0000000000000000 <-$rsp 0x00007ffff093bc08|+0x08: 0x00007ffff093bcfc -> 0x0000000000000231 0x00007ffff093bc10|+0x10: 0x000062700000aa20 -> 0x454c000000000001 0x00007ffff093bc18|+0x18: 0x000062700000aaf8 -> 0xbebebebebebebe00 0x00007ffff093bc20|+0x20: 0x000062700000aaa8 -> "1.2.840.10008.3.1.1.1" 0x00007ffff093bc28|+0x28: 0x0000000000000001 0x00007ffff093bc30|+0x30: 0x0000000000000000 <-$r13 0x00007ffff093bc38|+0x38: 0x0000000000000000 -------------------------------------------------------------------------[ boop ]---- 0x7ffff62fb568 <LDicomAssociate::SetBinary(char,+0> movzx r12d, ax 0x7ffff62fb56c <LDicomAssociate::SetBinary(char,+0> lea ebx, [r12+rsi1] 0x7ffff62fb570 <LDicomAssociate::SetBinary(char,+0> cmp r15d, ebx 0x7ffff62fb573 <LDicomAssociate::SetBinary(char,+0> jb 0x7ffff62fb3d0 <ZN15LDicomAssociate9SetBinaryEPcPjPS+128> 0x7ffff62fb579 <LDicomAssociate::SetBinary(char,+0> movzx ecx, BYTE PTR [r14+rcx1] ->0x7ffff62fb57e <LDicomAssociate::SetBinary(char,+0> cmp cl, 0x30 0x7ffff62fb581 <LDicomAssociate::SetBinary(char,+0> je 0x7ffff62fb5d0 <ZN15LDicomAssociate9SetBinaryEPcPjPS+640> 0x7ffff62fb583 <LDicomAssociate::SetBinary(char,+0> cmp cl, 0x40 0x7ffff62fb586 <LDicomAssociate::SetBinary(char,+0> jne 0x7ffff62fb57e <ZN15LDicomAssociate9SetBinaryEPcPjPS+558> 0x7ffff62fb588 <LDicomAssociate::SetBinary(char,+0> cmp ax, 0x40 0x7ffff62fb58c <LDicomAssociate::SetBinary(char,+0> ja 0x7ffff62fb618 <ZN15LDicomAssociate9SetBinaryEPcPjPS+712> -----------------------------------------------------------------------------[ trace ]----

0 0x00007ffff62fb57e in LDicomAssociate::SetBinary(char, unsigned int, LDicomAssociate*) () from /boop/LEADTOOLS/Bin/Lib/x64/libltdic.so.20

1 0x00007ffff6352133 in LDicomNet::Receive(int, unsigned char, unsigned char*, unsigned int) () from /boop/LEADTOOLS/Bin/Lib/x64/libltdic.so.20

2 0x00007ffff6352f89 in ?? () from /boop/LEADTOOLS/Bin/Lib/x64/libltdic.so.20

3 0x00007ffff47774a4 in startthread () from /lib/x8664-linux-gnu/libpthread.so.0

4 0x00007ffff4e81d0f in clone () from /lib/x86_64-linux-gnu/libc.so.6


Timeline

2019-08-08 - Vendor Disclosure
2019-09-06 - 30 day follow up with vendor re: case number assigned
2019-09-09 - Vendor acknowledged issue under review
2019-10-21 - 60+ day follow up
2019-12-05 - Vendor patched
2019-12-10 - Public Release

Credit

Discovered by Lilith [-_-]zzzZZZ of Cisco Talos.