Skip to content

Commit 675e5c0

Browse files
committed
Merge branch 'modbus-aux' of git://github.com/esmnemon/metasploit-framework into esmnemon-modbus-aux
2 parents 7a1c3e7 + 4ae482b commit 675e5c0

File tree

2 files changed

+119
-2
lines changed

2 files changed

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