Add read/write capabilities to modbusclient

bug/bundler_fix
Arnaud SOULLIE 2014-04-28 15:29:55 +02:00
parent c2bb26590c
commit a2ccbf9833
1 changed files with 132 additions and 55 deletions

View File

@ -8,74 +8,151 @@ require 'msf/core'
class Metasploit3 < Msf::Auxiliary
include Msf::Exploit::Remote::Tcp
include Msf::Auxiliary::Fuzzer
include Rex::Socket::Tcp
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.
'Name' => 'Modbus client, reloaded.',
'Description' => %q{
This module allows reading and writing data to a PLC using the Modbus protocol.
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).
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'
))
))
register_options(
[
OptEnum.new("MODE", [true, 'Command', "READ_REGISTER",
[
"READ_REGISTER",
"READ_COIL",
"WRITE_REGISTER",
"WRITE_COIL"
]
]),
Opt::RPORT(502),
OptInt.new('DATA', [false, "Data to write (WRITE_COIL and WRITE_REGISTER modes only)", 0xBEEF]),
OptInt.new('DATA_ADDRESS', [true, "Modbus data address", 0]),
OptInt.new('UNIT_NUMBER', [false, "Modbus unit number (255 if not used)", 255]),
], self.class)
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)
end
# Don't mess with live production SCADA systems
def scada_write_warning
print_status("Warning : do not try to alter live SCADA configuration. Bad shit can happened. Continue ? (y/n)")
go_on = gets
unless go_on.chomp == 'y'
print_error("Stopping module")
exit
end
end
# a wrapper just to be sure we increment the counter
def sendframe(payload)
sock.put(payload)
@modbuscounter += 1
r = sock.recv(65535, 0.1)
return r
end
def make_read_payload
payload = ""
payload += [datastore['UNIT_NUMBER']].pack("c")
payload += [@function_code].pack("c")
payload += [datastore['DATA_ADDRESS']].pack("n")
payload += [1].pack("n")
packetdata = ""
packetdata += [@modbuscounter].pack("n")
packetdata += "\x00\x00\x00" #dunno what these are
packetdata += [payload.size].pack("c") # size byte
packetdata += payload
return packetdata
end
def make_write_coil_payload(data)
payload = ""
payload += [datastore['UNIT_NUMBER']].pack("c")
payload += [@function_code].pack("c")
payload += [datastore['DATA_ADDRESS']].pack("n")
payload += [data].pack("c")
payload += "\x00"
packetdata = ""
packetdata += [@modbuscounter].pack("n")
packetdata += "\x00\x00\x00" #dunno what these are
packetdata += [payload.size].pack("c") # size byte
packetdata += payload
return packetdata
end
def make_write_register_payload(data)
payload = ""
payload += [datastore['UNIT_NUMBER']].pack("c")
payload += [@function_code].pack("c")
payload += [datastore['DATA_ADDRESS']].pack("n")
payload += [data].pack("n")
packetdata = ""
packetdata += [@modbuscounter].pack("n")
packetdata += "\x00\x00\x00" #dunno what these are
packetdata += [payload.size].pack("c") # size byte
packetdata += payload
return packetdata
end
def run
trans_id ="\x21\x00"
proto_id ="\x00\x00"
len ="\x00\x06"
func_id ="\x06"
@modbuscounter = 0x0000 # used for modbus frames
connect
case datastore['MODE']
when "READ_COIL"
@function_code = 1
response = sendframe(make_read_payload)
print_good("Coil value at address #{datastore['DATA_ADDRESS']} : " + response.reverse.unpack("c").to_s.gsub('[', '').gsub(']', ''))
#For debug: MODVALUE=19276 REGIS=18762, UNIT_ID=71
#trans_id="\x41\x42"
#proto_id="\x43\x44"
#len="\x45\x46"
#func_id="\x48"
when "READ_REGISTER"
@function_code = 3
response = sendframe(make_read_payload)
value = response.split[0][9..10].to_s.unpack("n").to_s.gsub('[', '').gsub(']','')
print_good("Register value at address #{datastore['DATA_ADDRESS']} : " + value)
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
when "WRITE_COIL"
scada_write_warning
@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")
exit
end
response = sendframe(make_write_coil_payload(data))
print_good("Value #{datastore['DATA']} successfully written at coil address #{datastore['DATA_ADDRESS']}")
connect()
sock.put(sploit)
sock.get_once
disconnect()
when "WRITE_REGISTER"
scada_write_warning
@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")
exit
end
response = sendframe(make_write_register_payload(datastore['DATA']))
print_good("Value #{datastore['DATA']} successfully written at registry address #{datastore['DATA_ADDRESS']}")
else
print_error("Invalid MODE")
return
end
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