Path: blob/master/modules/auxiliary/sniffer/psnuffle.rb
19516 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45##6# dsniff was helping me very often. Too bad that it doesn't work correctly7# anymore. Psnuffle should bring password sniffing into Metasploit local8# and if we get lucky even remote.9#10# Cheers - Max Moser - [email protected]11##1213class MetasploitModule < Msf::Auxiliary14include Msf::Auxiliary::Report15include Msf::Exploit::Capture1617def initialize18super(19'Name' => 'pSnuffle Packet Sniffer',20'Description' => 'This module sniffs passwords like dsniff did in the past.',21'Author' => 'Max Moser <mmo[at]remote-exploit.org>',22'License' => MSF_LICENSE,23'Actions' => [24[ 'Sniffer', { 'Description' => 'Run sniffer' } ],25[ 'List', { 'Description' => 'List protocols' } ]26],27'PassiveActions' => [ 'Sniffer' ],28'DefaultAction' => 'Sniffer',29'Notes' => {30'Stability' => [CRASH_SAFE],31'SideEffects' => [],32'Reliability' => []33}34)3536register_options [37OptString.new('PROTOCOLS', [true, 'A comma-delimited list of protocols to sniff or "all".', 'all']),38]3940register_advanced_options [41OptPath.new('ProtocolBase', [42true, 'The base directory containing the protocol decoders',43File.join(Msf::Config.data_directory, 'exploits', 'psnuffle')44]),45]46deregister_options('RHOSTS')47end4849def load_protocols50base = datastore['ProtocolBase']51unless File.directory? base52raise 'The ProtocolBase parameter is set to an invalid directory'53end5455allowed = datastore['PROTOCOLS'].split(',').map { |x| x.strip.downcase }56@protos = {}57decoders = Dir.new(base).entries.grep(/\.rb$/).sort58decoders.each do |n|59f = File.join(base, n)60m = ::Module.new61begin62m.module_eval(File.read(f, File.size(f)))63m.constants.grep(/^Sniffer(.*)/) do64proto = ::Regexp.last_match(1)65next unless allowed.include?(proto.downcase) || datastore['PROTOCOLS'] == 'all'6667klass = m.const_get("Sniffer#{proto}")68@protos[proto.downcase] = klass.new(framework, self)6970print_status("Loaded protocol #{proto} from #{f}...")71end72rescue StandardError => e73print_error("Decoder #{n} failed to load: #{e.class} #{e} #{e.backtrace}")74end75end76end7778def run79check_pcaprub_loaded # Check first80# Load all of our existing protocols81load_protocols8283if action.name == 'List'84print_status("Protocols: #{@protos.keys.sort.join(', ')}")85return86end8788print_status 'Sniffing traffic.....'89open_pcap9091each_packet do |pkt|92p = PacketFu::Packet.parse(pkt)93next unless p.is_tcp?94next if p.payload.empty?9596@protos.each_key do |k|97@protos[k].parse(p)98end99true100end101close_pcap102print_status 'Finished sniffing'103end104end105106# End module class107108# Basic class for taking care of sessions109class BaseProtocolParser110111attr_accessor :framework, :module, :sessions, :dport, :sigs112113def initialize(framework, mod)114self.framework = framework115self.module = mod116self.sessions = {}117self.dport = 0118register_sigs119end120121def parse(_pkt)122nil123end124125def register_sigs126self.sigs = {}127end128129#130# Glue methods to bridge parsers to the main module class131#132def print_status(msg)133self.module.print_status(msg)134end135136def print_error(msg)137self.module.print_error(msg)138end139140def report_cred(opts)141service_data = {142address: opts[:ip],143port: opts[:port],144service_name: opts[:service_name],145protocol: 'tcp',146workspace_id: self.module.myworkspace_id147}148149credential_data = {150origin_type: :service,151module_fullname: self.module.fullname,152username: opts[:user],153private_data: opts[:password],154private_type: opts[:type]155}.merge(service_data)156157if opts[:type] == :nonreplayable_hash158credential_data.merge!(jtr_format: opts[:jtr_format])159end160161login_data = {162core: self.module.create_credential(credential_data),163status: opts[:status],164proof: opts[:proof]165}.merge(service_data)166167unless opts[:status] == Metasploit::Model::Login::Status::UNTRIED168login_data.merge!(last_attempted_at: DateTime.now)169end170171self.module.create_credential_login(login_data)172end173174def report_note(*opts)175self.module.report_note(*opts)176end177178def report_service(*opts)179self.module.report_service(*opts)180end181182def find_session(sessionid)183purge_keys = []184sessions.each_key do |ses|185# Check for cleanup abilities... kills performance in large environments maybe186# When longer than 5 minutes no packet was related to the session, delete it187if ((sessions[ses][:mtime] - sessions[ses][:ctime]) > 300)188# too bad to this session has no action for a long time189purge_keys << ses190end191end192purge_keys.each { |ses| sessions.delete(ses) }193194# Does this session already exist?195if sessions[sessionid]196# Refresh the timestamp197sessions[sessionid][:mtime] = Time.now198elsif (sessionid =~ /^([^:]+):([^-]+)-([^:]+):(\d+)$/s)199# Create a new session entry along with the host/port from the id200sessions[sessionid] = {201client_host: ::Regexp.last_match(1),202client_port: ::Regexp.last_match(2),203host: ::Regexp.last_match(3),204port: ::Regexp.last_match(4),205session: sessionid,206ctime: Time.now,207mtime: Time.now208}209end210211sessions[sessionid]212end213214def get_session_src(pkt)215return "#{pkt.ip_daddr}:#{pkt.tcp_dport}-#{pkt.ip_saddr}-#{pkt.tcp_sport}" if pkt.is_tcp?216return "#{pkt.ip_daddr}:#{pkt.udp_dport}-#{pkt.ip_saddr}-#{pkt.udp_sport}" if pkt.is_udp?217218"#{pkt.ip_daddr}:0-#{pkt.ip_saddr}:0"219end220221def get_session_dst(pkt)222return "#{pkt.ip_saddr}:#{pkt.tcp_sport}-#{pkt.ip_daddr}:#{pkt.tcp_dport}" if pkt.is_tcp?223return "#{pkt.ip_saddr}:#{pkt.udp_sport}-#{pkt.ip_daddr}:#{pkt.udp_dport}" if pkt.is_udp?224225"#{pkt.ip_saddr}:0-#{pkt.ip_daddr}:0"226end227end228229230