#!/usr/bin/perl use Event; use Time::HiRes qw(time); use Curses; use Socket; use Storable; use GPS; my $GPS = eval { new GPS }; $DEV = "eth1"; $RAWDEV = "wifi0"; $PROC = "/proc/driver/aironet/$DEV"; $STOREFILE = "wvsniff.dat"; $SIG{INT} = $SIG{TERM} = $SIG{HUP} = sub { exit }; sub config { open my $config, ">$PROC/Config" or die "$PROC/Config: $!"; print $config $_[0]; } config "Mode: yna\n"; system "ifconfig", $DEV, "up"; system "ifconfig", $RAWDEV, "up"; my $initscr; sub init_screen { initscr; $initscr = 1; start_color; cbreak; noecho; immedok 0; nonl; raw; intrflush 0; keypad 1; idlok 1; scrollok 0; leaveok 0; } sub end_win { endwin if $initscr--; } init_screen; sub signal_quality { open my $stats, "<", "$PROC/Status" or return 0; sysread $stats, $buf, 1024; $buf =~ /Signal Quality: (\d+)/ && $1; } my $refresh = Event->idle(nice => -5, parked => 1, min => 0.01, max => 1, repeat => 0, cb => sub { @menu = (); &$curmenu; $menu_idx += @menu if $menu_idx < 0; $menu_idx -= @menu if $menu_idx >= @menu; erase; my $data = $GPS ? $GPS->data : {}; move 0, 0; addstr $frame++ . ": " . signal_quality . " " . localtime($data->{time}) . " $data->{lat} $data->{long} $data->{alt}m"; for my $idx (0 .. $#menu) { move 2 + $idx, 0; standout if $idx == $menu_idx; addstr $menu[$idx][0]; standend if $idx == $menu_idx; } move 3 + scalar@menu, 0; eval { $menu[$menu_idx][1]->(); }; refresh; }); Event->timer(after => 0, interval => 2, cb => sub { $refresh->start }); Event->io(fd => \*STDIN, poll => 'r', cb => sub { my $ch = getch; if ($ch eq "\x1b" or $ch eq "q") { Event::unloop; } elsif ($ch eq KEY_DOWN or $ch eq "j") { $menu_idx++; } elsif ($ch eq KEY_UP or $ch eq "k") { $menu_idx--; } else { eval { $menu[$menu_idx][2]->($ch); }; } $refresh->start; }); -r $STOREFILE and $db = Storable::retrieve $STOREFILE; END { $db->{version} = 0; Storable::store $db, $STOREFILE; end_win; config "Mode: adhoc\n"; } sub decode_tags { my $pkt = shift; my %tag; while ($pkt) { my ($id, $len) = unpack "C C", $pkt; my $data = substr $pkt, 2, $len; $pkt = substr $pkt, 2 + $len; $id = ({ 0 => SSID, 1 => rates, 2 => FH, 3 => DS, 4 => CF, 5 => TIM, 6 => IBSS, 16 => challenge, })->{$id}; $tag{$id} = $data; } %tag; } sub PF_PACKET (){ 17 } sub SOCK_RAW (){ 3 } sub ETH_P_ALL (){ 0x0003 } sub SIOCGIFINDEX (){ 0x000008933 } sub open_pcap_socket { socket my $cap, PF_PACKET, SOCK_RAW, (unpack "s", pack "n", ETH_P_ALL) or die "unable to open packet socket: $!"; my $ifidx = pack "a16 I", $RAWDEV; ioctl $cap, SIOCGIFINDEX, $ifidx or die "can't get index of interface $RAWDEV: $!"; $ifidx = unpack "x16 I", $ifidx; bind $cap, pack "S! S! I S! C C a8", PF_PACKET, (unpack "s", pack "n", ETH_P_ALL), $ifidx or die "unable to bind to interface: $!"; $cap; } sub e2h($) { join ":", map unpack("H*", $_), split //, $_[0]; } sub add_menu { my ($title, $display, $select) = @_; push @menu, [$title, $display, $select]; } $curmenu = \&menu_bss_list; sub bss2string { my $bss = shift; my $s = "AGE(" . int(time - $bss->{ts}) . ") " . "IP($bss->{ip}) " . "ARP($bss->{arp}) "; while (my ($k, $v) = each %{$bss->{ap}}) { $s .= "(" . e2h($k) . " $v->{mode} '$v->{essid}') "; } $s; } sub show_map { my ($map, $w, $h) = @_; my $data = $GPS->data; local $map->{"$data->{lat};$data->{long}"} = 0; my @data = (("-" x $w) x $h); my ($x1, $y1, $x2, $y2) = (1e6, 1e6, 1e-6, 1e-6); my $level = 1; while (my ($k, $v) = each %$map) { my ($y, $x) = split /;/, $k; $x1 = $x if $x1 > $x; $x2 = $x if $x2 < $x; $y1 = $y if $y1 > $y; $y2 = $y if $y2 < $y; $level = $v if $level < $v; } $x2 = ($w - 1) / (($x2 - $x1) || 1e-6); $y2 = ($h - 1) / (($y2 - $y1) || 1e-6); while (my ($k, $v) = each %$map) { my ($y, $x) = split /;/, $k; $y = ($y - $y1) * $y2; $x = ($x - $x1) * $x2; substr $data[$y], $x, 1, int($map->{$k} * 9 / $level); } for (@data) { addstr $_ . "\n"; } } sub display_bss { my $bss = shift; addstr "TS: $bss->{ts}\n"; addstr "WEP/Weak packets received: $bss->{wep}/$bss->{weak}\n"; addstr "ARP/IP packets received: $bss->{arp}/$bss->{ip}\n"; addstr "\naccess points\n"; while (my ($k, $v) = each %{$bss->{ap}}) { addstr " " . e2h($k) . "\n"; addstr " mode $v->{mode}; channel $v->{channel}; essid '$v->{essid}'; beacon frames $v->{beacon}\n"; } addstr "\nstations\n"; while (my ($k, $v) = each %{$bss->{station}}) { addstr " " . e2h($k); while (my ($k, $v) = each %$v) { addstr " " . (inet_ntoa $k) . "($v)"; } addstr "\n"; } if ($bss->{gps}) { my ($x, $y); getyx $y, $x; addstr "\nmap\n"; my $h = (&getmaxy - $y) - 3; show_map $bss->{gps}, int($h*1.8), $h; } } sub menu_bss_list { my @menu; for my $k (sort { int $db->{$b}{ts} <=> int $db->{$a}{ts} } keys %$db) { my $v = $db->{$k}; add_menu e2h($k) . " " . bss2string($v), sub { display_bss $v }, sub { if ($_[0] eq KEY_LEFT or $_[0] eq "h") { Event::unloop; } elsif ($_[0] eq KEY_RIGHT or $_[0] eq "l") { #$curmenu = sub { display_bss $k }; } }; } } sub activity { if ($GPS) { my $data = $GPS->data; $db->{$_[0]}{gps}{"$data->{lat};$data->{long}"} = signal_quality; } $db->{$_[0]}->{ts} = time; $refresh->start; } sub reg_ip { my ($bssid, $ip, $ether) = @_; $db->{$bssid}{station}{$ether}{$ip}++; activity $bssid; } { my $cap = open_pcap_socket; my $pkt; open LOG, ">/tmp/log";#d# Event->io(fd => $cap, poll => 'r', cb => sub { sysread $$cap, $pkt, 120; printf LOG "%s\n", unpack "H*", $pkt; my ($fc1, $fc2, $sid, $a1, $a2, $a3, $sc, $pkt) = unpack "C C n a6 a6 a6 S a*", $pkt; my $a4; if ($fc2 & 0x03 == 0x03) { $a4 = substr $pkt, 0, 6; $pkt = substr $pkt, 6; } if ($fc1 == 0x80 or $fc1 == 0x50) { my ($ts1, $ts2, $bi, $cf, $pkt) = unpack "L L S S a*", $pkt; my %tag = decode_tags $pkt; my $ap = $db->{$a3}{ap}{$a2} ||= {}; $ap->{mode} = ($cf & 3) == 1 ? "AP" : "adhoc"; # ESSID ' ' workaround for "closed-network feature" $ap->{essid} = $tag{SSID} unless $ap->{essid} && $tag{SSID} eq " "; $ap->{channel} = ord $tag{DS}; $ap->{beacon}++; activity $a3; } elsif ($fc1 == 0x08) { my $bssid = ($a3, $a1, $a2, $a2)[$fc2 & 3]; if ($fc2 & 0x40) { my ($iv1, $iv2, $iv3, $ivopt, $byte) = unpack "C C C C C", $pkt; $db->{$bssid}{wep}++; if ($iv1 > 2 and $iv1 < 14 and $iv2 == 255) { $db->{$bssid}{weak}++; } } else { my ($llc, $et, $pkt) = unpack "a6 n a*", $pkt; if ($llc eq "\xaa\xaa\x03\x00\x00\x00" or $llc eq "\xaa\xaa\x03\x00\x00\xf8") { # SNAP/UI/{ENCAP_ETHER,CISCO} if ($et == 0x0800) { my ($vhl, $tos, $len, $id, $off, $ttl, $prot, $sum, $src, $dst, $pkt) = unpack "C C n n n C C n a4 a4 a*", $pkt; if ($vhl & 0x40 == 0x40) { $db->{$bssid}{ip}++; reg_ip $bssid, $src, ($a2, $a2, $a3, $a4)[$fc2 & 3]; reg_ip $bssid, $dst, ($a1, $a3, $a1, $a3)[$fc2 & 3]; } } elsif ($et == 0x0806) { my ($hrd, $pro, $hln, $pln, $op, $src_hw, $src, $dst_hw, $dst) = unpack "n n C C n a6 a4 a6 a4", $pkt; if ($hrd == 1 and $hln == 6 and $pln == 4) { reg_ip $bssid, $src, $src_hw; reg_ip $bssid, $dst, $dst_hw if $op != 1; $db->{$bssid}{arp}++; } } } else { 0 && printf "?? %02x %02x %04x %04x: %s.%s \n", $fc1, $fc2, $sid, $sc, (unpack "H*", $llc), unpack "H*", $pkt; } } } elsif ($fc1 != 0x40 and $fc1 != 0x10 and $fc1 != 0x00 and $fc1 != 0xb0) { 0 && printf "%02x %02x %04x %04x: %s \n", $fc1, $fc2, $sid, $sc, unpack "H*", $pkt; } }); } Event::loop;