Skip to content

Commit f707a47

Browse files
committed
Merge branch 'esmnemon-modbus-aux'
2 parents 222af8c + aa6ac36 commit f707a47

File tree

2 files changed

+113
-2
lines changed

2 files changed

+113
-2
lines changed
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
##
2+
# This file is part of the Metasploit Framework and may be subject to
3+
# redistribution and commercial restrictions. Please see the Metasploit
4+
# Framework web site for more information on licensing and terms of use.
5+
# http://metasploit.com/framework/
6+
##
7+
8+
require 'msf/core'
9+
10+
class Metasploit3 < Msf::Auxiliary
11+
include Msf::Exploit::Remote::Tcp
12+
include Msf::Auxiliary::Fuzzer
13+
14+
def initialize(info = {})
15+
super(update_info(info,
16+
'Name' => 'Modbus Unit ID and Station ID Enumerator',
17+
'Description' => %q{
18+
Modbus is a cleartext protocol used in common SCADA systems, developed
19+
originally as a serial-line (RS232) async protocol, and later transformed
20+
to IP, which is called ModbusTCP. default tcpport is 502.
21+
22+
This module sends a command (0x04, read input register) to the modbus endpoint.
23+
If this command is sent to the correct unit-id, it returns with the same funcion-id.
24+
if not, it should be added 0x80, so that it sys 0x84, and an exception-code follows
25+
which do not interest us. This does not always happen, but at least the first 4
26+
bytes in the return-packet should be exact the same as what was sent.
27+
28+
You can change port, ip and the scan-range for unit-id. There is also added a
29+
value - BENICE - to make the scanner sleep a second or more between probes. We
30+
have seen installations where scanning too many too fast workes like a DoS.
31+
},
32+
'References' =>
33+
[
34+
[ 'URL', 'http://www.saia-pcd.com/en/products/plc/pcd-overview/Pages/pcd1-m2.aspx' ],
35+
[ 'URL', 'http://en.wikipedia.org/wiki/Modbus:TCP' ]
36+
],
37+
'Author' => [ 'EsMnemon <esm[at]mnemonic.no>' ],
38+
'License' => MSF_LICENSE,
39+
'DisclosureDate' => 'Oct 28 2012'
40+
))
41+
42+
register_options(
43+
[
44+
Opt::RPORT(502),
45+
OptInt.new('UNIT_ID_FROM', [true, "ModBus Unit Identifier scan from value [1..254]", 1]),
46+
OptInt.new('UNIT_ID_TO', [true, "ModBus Unit Identifier scan to value [UNIT_ID_FROM..254]", 254]),
47+
OptInt.new('BENICE', [true, "Seconds to sleep between StationID-probes, just for beeing nice", 1]),
48+
OptInt.new('TIMEOUT', [true, 'Timeout for the network probe, 0 means no timeout', 2])
49+
], self.class)
50+
end
51+
52+
def run
53+
start="\x21\x00\x00\x00\x00\x06"
54+
theend="\x04\x00\x01\x00\x00"
55+
noll="\x00"
56+
# between, \01..\0ff (1-255)
57+
unless (1..255).include? datastore['UNIT_ID_FROM']
58+
print_status("unit ID must be between 1 and 254 adjusting UNIT_ID_FROM to 1")
59+
datastore['UNIT_ID_FROM']=1
60+
end
61+
62+
unless (1..255).include? datastore['UNIT_ID_TO']
63+
print_status("Unit ID must be between #{datastore['UNIT_ID_FROM']} and 255")
64+
print_warning("Adjusting UNIT_ID_TO to #{datastore['UNIT_ID_FROM']} ")
65+
datastore['UNIT_ID_TO'] = datastore['UNIT_ID_FROM']
66+
end
67+
68+
if datastore['UNIT_ID_FROM'] > datastore['UNIT_ID_TO'] then
69+
print_warning("UNIT_ID_TO is less than UNIT_ID_FROM, setting them equal")
70+
datastore['UNIT_ID_TO'] = datastore['UNIT_ID_FROM']
71+
end
72+
73+
datastore['UNIT_ID_FROM'].upto(datastore['UNIT_ID_TO']) do |counter|
74+
sploit = start
75+
sploit += [counter].pack("C")
76+
sploit += theend
77+
select(nil,nil,nil,datastore['BENICE'])
78+
connect()
79+
sock.put(sploit)
80+
81+
data = sock.get_once(12, datastore['TIMEOUT'])
82+
if (data.nil?)
83+
data=noll+noll+noll+noll
84+
end
85+
86+
if data[0,4] == "\x21\x00\x00\x00" #return of the same trans-id+proto-id
87+
print_good("Received: correct MODBUS/TCP from stationID #{counter}")
88+
else
89+
print_status("Received: incorrect/none data from stationID #{counter} (probably not in use)")
90+
end
91+
92+
disconnect()
93+
end
94+
end
95+
end
96+
97+
98+
=begin
99+
For testing purposes:
100+
101+
This client is developed and tested against a SAIA PCD1.M2 system
102+
http://www.saia-pcd.com/en/products/plc/pcd-overview/Pages/pcd1-m2.aspx
103+
and a modbus/tcp PLC simulator from plcsimulator.org
104+
and the Modbus SLAVE from http://www.modbustools.com/
105+
106+
Mission is to find Unit-ID/stationID of the modbus-endpoint:
107+
RHOST=IP of the modbus-service (PLC)
108+
RPORT=Usually 502
109+
=end

modules/auxiliary/scanner/scada/modbusdetect.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,22 +34,24 @@ def initialize
3434
register_options(
3535
[
3636
Opt::RPORT(502),
37+
OptInt.new('UNIT_ID', [true, "ModBus Unit Identifier, 1..255, most often 1 ", 1]),
3738
OptInt.new('TIMEOUT', [true, 'Timeout for the network probe', 10])
3839
], self.class)
3940
end
4041

4142
def run_host(ip)
4243
#read input register=func:04, register 1
4344
sploit="\x21\x00\x00\x00\x00\x06\x01\x04\x00\x01\x00\x00"
45+
sploit[6] = [datastore['UNIT_ID']].pack("C")
4446
connect()
4547
sock.put(sploit)
4648
data = sock.get_once
4749

48-
# Theory: Whene sending a modbus request of some sort, the endpoint will return
50+
# Theory: When sending a modbus request of some sort, the endpoint will return
4951
# with at least the same transaction-id, and protocol-id
5052
if data
5153
if data[0,4] == "\x21\x00\x00\x00"
52-
print_good("#{ip}:#{rport} - MODBUS - received correct MODBUS/TCP header")
54+
print_good("#{ip}:#{rport} - MODBUS - received correct MODBUS/TCP header (unit-ID: #{datastore['UNIT_ID']})")
5355
else
5456
print_error("#{ip}:#{rport} - MODBUS - received incorrect data #{data[0,4].inspect} (not modbus/tcp?)")
5557
end

0 commit comments

Comments
 (0)