Addition of multiple read/write to auxiliary/scanner/scada/modbusclient.rb
parent
be8d6df093
commit
c0a8b01c2b
|
@ -20,24 +20,31 @@ class Metasploit3 < Msf::Auxiliary
|
|||
'Author' =>
|
||||
[
|
||||
'EsMnemon <esm[at]mnemonic.no>', # original write-only module
|
||||
'Arnaud SOULLIE <arnaud.soullie[at]solucom.fr>' # new code that allows read/write
|
||||
'Arnaud SOULLIE <arnaud.soullie[at]solucom.fr>', # code that allows read/write
|
||||
'Alexandrine TORRENTS <alexandrine.torrents[at]eurecom.fr>', # code that allows reading/writing at multiple consecutive addresses
|
||||
'Mathieu CHEVALIER <mathieu.chevalier[at]eurecom.fr>'
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'Actions' =>
|
||||
[
|
||||
['READ_COIL', { 'Description' => 'Read one bit from a coil' } ],
|
||||
['READ_COILS', { 'Description' => 'Read bits from several coils' } ],
|
||||
['READ_REGISTERS', { 'Description' => 'Read words from several registers' } ],
|
||||
['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' } ]
|
||||
['WRITE_REGISTER', { 'Description' => 'Write one word to a register' } ],
|
||||
['WRITE_COILS', { 'Description' => 'Write bits to several coils' } ],
|
||||
['WRITE_REGISTERS', { 'Description' => 'Write words to several registers' } ]
|
||||
],
|
||||
'DefaultAction' => 'READ_REGISTER'
|
||||
'DefaultAction' => 'READ_REGISTERS'
|
||||
))
|
||||
|
||||
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('NUMBER', [false, "Number of coils/registers to read (READ_COILS ans READ_REGISTERS modes only)", 1]),
|
||||
OptInt.new('DATA', [false, "Data to write (WRITE_COIL and WRITE_REGISTER modes only)"]),
|
||||
OptString.new('DATA_COILS', [false, "Data in binary to write (WRITE_COILS mode only) e.g. 0110"]),
|
||||
OptString.new('DATA_REGISTERS', [false, "Words to write to each register separated with a comma (WRITE_REGISTERS mode only) e.g. 1,2,3,4"]),
|
||||
OptInt.new('UNIT_NUMBER', [false, "Modbus unit number", 1]),
|
||||
], self.class)
|
||||
|
||||
|
@ -63,7 +70,7 @@ class Metasploit3 < Msf::Auxiliary
|
|||
payload = [datastore['UNIT_NUMBER']].pack("c")
|
||||
payload += [@function_code].pack("c")
|
||||
payload += [datastore['DATA_ADDRESS']].pack("n")
|
||||
payload += [1].pack("n")
|
||||
payload += [datastore['NUMBER']].pack("n")
|
||||
make_payload(payload)
|
||||
end
|
||||
|
||||
|
@ -79,6 +86,21 @@ class Metasploit3 < Msf::Auxiliary
|
|||
packet_data
|
||||
end
|
||||
|
||||
def make_write_coils_payload(data, byte)
|
||||
payload = [datastore['UNIT_NUMBER']].pack("c")
|
||||
payload += [@function_code].pack("c")
|
||||
payload += [datastore['DATA_ADDRESS']].pack("n")
|
||||
payload += [datastore['DATA_COILS'].size].pack("n") # bit count
|
||||
payload += [byte].pack("c") # byte count
|
||||
for i in 0..(byte-1)
|
||||
payload += [data[i]].pack("b*")
|
||||
end
|
||||
|
||||
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")
|
||||
|
@ -88,6 +110,19 @@ class Metasploit3 < Msf::Auxiliary
|
|||
make_payload(payload)
|
||||
end
|
||||
|
||||
def make_write_registers_payload(data, size)
|
||||
payload = [datastore['UNIT_NUMBER']].pack("c")
|
||||
payload += [@function_code].pack("c")
|
||||
payload += [datastore['DATA_ADDRESS']].pack("n")
|
||||
payload += [size].pack("n") # word count
|
||||
payload += [2*size].pack("c") # byte count
|
||||
for i in 0..(size-1)
|
||||
payload += [data[i]].pack("n")
|
||||
end
|
||||
|
||||
make_payload(payload)
|
||||
end
|
||||
|
||||
def handle_error(response)
|
||||
case response.reverse.unpack("c")[0].to_i
|
||||
when 1
|
||||
|
@ -106,34 +141,57 @@ class Metasploit3 < Msf::Auxiliary
|
|||
return
|
||||
end
|
||||
|
||||
def read_coil
|
||||
def read_coils
|
||||
if datastore['NUMBER']+datastore['DATA_ADDRESS'] > 65535
|
||||
print_error("Coils addresses go from 0 to 65535. You cannot go beyond.")
|
||||
return
|
||||
end
|
||||
@function_code = 0x1
|
||||
print_status("Sending READ COIL...")
|
||||
print_status("Sending READ COILS...")
|
||||
response = send_frame(make_read_payload)
|
||||
values = []
|
||||
if response.nil?
|
||||
print_error("No answer for the READ COIL")
|
||||
print_error("No answer for the READ COILS")
|
||||
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}")
|
||||
loop = (datastore['NUMBER']-1)/8
|
||||
for i in 0..loop
|
||||
bin_value = response[9+i].unpack("b*")[0]
|
||||
list = bin_value.split("")
|
||||
for j in 0..7
|
||||
list[j] = list[j].to_i
|
||||
values[i*8 + j] = list[j]
|
||||
end
|
||||
end
|
||||
values = values[0..(datastore['NUMBER']-1)]
|
||||
print_good("#{datastore['NUMBER']} coil values from address #{datastore['DATA_ADDRESS']} : ")
|
||||
print_good("#{values}")
|
||||
else
|
||||
print_error("Unknown answer")
|
||||
end
|
||||
end
|
||||
|
||||
def read_register
|
||||
def read_registers
|
||||
if datastore['NUMBER']+datastore['DATA_ADDRESS'] > 65535
|
||||
print_error("Registers addresses go from 0 to 65535. You cannot go beyond.")
|
||||
return
|
||||
end
|
||||
@function_code = 3
|
||||
print_status("Sending READ REGISTER...")
|
||||
print_status("Sending READ REGISTERS...")
|
||||
response = send_frame(make_read_payload)
|
||||
values = []
|
||||
if response.nil?
|
||||
print_error("No answer for the READ REGISTER")
|
||||
print_error("No answer for the READ REGISTERS")
|
||||
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}")
|
||||
for i in 0..(datastore['NUMBER']-1)
|
||||
values.push(response[9+2*i..10+2*i].unpack("n")[0])
|
||||
end
|
||||
print_good("#{datastore['NUMBER']} register values from address #{datastore['DATA_ADDRESS']} : ")
|
||||
print_good("#{values}")
|
||||
else
|
||||
print_error("Unknown answer")
|
||||
end
|
||||
|
@ -162,6 +220,39 @@ class Metasploit3 < Msf::Auxiliary
|
|||
end
|
||||
end
|
||||
|
||||
def write_coils
|
||||
@function_code = 15
|
||||
temp = datastore['DATA_COILS']
|
||||
check = temp.split("")
|
||||
if temp.size > 65535
|
||||
print_error("DATA_COILS size must be between 0 and 65535")
|
||||
return
|
||||
end
|
||||
for j in check
|
||||
if j=="0" or j=="1"
|
||||
else
|
||||
print_error("DATA_COILS value must only contain 0s and 1s without space")
|
||||
return
|
||||
end
|
||||
end
|
||||
byte_number = (temp.size-1)/8 + 1
|
||||
data = []
|
||||
for i in 0..(byte_number-1)
|
||||
data.push(temp[(i*8+0)..(i*8+7)])
|
||||
end
|
||||
print_status("Sending WRITE COILS...")
|
||||
response = send_frame(make_write_coils_payload(data, byte_number))
|
||||
if response.nil?
|
||||
print_error("No answer for the WRITE COILS")
|
||||
elsif response.unpack("C*")[7] == (0x80 | @function_code)
|
||||
handle_error(response)
|
||||
elsif response.unpack("C*")[7] == @function_code
|
||||
print_good("Values #{datastore['DATA_COILS']} successfully written from 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
|
||||
|
@ -181,18 +272,74 @@ class Metasploit3 < Msf::Auxiliary
|
|||
end
|
||||
end
|
||||
|
||||
def write_registers
|
||||
@function_code = 16
|
||||
check = datastore['DATA_REGISTERS'].split("")
|
||||
for j in 0..(check.size-1)
|
||||
if check[j] == "0" or check[j]== "1" or check[j]== "2" or check[j]== "3" or check[j]== "4" or check[j]== "5" or check[j]== "6" or check[j]== "7" or check[j]== "8" or check[j]== "9" or check[j]== ","
|
||||
if check[j] == "," and check[j+1] == ","
|
||||
print_error("DATA_REGISTERS cannot contain two consecutive commas")
|
||||
return
|
||||
end
|
||||
else
|
||||
print_error("DATA_REGISTERS value must only contain numbers and commas without space")
|
||||
return
|
||||
end
|
||||
end
|
||||
list = datastore['DATA_REGISTERS'].split(",")
|
||||
if list.size+datastore['DATA_ADDRESS'] > 65535
|
||||
print_error("Registers addresses go from 0 to 65535. You cannot go beyond.")
|
||||
return
|
||||
end
|
||||
data = []
|
||||
for i in 0..(list.size-1)
|
||||
data[i] = list[i].to_i
|
||||
end
|
||||
for j in 0..(data.size-1)
|
||||
if data[j] < 0 || data[j] > 65535
|
||||
print_error("Each word to write must be an integer between 0 and 65535 in WRITE_REGISTERS mode")
|
||||
return
|
||||
end
|
||||
end
|
||||
print_status("Sending WRITE REGISTERS...")
|
||||
response = send_frame(make_write_registers_payload(data, data.size))
|
||||
if response.nil?
|
||||
print_error("No answer for the WRITE REGISTERS")
|
||||
elsif response.unpack("C*")[7] == (0x80 | @function_code)
|
||||
handle_error(response)
|
||||
elsif response.unpack("C*")[7] == @function_code
|
||||
print_good("Values #{datastore['DATA_REGISTERS']} successfully written from registry address #{datastore['DATA_ADDRESS']}")
|
||||
else
|
||||
print_error("Unknown answer")
|
||||
end
|
||||
end
|
||||
|
||||
def run
|
||||
@modbus_counter = 0x0000 # used for modbus frames
|
||||
connect
|
||||
case action.name
|
||||
when "READ_COIL"
|
||||
read_coil
|
||||
when "READ_REGISTER"
|
||||
read_register
|
||||
when "READ_COILS"
|
||||
read_coils
|
||||
when "READ_REGISTERS"
|
||||
read_registers
|
||||
when "WRITE_COIL"
|
||||
write_coil
|
||||
when "WRITE_REGISTER"
|
||||
write_register
|
||||
when "WRITE_COILS"
|
||||
if datastore['DATA_COILS'] == nil
|
||||
print_error("The following option is needed in WRITE_COILS mode: DATA_COILS.")
|
||||
return
|
||||
else
|
||||
write_coils
|
||||
end
|
||||
when "WRITE_REGISTERS"
|
||||
if datastore['DATA_REGISTERS'] == nil
|
||||
print_error("The following option is needed in WRITE_REGISTERS mode: DATA_REGISTERS.")
|
||||
return
|
||||
else
|
||||
write_registers
|
||||
end
|
||||
else
|
||||
print_error("Invalid ACTION")
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue