Talos Vulnerability Report

TALOS-2024-1936

AutomationDirect P3-550E Programming Software Connection FiBurn heap-based buffer overflow vulnerability

May 28, 2024
CVE Number

CVE-2024-24851

SUMMARY

A heap-based buffer overflow vulnerability exists in the Programming Software Connection FiBurn functionality of AutomationDirect P3-550E 1.2.10.9. A specially crafted network packet can lead to a buffer overflow. An attacker can send an unauthenticated packet to trigger this vulnerability.

CONFIRMED VULNERABLE VERSIONS

The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.

AutomationDirect P3-550E 1.2.10.9

PRODUCT URLS

P3-550E - https://www.automationdirect.com/adc/shopping/catalog/programmable_controllers/productivity3000plcs(modular)/cpus/p3-550e

CVSSv3 SCORE

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

CWE

CWE-805 - Buffer Access with Incorrect Length Value

DETAILS

The P3-550E is the most recent CPU module released in the Productivity3000 line of Programmable Automation Controllers from AutomationDirect. It is an affordable control CPU which communicates remotely via ethernet, serial, and USB and exposes a variety of control services, including MQTT, Modbus, ENIP, and an unnamed programming/control protocol.

The P3-550E exposes a “Programming Software Connection” service over UDP port 9999 that is used by the engineering workstation software to program and otherwise configure the device. The protocol is not well documented and what information we do have is pieced together from reverse engineering efforts.

Each message of the protocol is prefixed with a 12-byte header containing the requesting client’s IP address and originating port, two 16-bit fields only referred to as ‘GBS’ and ‘IMM’, a 16-bit field of unknown value, followed by a payload which varies by the type of request.

0                               1
0 1 2 3 4 5 6 7 8 9 A B C D E F 0 1 2 3 4 5 6 7 8 9 A B C D E F
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|              GBS              |          Client Port          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                           Client IP                           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|              IMM              |            UNKNOWN            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

The first byte of the IMM field acts as a function code, dictating which high-level feature set is being requested, and the second byte dictates the exact functionality expected. The protocol format for the remainder of the message is dependent on the IMM value.

These vulnerabiliies arise in the file-system related feature set, reachable when the first byte of IMM is 0xD. The function responsible for implementing these features is located at offset 0xb5430 and we refer to it as _DISCOVERY_CALLBACK_D. Within this function, the second byte of IMM is used in a switch-case to identify the exact feature being requested. We are most interested in the implementation of the handler associated with IMM[1] = 0x12. This sub-function expect a payload which contains two fields of interest - a payload and its length. It appears that the contents of the payload are file contents to be burned to local memory upon completion of the file transfer.

 0                               1
 0 1 2 3 4 5 6 7 8 9 A B C D E F 0 1 2 3 4 5 6 7 8 9 A B C D E F
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|              UNK              |              UNK              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                              LEN                              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                            PAYLOAD                            |
|                              ...                              |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 

Within the switch-case associated with IMM[1] = 0x12 a function we refer to as IMM_0D_FIBurn_Handler (located at offset 0xb5d04) is called, receiving the pkt variable as its only parameter. The standard code flow within this function will ultimately call into the vulnerable function, sub_dff18, which takes as its parameters the payload length recovered from offset 0x10 of the packet, and a pointer to the start of the payload which begins at offset 0x14 of the packet. This function begins at offset 0xdff18 of the firmware, and an annotated decompilation is included below.

0000dff18  int32_t sub_dff18(int32_t payload_len, void* payload)
0000dff18  {
0000dff38      data_17fdc1 = 0;
0000dff48      int32_t r30_1 = 0xa;
0000dff60      while (true)
0000dff60      {
                   // Wait on a global flag to be set, or until we've unsuccessfully waited 10 times
0000dff68          if (r30_1-- == 0)
0000dff68          {
0000dff68              break;
0000dff68          }
0000dff74          if (CHUNK_RX_structure.field_18 != 0)
0000dff70          {
0000dff74              break;
0000dff74          }
0000dff5c          NU_Task_Sleep(2);
0000dff58      }
0000dff84      if (data_1fcae0 != 3)
0000dff80      {
0000dff9c          void* dest;
                   // [1] Allocate heap memory based on attacker controlled `payload_len`
                   //     Observe that `payload_len + 0x14` can overflow, resulting in a smaller than expected allocation
0000dff9c          NU_STATUS status = NU_Allocate_Memory(&SYSMEM_pool, &ret_ptr, (payload_len + 0x14), 0);
0000dffa8          if (status == NU_SUCCESS)
0000dffa0          {
                       // [2] Copy an attacker controlled number of bytes from `payload` to `dest`, which will
                       //     crash when `payload_len` is large enough to overflow, due to the null-byte padding of `strncpy`
0000dffb8              strncpy(dest, payload, payload_len);
                       // [3] Note that if somehow this call to `strncpy` were not to crash, then this following line would 
                       //     result in an arbitrary null-byte overwrite, letting an attacker manipulate heap chunk metadata
0000dffbc              dest[payload_len] = 0;
0000dffbc              ...

The intent of this function is to prepare a structure which contains the information necessary to burn the payload into a target file on the system’s local memory and then spawning a new task which takes the structure as its parameter. It does this by first attempting to allocate this structure, which varies in length based on the size of the attacker-controlled payload. The non-varying size of the structure is 0x14 bytes, as evidenced by the the allocation requesting payload_len + 0x14 bytes of memory. If payload_len is sufficiently large, this calculation can overflow and an unexpectedly small heap allocation might be made. After the allocation, a call to strncpy is made, which will begin to copy the packet’s payload, but ultimately crash on a memory access violation while trying to pad null-bytes. An unauthenticated attacker who submits an appropriately formatted packet to this service can cause the device to crash due to memory access violations during the null-byte padding phase of strncpy.

VENDOR RESPONSE

A CISA advisory can be found here: https://www.cisa.gov/news-events/ics-advisories/icsa-24-144-01

TIMELINE

2024-02-14 - Initial Vendor Contact
2024-02-15 - Vendor Disclosure
2024-05-23 - Vendor Patch Release
2024-05-28 - Public Release

Credit

Discovered by Matt Wiseman of Cisco Talos.