metasploit-framework/plugins/pcap_log.rb

191 lines
5.3 KiB
Ruby

##
# 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