## # 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. # https://metasploit.com/framework/ ## module Msf class Plugin::PcapLog < Msf::Plugin # Only little-endian is supported in this implementation. PCAP_FILE_HEADER = "\xD4\xC3\xB2\xA1\x02\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00`\x00\x00\x00\x01\x00\x00\x00".freeze # # Implements a pcap console command dispatcher. # class PcapLogDispatcher include Msf::Ui::Console::CommandDispatcher def name 'PcapLog' end def commands { 'pcap_filter' => 'Set/Get a BPF-style packet filter', 'pcap_dir' => 'Set/Get a directory to log pcaps to', 'pcap_prefix' => 'Set/Get a filename prefix to log pcaps to', 'pcap_iface' => 'Set/Get an interface to capture from', 'pcap_start' => 'Start a capture', 'pcap_stop' => 'Stop a running capture', 'pcap_show_config' => 'Show the current PcapLog configuration' } end def cmd_pcap_filter(*args) @filter = args.join(' ') || @filter print_line "#{name} BPF filter: #{@filter}" end def cmd_pcap_prefix(*args) @prefix = args[0] || @prefix || 'msf3-session' print_line "#{name} prefix: #{@prefix}" end def cmd_pcap_dir(*args) @dir = args[0] || @dir || '/tmp' print_line "#{name} Directory: #{@dir}" end def cmd_pcap_iface(*args) @iface = args[0] || @iface print_line "#{name} Interface: #{@iface}" end def cmd_pcap_start(*_args) unless @pcaprub_loaded print_error('Pcap module not available') return false end if @capture_thread && @capture_thread.alive? print_error 'Capture already started.' return false end gen_fname print_line "Starting packet capture from #{@iface} to #{@fname}" okay, msg = validate_options unless okay print_error msg return false end dev = (@iface || ::Pcap.lookupdev) @capture_file.write(PCAP_FILE_HEADER) @capture_file.flush @pcap = ::Pcap.open_live(dev, 65535, true, 1) @pcap.setfilter(@filter) if @filter @capture_thread = Thread.new do @pcap.each do |pkt| @capture_file.write(convert_to_pcap(pkt)) @capture_file.flush end end end def cmd_pcap_stop(*_args) if @capture_thread && @capture_thread.alive? print_line "Stopping packet capture from #{@iface} to #{@fname}" print_line "Capture Stats: #{@pcap.stats.inspect}" @pcap = nil @capture_file.close if @capture_file.respond_to? :close @capture_thread.kill @capture_thread = nil else print_error 'No capture running.' end end def convert_to_pcap(packet) t = Time.now sz = packet.size [t.to_i, t.usec, sz, sz, packet].pack('V4A*') end def gen_fname t = Time.now file_part = format('%s_%04d-%02d-%02d_%02d-%02d-%02d.pcap', @prefix, t.year, t.month, t.mday, t.hour, t.min, t.sec) @fname = File.join(@dir, file_part) end # Check for euid 0 and check for a valid place to write files def validate_options # Check for root. unless Process.euid.zero? msg = 'You must run as root in order to capture packets.' return [false, msg] end # Check directory suitability. unless File.directory? @dir msg = "Invalid pcap directory specified: '#{@dir}'" return [false, msg] end unless File.writable? @dir msg = "No write permission to directory: '#{@dir}'" return [false, msg] end @capture_file = File.open(@fname, 'ab') unless File.writable? @fname msg = "Cannot write to file: '#{@fname}'" return [false, msg] end # If you got this far, you're golden. msg = "We're good!" return [true, msg] end # Need to pretend to have a datastore for Exploit::Capture to # function. def datastore {} end def initialize(*args) super @dir = File.join(Msf::Config.config_directory, 'logs') @prefix = 'msf3-session' @filter = nil @pcaprub_loaded = false begin require 'pcaprub' @pcaprub_loaded = true @iface = ::Pcap.lookupdev rescue ::Exception => e print_error "#{e.class}: #{e}" @pcaprub_loaded = false @pcaprub_error = e end end end def initialize(framework, opts) super add_console_dispatcher(PcapLogDispatcher) print_status 'PcapLog plugin loaded.' end # Kill the background thread def cleanup @capture_thread.kill if @capture_thread && @capture_thread.alive? @capture_file.close if @capture_file.respond_to? :close remove_console_dispatcher('PcapLog') end def name 'pcap_log' end def desc 'Logs all socket operations to pcaps (in /tmp by default)' end end end