Merge branch 'master' into staging/electro_release
commit
cace6581ce
|
@ -8,74 +8,200 @@ require 'msf/core'
|
|||
class Metasploit3 < Msf::Auxiliary
|
||||
|
||||
include Msf::Exploit::Remote::Tcp
|
||||
include Msf::Auxiliary::Fuzzer
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'Modbus Client Utility',
|
||||
'Description' => %q{
|
||||
This module sends a command (0x06, write to one register) to a Modbus endpoint.
|
||||
You can change port, IP, register to write and data to write, as well as unit-id.
|
||||
|
||||
Modbus is a clear text protocol used in common SCADA systems, developed
|
||||
originally as a serial-line (RS232) async protocol. It is later transformed
|
||||
to IP, which is called ModbusTCP.
|
||||
|
||||
There are a handful of functions which are possible to do, but this
|
||||
client has only implemented the function "write value to register" (\x48).
|
||||
'Name' => 'Modbus Client Utility',
|
||||
'Description' => %q{
|
||||
This module allows reading and writing data to a PLC using the Modbus protocol.
|
||||
This module is based on the 'modiconstop.rb' Basecamp module from DigitalBond,
|
||||
as well as the mbtget perl script.
|
||||
},
|
||||
'Author' => [ 'EsMnemon <esm[at]mnemonic.no>' ],
|
||||
'References' =>
|
||||
'Author' =>
|
||||
[
|
||||
['URL', 'http://www.saia-pcd.com/en/products/plc/pcd-overview/Pages/pcd1-m2.aspx']
|
||||
'EsMnemon <esm[at]mnemonic.no>', # original write-only module
|
||||
'Arnaud SOULLIE <arnaud.soullie[at]solucom.fr>' # new code that allows read/write
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'DisclosureDate' => 'Nov 1 2011'
|
||||
))
|
||||
'Actions' =>
|
||||
[
|
||||
['READ_COIL', { 'Description' => 'Read one bit from a coil' } ],
|
||||
['WRITE_COIL', { 'Description' => 'Write one bit to a coil' } ],
|
||||
['READ_REGISTER', { 'Description' => 'Read one word from a register' } ],
|
||||
['WRITE_REGISTER', { 'Description' => 'Write one word to a register' } ]
|
||||
],
|
||||
'DefaultAction' => 'READ_REGISTER'
|
||||
))
|
||||
|
||||
register_options([
|
||||
Opt::RPORT(502),
|
||||
OptInt.new('UNIT_ID', [true, "ModBus Unit Identifier ", 1]),
|
||||
OptInt.new('MODVALUE', [true, "ModBus value to write (data) ", 2]),
|
||||
OptInt.new('REGIS', [true, "ModBus Register definition", 1002])
|
||||
], self.class)
|
||||
register_options(
|
||||
[
|
||||
Opt::RPORT(502),
|
||||
OptInt.new('DATA', [false, "Data to write (WRITE_COIL and WRITE_REGISTER modes only)"]),
|
||||
OptInt.new('DATA_ADDRESS', [true, "Modbus data address"]),
|
||||
OptInt.new('UNIT_NUMBER', [false, "Modbus unit number", 1]),
|
||||
], self.class)
|
||||
|
||||
end
|
||||
|
||||
# a wrapper just to be sure we increment the counter
|
||||
def send_frame(payload)
|
||||
sock.put(payload)
|
||||
@modbus_counter += 1
|
||||
r = sock.get(sock.def_read_timeout)
|
||||
return r
|
||||
end
|
||||
|
||||
def make_payload(payload)
|
||||
packet_data = [@modbus_counter].pack("n")
|
||||
packet_data += "\x00\x00\x00" #dunno what these are
|
||||
packet_data += [payload.size].pack("c") # size byte
|
||||
packet_data += payload
|
||||
|
||||
packet_data
|
||||
end
|
||||
|
||||
def make_read_payload
|
||||
payload = [datastore['UNIT_NUMBER']].pack("c")
|
||||
payload += [@function_code].pack("c")
|
||||
payload += [datastore['DATA_ADDRESS']].pack("n")
|
||||
payload += [1].pack("n")
|
||||
|
||||
packet_data = make_payload(payload)
|
||||
|
||||
packet_data
|
||||
end
|
||||
|
||||
def make_write_coil_payload(data)
|
||||
payload = [datastore['UNIT_NUMBER']].pack("c")
|
||||
payload += [@function_code].pack("c")
|
||||
payload += [datastore['DATA_ADDRESS']].pack("n")
|
||||
payload += [data].pack("c")
|
||||
payload += "\x00"
|
||||
|
||||
packet_data = make_payload(payload)
|
||||
|
||||
packet_data
|
||||
end
|
||||
|
||||
def make_write_register_payload(data)
|
||||
payload = [datastore['UNIT_NUMBER']].pack("c")
|
||||
payload += [@function_code].pack("c")
|
||||
payload += [datastore['DATA_ADDRESS']].pack("n")
|
||||
payload += [data].pack("n")
|
||||
|
||||
packet_data = make_payload(payload)
|
||||
|
||||
packet_data
|
||||
end
|
||||
|
||||
def handle_error(response)
|
||||
case response.reverse.unpack("c")[0].to_i
|
||||
when 1
|
||||
print_error("Error : ILLEGAL FUNCTION")
|
||||
when 2
|
||||
print_error("Error : ILLEGAL DATA ADDRESS")
|
||||
when 3
|
||||
print_error("Error : ILLEGAL DATA VALUE")
|
||||
when 4
|
||||
print_error("Error : SLAVE DEVICE FAILURE")
|
||||
when 6
|
||||
print_error("Error : SLAVE DEVICE BUSY")
|
||||
else
|
||||
print_error("Unknown error")
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
def read_coil
|
||||
@function_code = 0x1
|
||||
print_status("Sending READ COIL...")
|
||||
response = send_frame(make_read_payload)
|
||||
if response.nil?
|
||||
print_error("No answer for the READ COIL")
|
||||
return
|
||||
elsif response.unpack("C*")[7] == (0x80 | @function_code)
|
||||
handle_error(response)
|
||||
elsif response.unpack("C*")[7] == @function_code
|
||||
value = response[9].unpack("c")[0]
|
||||
print_good("Coil value at address #{datastore['DATA_ADDRESS']} : #{value}")
|
||||
else
|
||||
print_error("Unknown answer")
|
||||
end
|
||||
end
|
||||
|
||||
def read_register
|
||||
@function_code = 3
|
||||
print_status("Sending READ REGISTER...")
|
||||
response = send_frame(make_read_payload)
|
||||
if response.nil?
|
||||
print_error("No answer for the READ REGISTER")
|
||||
elsif response.unpack("C*")[7] == (0x80 | @function_code)
|
||||
handle_error(response)
|
||||
elsif response.unpack("C*")[7] == @function_code
|
||||
value = response[9..10].unpack("n")[0]
|
||||
print_good("Register value at address #{datastore['DATA_ADDRESS']} : #{value}")
|
||||
else
|
||||
print_error("Unknown answer")
|
||||
end
|
||||
end
|
||||
|
||||
def write_coil
|
||||
@function_code = 5
|
||||
if datastore['DATA'] == 0
|
||||
data = 0
|
||||
elsif datastore['DATA'] == 1
|
||||
data = 255
|
||||
else
|
||||
print_error("Data value must be 0 or 1 in WRITE_COIL mode")
|
||||
return
|
||||
end
|
||||
print_status("Sending WRITE COIL...")
|
||||
response = send_frame(make_write_coil_payload(data))
|
||||
if response.nil?
|
||||
print_error("No answer for the WRITE COIL")
|
||||
elsif response.unpack("C*")[7] == (0x80 | @function_code)
|
||||
handle_error(response)
|
||||
elsif response.unpack("C*")[7] == @function_code
|
||||
print_good("Value #{datastore['DATA']} successfully written at coil address #{datastore['DATA_ADDRESS']}")
|
||||
else
|
||||
print_error("Unknown answer")
|
||||
end
|
||||
end
|
||||
|
||||
def write_register
|
||||
@function_code = 6
|
||||
if datastore['DATA'] < 0 || datastore['DATA'] > 65535
|
||||
print_error("Data to write must be an integer between 0 and 65535 in WRITE_REGISTER mode")
|
||||
return
|
||||
end
|
||||
print_status("Sending WRITE REGISTER...")
|
||||
response = send_frame(make_write_register_payload(datastore['DATA']))
|
||||
if response.nil?
|
||||
print_error("No answer for the WRITE REGISTER")
|
||||
elsif response.unpack("C*")[7] == (0x80 | @function_code)
|
||||
handle_error(response)
|
||||
elsif response.unpack("C*")[7] == @function_code
|
||||
print_good("Value #{datastore['DATA']} successfully written at registry address #{datastore['DATA_ADDRESS']}")
|
||||
else
|
||||
print_error("Unknown answer")
|
||||
end
|
||||
end
|
||||
|
||||
def run
|
||||
trans_id ="\x21\x00"
|
||||
proto_id ="\x00\x00"
|
||||
len ="\x00\x06"
|
||||
func_id ="\x06"
|
||||
|
||||
#For debug: MODVALUE=19276 REGIS=18762, UNIT_ID=71
|
||||
#trans_id="\x41\x42"
|
||||
#proto_id="\x43\x44"
|
||||
#len="\x45\x46"
|
||||
#func_id="\x48"
|
||||
|
||||
sploit = trans_id
|
||||
sploit += proto_id
|
||||
sploit += len
|
||||
sploit += [datastore['UNIT_ID']].pack("C")
|
||||
sploit += func_id
|
||||
sploit += [datastore['REGIS']].pack("S").reverse
|
||||
sploit += [datastore['MODVALUE']].pack("S").reverse
|
||||
|
||||
connect()
|
||||
sock.put(sploit)
|
||||
sock.get_once
|
||||
disconnect()
|
||||
@modbus_counter = 0x0000 # used for modbus frames
|
||||
connect
|
||||
case action.name
|
||||
when "READ_COIL"
|
||||
read_coil
|
||||
when "READ_REGISTER"
|
||||
read_register
|
||||
when "WRITE_COIL"
|
||||
write_coil
|
||||
when "WRITE_REGISTER"
|
||||
write_register
|
||||
else
|
||||
print_error("Invalid ACTION")
|
||||
end
|
||||
disconnect
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
=begin
|
||||
MODBUS: 10 00 00 00 00 06 01 06 03 ea 00 02
|
||||
tested on a SAIA PCD1.M2
|
||||
scapy - even with source-IP
|
||||
sploit="\x21\x00\x00\x00\x00\x06\x01\x06\x03\xea\x00\x02"
|
||||
ip=IP(dst="172.16.10.10",src="172.16.10.155",proto=6,flags=2)
|
||||
tcp=TCP(dport=509)
|
||||
send(ip/tcp/sploit)
|
||||
|
||||
=end
|
||||
end
|
Loading…
Reference in New Issue