Extracted common AFP functionality to mixin

unstable
Gregory Man 2012-03-06 18:22:38 +02:00
parent 1a364df37e
commit 5b13b7d1d9
3 changed files with 159 additions and 157 deletions

142
lib/msf/core/exploit/afp.rb Normal file
View File

@ -0,0 +1,142 @@
require 'msf/core'
require 'msf/core/exploit/tcp'
module Msf
module Exploit::Remote::AFP
include Msf::Exploit::Remote::Tcp
def initialize(info={})
super
@id = 1
register_options(
[
Opt::RPORT(548)
], Msf::Exploit::Remote::AFP)
end
def get_info
packet = "\00" # Flag: Request
packet << "\x03" # Command: FPGetSrvrInfo
packet << [@id].pack('n') # requestID
packet << "\x00\x00\x00\x00" #Data offset
packet << "\x00\x00\x00\x00" #Length
packet << "\x00\x00\x00\x00" #Reserved
sock.put(packet)
@id += 1
response = sock.timed_read(1024)
parse_response(response)
end
def parse_response(response)
parsed_data = {}
flags, command, request_id, error_code, length, reserved = response.unpack('CCnNNN')
raise "Server response with error" if error_code != 0
body = get_body(response, length)
machine_type_offset, afp_versions_offset, uam_count_offset, icon_offset, server_flags =
body.unpack('nnnnn')
server_name_length = body.unpack('@10C').first
parsed_data[:server_name] = body.unpack("@11A#{server_name_length}").first
pos = 11 + server_name_length
pos += 1 if pos % 2 != 0 #padding
server_signature_offset, network_addresses_offset, directory_names_offset,
utf8_servername_offset = body.unpack("@#{pos}nnnn")
parsed_data[:machine_type] = read_pascal_string(body, machine_type_offset)
parsed_data[:versions] = read_array(body, afp_versions_offset)
parsed_data[:uams] = read_array(body, uam_count_offset)
# skiped icon
parsed_data[:server_flags] = parse_flags(server_flags)
parsed_data[:signature] = body.unpack("@#{server_signature_offset}H32").first
network_addresses = read_array(body, network_addresses_offset, true)
parsed_data[:network_addresses] = parse_network_addresses(network_addresses)
# skiped directory names
parsed_data[:utf8_server_name] = read_utf8_pascal_string(body, utf8_servername_offset)
return parsed_data
end
def get_body(packet, body_length)
body = packet[16..body_length + 15]
raise "Invalid body length" if body.length != body_length
return body
end
def read_pascal_string(str, offset)
length = str.unpack("@#{offset}C").first
return str.unpack("@#{offset + 1}A#{length}").first
end
def read_utf8_pascal_string(str, offset)
length = str.unpack("@#{offset}n").first
return str[offset + 2..offset + length + 1]
end
def read_array(str, offset, afp_network_address=false)
size = str.unpack("@#{offset}C").first
pos = offset + 1
result = []
size.times do
result << read_pascal_string(str, pos)
pos += str.unpack("@#{pos}C").first
pos += 1 unless afp_network_address
end
return result
end
def parse_network_addresses(network_addresses)
parsed_addreses = []
network_addresses.each do |address|
case address.unpack('C').first
when 0 #Reserved
next
when 1 # Four-byte IP address
parsed_addreses << IPAddr.ntop(address[1..4]).to_s
when 2 # Four-byte IP address followed by a two-byte port number
parsed_addreses << "#{IPAddr.ntop(address[1..4])}:#{address[5..6].unpack("n").first}"
when 3 # DDP address (depricated)
next
when 4 # DNS name (maximum of 254 bytes)
parsed_addreses << address[1..address.length - 2]
when 5 # This functionality is deprecated.
next
when 6 # IPv6 address (16 bytes)
parsed_addreses << "[#{IPAddr.ntop(address[1..16])}]"
when 7 # IPv6 address (16 bytes) followed by a two-byte port number
parsed_addreses << "[#{IPAddr.ntop(address[1..16])}]:#{address[17..18].unpack("n").first}"
else # Something wrong?
raise "Error pasing network addresses"
end
end
return parsed_addreses
end
def parse_flags(flags)
flags = flags.to_s(2)
result = {}
result['Super Client'] = flags[0,1] == '1' ? true : false
result['UUIDs'] = flags[5,1] == '1' ? true : false
result['UTF8 Server Name'] = flags[6,1] == '1' ? true : false
result['Open Directory'] = flags[7,1] == '1' ? true : false
result['Reconnect'] = flags[8,1] == '1' ? true : false
result['Server Notifications'] = flags[9,1] == '1' ? true : false
result['TCP/IP'] = flags[10,1] == '1' ? true : false
result['Server Signature'] = flags[11,1] == '1' ? true : false
result['Server Messages'] = flags[12,1] == '1' ? true : false
result['Password Saving Prohibited'] = flags[13,1] == '1' ? true : false
result['Password Changing'] = flags[14,1] == '1' ? true : false
result['Copy File'] = flags[5,1] == '1' ? true : false
return result
end
end
end

View File

@ -56,6 +56,7 @@ require 'msf/core/exploit/postgres'
require 'msf/core/exploit/vim_soap'
require 'msf/core/exploit/wdbrpc'
require 'msf/core/exploit/wdbrpc_client'
require 'msf/core/exploit/afp'
# Telephony

View File

@ -11,7 +11,7 @@ class Metasploit3 < Msf::Auxiliary
include Msf::Auxiliary::Report
include Msf::Auxiliary::Scanner
include Msf::Exploit::Remote::Tcp
include Msf::Exploit::Remote::AFP
def initialize(info={})
super(update_info(info,
@ -29,11 +29,6 @@ class Metasploit3 < Msf::Auxiliary
'License' => MSF_LICENSE
))
register_options(
[
Opt::RPORT(548)
], self.class)
deregister_options('RHOST')
end
@ -41,174 +36,38 @@ class Metasploit3 < Msf::Auxiliary
print_status("Scanning IP: #{ip.to_s}")
begin
connect
get_server_info
response = get_info
report(response)
rescue ::Timeout::Error
rescue ::Interrupt
raise $!
rescue ::Rex::ConnectionError, ::IOError, ::Errno::ECONNRESET, ::Errno::ENOPROTOOPT
rescue ::Exception
raise $!
print_error("#{rhost}:#{rport} #{$!.class} #{$!}")
ensure
disconnect
end
end
def get_server_info
packet = "\00" # Flag: Request
packet << "\x03" # Command: FPGetSrvrInfo
packet << "\x01\x03" # requestID
packet << "\x00\x00\x00\x00" #Data offset
packet << "\x00\x00\x00\x00" #Length
packet << "\x00\x00\x00\x00" #Reserved
sock.put(packet)
response = sock.timed_read(1024)
parse_response(response)
end
def parse_response(response)
flags = response[0]
command = response[1]
request_id = response[2..3]
error_code = response[4..7]
length = response[8..11]
reserved = response[12..15]
body = response[16..length.unpack('N').first + 15]
raise "Invalid packet length" if body.length != length.unpack('N').first
machine_type_offset = body[0..1]
version_count_offset = body[2..3]
uam_count_offset = body[4..5]
icon_offset = body[6..7]
flags = body[8..9]
server_name_length = body[10]
server_name_length = server_name_length.unpack("C").first if server_name_length.is_a?(String)
server_name = read_pascal_string(body, 10)
pos = 10 + server_name_length + 1
pos += 1 if pos % 2 != 0 #padding
server_signature_offset = body[pos..pos + 1]
network_addresses_count_offset = body[pos + 2..pos + 3]
directory_names_count_offset = body[pos + 4..pos + 5]
utf8_server_name_offset = body[pos + 6..pos + 7]
machine_type = read_pascal_string(body, machine_type_offset)
versions = read_array(body, version_count_offset)
uams = read_array(body, uam_count_offset)
num_signature_offset = server_signature_offset.unpack('n').first
server_signature = body[num_signature_offset..num_signature_offset + 15]
directories = read_array(body, directory_names_count_offset)
utf8_server_name = read_utf8_pascal_string(body, utf8_server_name_offset)
parsed_flags = parse_flags(flags)
network_addresses = read_array(body, network_addresses_count_offset, true)
parsed_network_addresses = parse_network_addresses(network_addresses)
#report
report_info = "Server Flags: 0x#{flags.unpack('H*').first}\n" +
format_flags_report(parsed_flags) +
" Server Name: #{server_name} \n" +
" Machine Type: #{machine_type} \n" +
" AFP Versions: #{versions.join(', ')} \n" +
" UAMs: #{uams.join(', ')}\n" +
" Server Signature: #{server_signature.unpack("H*").first.to_s}\n" +
def report(response)
report_info = "Server Name: #{response[:server_name]} \n" +
" Server Flags: \n" +
format_flags_report(response[:server_flags]) +
" Machine Type: #{response[:machine_type]} \n" +
" AFP Versions: #{response[:versions].join(', ')} \n" +
" UAMs: #{response[:uams].join(', ')}\n" +
" Server Signature: #{response[:signature]}\n" +
" Server Network Address: \n" +
format_addresses_report(parsed_network_addresses) +
" UTF8 Server Name: #{utf8_server_name}"
format_addresses_report(response[:network_addresses]) +
" UTF8 Server Name: #{response[:utf8_server_name]}"
print_status("#{rhost}:#{rport} APF:\n #{report_info}")
report_note(:host => datastore['RHOST'],
:proto => 'TCP',
:port => datastore['RPORT'],
:type => 'afp_server_info',
:data => {
:server_name => Iconv.conv("UTF-8//IGNORE", "US-ASCII", server_name),
:flags => { :raw => "0x#{flags.unpack('H*').first}", :parsed => parsed_flags },
:machine_type => machine_type,
:afp_versions => versions,
:uams => uams,
:server_signature => server_signature.unpack("H*").first.to_s,
:network_addresses => parsed_network_addresses,
:utf8_server_name => utf8_server_name
})
end
def parse_network_addresses(network_addresses)
parsed_addreses = []
network_addresses.each do |address|
case address[0].is_a?(String) ? address[0].unpack('C').first : address[0]
when 0 #Reserved
next
when 1 # Four-byte IP address
parsed_addreses << IPAddr.ntop(address[1..4]).to_s
when 2 # Four-byte IP address followed by a two-byte port number
parsed_addreses << "#{IPAddr.ntop(address[1..4])}:#{address[5..6].unpack("n").first}"
when 3 # DDP address (depricated)
next
when 4 # DNS name (maximum of 254 bytes)
parsed_addreses << address[1..address.length - 2]
when 5 # This functionality is deprecated.
next
when 6 # IPv6 address (16 bytes)
parsed_addreses << "[#{IPAddr.ntop(address[1..16])}]"
when 7 # IPv6 address (16 bytes) followed by a two-byte port number
parsed_addreses << "[#{IPAddr.ntop(address[1..16])}]:#{address[17..18].unpack("n").first}"
else # Something wrong?
raise "Error pasing network addresses"
end
end
return parsed_addreses
end
def parse_flags(flags)
flags = flags.unpack("n").first.to_s(2)
result = {}
result['Super Client'] = flags[0,1] == '1' ? true : false
result['UUIDs'] = flags[5,1] == '1' ? true : false
result['UTF8 Server Name'] = flags[6,1] == '1' ? true : false
result['Open Directory'] = flags[7,1] == '1' ? true : false
result['Reconnect'] = flags[8,1] == '1' ? true : false
result['Server Notifications'] = flags[9,1] == '1' ? true : false
result['TCP/IP'] = flags[10,1] == '1' ? true : false
result['Server Signature'] = flags[11,1] == '1' ? true : false
result['Server Messages'] = flags[12,1] == '1' ? true : false
result['Password Saving Prohibited'] = flags[13,1] == '1' ? true : false
result['Password Changing'] = flags[14,1] == '1' ? true : false
result['Copy File'] = flags[5,1] == '1' ? true : false
return result
end
def read_utf8_pascal_string(str, offset)
offset = offset.unpack("n").first if offset.is_a?(String)
length = str[offset..offset+1].unpack("n").first
return str[offset + 2..offset + length + 1]
end
def read_pascal_string(str, offset)
offset = offset.unpack("n").first if offset.is_a?(String)
length = str[offset]
length = length.unpack("C").first if length.is_a?(String)
return str[offset + 1..offset + length]
end
def read_array(str, offset, afp_network_address=false)
offset = offset.unpack("n").first if offset.is_a?(String)
size = str[offset]
size = size.unpack("C").first if size.is_a?(String)
pos = offset + 1
result = []
size.times do
result << read_pascal_string(str, pos)
pos += str[pos].is_a?(String) ? str[pos].unpack('C').first : str[pos]
pos += 1 unless afp_network_address
end
return result
:data => response)
end
def format_flags_report(parsed_flags)