source: repository/lib/Metabrik/Network/Arp.pm

Last change on this file was 992:6bd6acfc81d5, checked in by GomoR <gomor@…>, 2 weeks ago
  • update: copyright notice
  • new: support for Kali Linux operating system
  • remove: api::onyphe is no more included, it use handled in its own repository
File size: 9.9 KB
Line 
1#
2# $Id$
3#
4# network::arp Brik
5#
6package Metabrik::Network::Arp;
7use strict;
8use warnings;
9
10use base qw(Metabrik::Network::Frame Metabrik::System::Package);
11
12sub brik_properties {
13   return {
14      revision => '$Revision$',
15      tags => [ qw(unstable cache poison eui64 discover scan eui-64) ],
16      author => 'GomoR <GomoR[at]metabrik.org>',
17      license => 'http://opensource.org/licenses/BSD-3-Clause',
18      attributes => {
19         try => [ qw(try_count) ],
20         rtimeout => [ qw(timeout_seconds) ],
21         count => [ qw(count) ],
22         max_runtime => [ qw(max_runtime) ],
23         device => [ qw(device) ],
24         _pidfile => [ qw(INTERNAL) ],
25      },
26      attributes_default => {
27         try => 2,
28         rtimeout => 2,
29      },
30      commands => {
31         install => [ ], # Inherited
32         cache => [ ],
33         half_poison => [ qw(gateway victim|OPTIONAL device|OPTIONAL) ],
34         full_poison => [ qw(gateway victim|OPTIONAL device|OPTIONAL) ],
35         mac2eui64 => [ qw(mac_address) ],
36         scan => [ qw(subnet|OPTIONAL device[OPTIONAL) ],
37         get_ipv4_neighbors => [ qw(subnet|OPTIONAL device|OPTIONAL) ],
38         get_ipv6_neighbors => [ qw(subnet|OPTIONAL device|OPTIONAL) ],
39         get_mac_neighbors => [ qw(subnet|OPTIONAL device|OPTIONAL) ],
40         stop_poison => [ ],
41      },
42      require_modules => {
43         'Net::Frame::Layer::ARP' => [ ],
44         'Net::Libdnet::Arp' => [ ],
45         'Metabrik::Network::Address' => [ ],
46         'Metabrik::Network::Arp' => [ ],
47         'Metabrik::Network::Read' => [ ],
48         'Metabrik::Network::Write' => [ ],
49         'Metabrik::Shell::Command' => [ ],
50         'Metabrik::System::Process' => [ ],
51      },
52      optional_binaries => {
53         arpspoof => [ ],
54      },
55      need_packages => {
56         ubuntu => [ qw(dsniff libnet-libdnet-perl) ],
57         debian => [ qw(dsniff libnet-libdnet-perl) ],
58         kali => [ qw(dsniff libnet-libdnet-perl) ],
59         freebsd => [ qw(libdnet) ],
60      },
61   };
62}
63
64sub brik_use_properties {
65   my $self = shift;
66
67   return {
68      attributes_default => {
69         device => defined($self->global) && $self->global->device || 'eth0',
70      },
71   };
72}
73
74sub _loop {
75   my ($entry, $data) = @_;
76
77   $data->{ip}->{$entry->{arp_pa}} = $entry->{arp_ha};
78   $data->{mac}->{$entry->{arp_ha}} = $entry->{arp_pa};
79
80   return $data;
81}
82
83sub cache {
84   my $self = shift;
85
86   my $dnet = Net::Libdnet::Arp->new;
87
88   my %data = ();
89   $dnet->loop(\&_loop, \%data);
90
91   return \%data;
92}
93
94sub half_poison {
95   my $self = shift;
96   my ($gateway, $victim, $device) = @_;
97
98   if (! $self->brik_has_binary("arpspoof")) {
99      return $self->log->error("half_poison: you have to install dsniff package");
100   }
101
102   $device ||= $self->device;
103   $self->brik_help_run_undef_arg('half_poison', $gateway) or return;
104   $self->brik_help_run_undef_arg('half_poison', $device) or return;
105
106   my $cmd = "arpspoof -i $device -c both";
107   $cmd .= " -t $victim" if defined($victim);  # Or default to all LAN hosts
108   $cmd .= " $gateway";
109
110   my $sc = Metabrik::Shell::Command->new_from_brik_init($self) or return;
111   my $sp = Metabrik::System::Process->new_from_brik_init($self) or return;
112   my $pidfile = $sp->start(sub { $sc->system($cmd) });
113   $self->_pidfile($pidfile);
114
115   $self->log->info("half_poison: daemonized to pidfile[$pidfile]");
116
117   return 1;
118}
119
120sub full_poison {
121   my $self = shift;
122   my ($gateway, $victim, $device) = @_;
123
124   if (! $self->brik_has_binary("arpspoof")) {
125      return $self->log->error("full_poison: you have to install dsniff package");
126   }
127
128   $device ||= $self->device;
129   $self->brik_help_run_undef_arg('full_poison', $gateway) or return;
130   $self->brik_help_run_undef_arg('full_poison', $device) or return;
131
132   my $cmd = "arpspoof -i $device -c both -r";
133   $cmd .= " -t $victim" if defined($victim);  # Or default to all LAN hosts
134   $cmd .= " $gateway";
135
136   my $sc = Metabrik::Shell::Command->new_from_brik_init($self) or return;
137   my $sp = Metabrik::System::Process->new_from_brik_init($self) or return;
138   my $pidfile = $sp->start(sub { $sc->system($cmd) });
139   $self->_pidfile($pidfile);
140
141   $self->log->info("full_poison: daemonized to pidfile[$pidfile]");
142
143   return 1;
144}
145
146# Taken from Net::SinFP3
147sub mac2eui64 {
148   my $self = shift;
149   my ($mac) = @_;
150
151   $self->brik_help_run_undef_arg('mac2eui64', $mac) or return;
152
153   if ($mac !~ /^[0-9a-z]{2}:[0-9a-z]{2}:[0-9a-z]{2}:[0-9a-z]{2}:[0-9a-z]{2}:[0-9a-z]{2}$/i) {
154      return $self->log->error("mac2eui64: invalid MAC address [$mac]");
155   }
156
157   my @b  = split(':', $mac);
158   my $b0 = hex($b[0]) ^ 2;
159
160   return sprintf("fe80::%x%x:%xff:fe%x:%x%x", $b0, hex($b[1]), hex($b[2]),
161      hex($b[3]), hex($b[4]), hex($b[5]));
162}
163
164sub _get_arp_frame {
165   my $self = shift;
166   my ($dst_ip) = @_;
167
168   my $eth = $self->eth;
169   $eth->type(0x0806);  # ARP
170
171   my $arp = $self->arp($dst_ip);
172   my $frame = $self->frame([ $eth, $arp ]);
173
174   return $frame;
175}
176
177sub scan {
178   my $self = shift;
179   my ($subnet, $device) = @_;
180
181   $self->brik_help_run_must_be_root('scan') or return;
182
183   $device ||= $self->device;
184   $self->brik_help_run_undef_arg('scan', $device) or return;
185
186   my $interface = $self->get_device_info($device) or return;
187
188   $subnet ||= $interface->{subnet4};
189   $self->brik_help_run_undef_arg('scan', $subnet) or return;
190
191   my $arp_cache = $self->cache
192      or return $self->log->error("scan: cache failed");
193
194   my $scan_arp_cache = {};
195   for my $this (keys %{$arp_cache->{mac}}) {
196      my $mac = $this;
197      my $ip = $arp_cache->{mac}{$this};
198      $self->log->verbose("scan: found MAC [$mac] in cache for IPv4 [$ip]");
199      $scan_arp_cache->{$ip} = $mac;
200   }
201
202   my $na = Metabrik::Network::Address->new_from_brik_init($self) or return;
203
204   my $ip_list = $na->ipv4_list($subnet) or return;
205
206   my $reply_cache = {};
207   my @frame_list = ();
208   for my $ip (@$ip_list) {
209      # We scan ARP for everyone but our own IP
210      if (exists($interface->{ipv4}) && $ip eq $interface->{ipv4}) {
211         next;
212      }
213
214      if (exists($scan_arp_cache->{$ip})) {
215         my $mac = $scan_arp_cache->{$ip};
216         $reply_cache->{$ip} = $mac;
217      }
218      else {
219         # If it is not in ARP cache yet
220         push @frame_list, $self->_get_arp_frame($ip);
221      }
222   }
223
224   my $nw = Metabrik::Network::Write->new_from_brik_init($self) or return;
225
226   my $write = $nw->open(2, $interface->{device}) or return;
227
228   my $nr = Metabrik::Network::Read->new_from_brik_init($self) or return;
229   $nr->rtimeout($self->rtimeout);
230   $nr->count($self->count || 0);
231
232   my $filter = 'arp and src net '.$subnet.' and dst host '.$interface->{ipv4};
233   my $read = $nr->open(2, $interface->{device}, $filter) or return;
234
235   # We will send frames 3 times max
236   my $try = $self->try;
237   for my $t (1..$try) {
238      # We send all frames
239      for my $r (@frame_list) {
240         $self->log->debug($r->print);
241         my $dst_ip = $r->ref->{ARP}->dstIp;
242         if (! exists($reply_cache->{$dst_ip})) {
243            $nw->send($r->raw)
244               or $self->log->warning("scan: send failed");
245         }
246      }
247
248      # Then we wait for all replies until a timeout occurs
249      my $h_list = $nr->read_until_timeout;
250      for my $h (@$h_list) {
251         my $r = $self->from_read($h);
252         #$self->log->verbose("scan: read next returned some stuff".$r->print);
253
254         if ($r->ref->{ARP}->opCode != &Net::Frame::Layer::ARP::NF_ARP_OPCODE_REPLY) {
255            next;
256         }
257
258         my $src_ip = $r->ref->{ARP}->srcIp;
259         if (! exists($reply_cache->{$src_ip})) {
260            my $mac = $r->ref->{ARP}->src;
261            $self->log->info("scan: received MAC [$mac] for IPV4 [$src_ip]");
262            $reply_cache->{$src_ip} = $r->ref->{ARP}->src;
263
264            # Put it in ARP cache table for next round
265            $scan_arp_cache->{$src_ip} = $mac;
266         }
267      }
268
269      $nr->reset_timeout;
270   }
271
272   $nw->close;
273   $nr->close;
274
275   my %results = ();
276   for (keys %$reply_cache) {
277      my $mac = $reply_cache->{$_};
278      my $ip4 = $_;
279      my $ip6 = $self->mac2eui64($mac);
280      $self->log->verbose(sprintf("%-16s => %s  [%s]", $ip4, $mac, $ip6));
281      $results{by_ipv4}{$ip4} = { ipv6 => $ip6, mac => $mac, ipv4 => $ip4 };
282      $results{by_mac}{$mac} = { ipv6 => $ip6, mac => $mac, ipv4 => $ip4 };
283      $results{by_ipv6}{$ip6} = { ipv6 => $ip6, mac => $mac, ipv4 => $ip4 };
284   }
285
286   return \%results;
287}
288
289sub get_ipv4_neighbors {
290   my $self = shift;
291   my ($subnet, $device) = @_;
292
293   my $scan = $self->scan($subnet, $device) or return;
294   my $ipv4 = $scan->{by_ipv4};
295   if (! defined($ipv4)) {
296      return $self->log->info("get_ipv4_neighbors: no IPv4 neighbor found");
297   }
298
299   return $ipv4;
300}
301
302sub get_ipv6_neighbors {
303   my $self = shift;
304   my ($subnet, $device) = @_;
305
306   my $scan = $self->scan($subnet, $device) or return;
307   my $ipv6 = $scan->{by_ipv6};
308   if (! defined($ipv6)) {
309      return $self->log->info("get_ipv6_neighbors: no IPv6 neighbor found");
310   }
311
312   return $ipv6;
313}
314
315sub get_mac_neighbors {
316   my $self = shift;
317   my ($subnet, $device) = @_;
318
319   my $scan = $self->scan($subnet, $device) or return;
320   my $mac = $scan->{by_mac};
321   if (! defined($mac)) {
322      return $self->log->info("get_mac_neighbors: no MAC neighbor found");
323   }
324
325   return $mac;
326}
327
328sub stop_poison {
329   my $self = shift;
330
331   my $pidfile = $self->_pidfile;
332   if (defined($pidfile)) {
333      my $sp = Metabrik::System::Process->new_from_brik_init($self) or return;
334      $sp->force_kill(1);
335      $sp->kill_from_pidfile($pidfile);
336      $self->log->verbose("stop_poison: killing arpspoof process");
337      $self->_pidfile(undef);
338   }
339
340   return 1;
341}
342
343sub brik_fini {
344   my $self = shift;
345
346   return $self->stop_poison;
347}
348
3491;
350
351__END__
352
353=head1 NAME
354
355Metabrik::Network::Arp - network::arp Brik
356
357=head1 COPYRIGHT AND LICENSE
358
359Copyright (c) 2014-2019, Patrice E<lt>GomoRE<gt> Auffret
360
361You may distribute this module under the terms of The BSD 3-Clause License.
362See LICENSE file in the source distribution archive.
363
364=head1 AUTHOR
365
366Patrice E<lt>GomoRE<gt> Auffret
367
368=cut
Note: See TracBrowser for help on using the repository browser.