| 1 |
#!/usr/bin/perl |
| 2 |
|
| 3 |
use Event; |
| 4 |
use Time::HiRes qw(time); |
| 5 |
use Curses; |
| 6 |
use Socket; |
| 7 |
use Storable; |
| 8 |
|
| 9 |
$DEV = "eth1"; |
| 10 |
$RAWDEV = "wifi0"; |
| 11 |
$PROC = "/proc/driver/aironet/$DEV"; |
| 12 |
$STOREFILE = "wvsniff.dat"; |
| 13 |
|
| 14 |
my $GPS = eval { require GPSx; new GPS }; |
| 15 |
|
| 16 |
$SIG{INT} = |
| 17 |
$SIG{TERM} = |
| 18 |
$SIG{HUP} = sub { exit }; |
| 19 |
|
| 20 |
sub config { |
| 21 |
open my $config, ">$PROC/Config" |
| 22 |
or die "$PROC/Config: $!"; |
| 23 |
print $config $_[0]; |
| 24 |
} |
| 25 |
|
| 26 |
config "Mode: yna\n"; |
| 27 |
system "ifconfig", $DEV, "up"; |
| 28 |
system "ifconfig", $RAWDEV, "up"; |
| 29 |
|
| 30 |
my $initscr; |
| 31 |
sub init_screen { |
| 32 |
initscr; |
| 33 |
$initscr = 1; |
| 34 |
start_color; |
| 35 |
cbreak; noecho; |
| 36 |
immedok 0; nonl; raw; intrflush 0; keypad 1; |
| 37 |
idlok 1; scrollok 0; leaveok 0; |
| 38 |
} |
| 39 |
|
| 40 |
sub end_win { |
| 41 |
endwin if $initscr--; |
| 42 |
} |
| 43 |
|
| 44 |
init_screen; |
| 45 |
|
| 46 |
sub signal_quality { |
| 47 |
open my $stats, "<", "$PROC/Status" |
| 48 |
or return 0; |
| 49 |
sysread $stats, $buf, 1024; |
| 50 |
$buf =~ /Signal Quality: (\d+)/ && $1; |
| 51 |
} |
| 52 |
|
| 53 |
my $refresh = Event->idle(nice => -5, parked => 1, min => 0.01, max => 1, repeat => 0, cb => sub { |
| 54 |
@menu = (); |
| 55 |
&$curmenu; |
| 56 |
|
| 57 |
$menu_idx += @menu if $menu_idx < 0; |
| 58 |
$menu_idx -= @menu if $menu_idx >= @menu; |
| 59 |
|
| 60 |
erase; |
| 61 |
|
| 62 |
my $data = $GPS ? $GPS->data : {}; |
| 63 |
|
| 64 |
move 0, 0; |
| 65 |
addstr $frame++ . ": " . signal_quality . " " . localtime($data->{time}) . " $data->{lat} $data->{long} $data->{alt}m"; |
| 66 |
|
| 67 |
for my $idx (0 .. $#menu) { |
| 68 |
move 2 + $idx, 0; |
| 69 |
standout if $idx == $menu_idx; |
| 70 |
addstr $menu[$idx][0]; |
| 71 |
standend if $idx == $menu_idx; |
| 72 |
} |
| 73 |
|
| 74 |
move 3 + scalar@menu, 0; |
| 75 |
eval { |
| 76 |
$menu[$menu_idx][1]->(); |
| 77 |
}; |
| 78 |
|
| 79 |
refresh; |
| 80 |
}); |
| 81 |
|
| 82 |
Event->timer(after => 0, interval => 2, cb => sub { $refresh->start }); |
| 83 |
|
| 84 |
Event->io(fd => \*STDIN, poll => 'r', cb => sub { |
| 85 |
my $ch = getch; |
| 86 |
if ($ch eq "\x1b" or $ch eq "q") { |
| 87 |
Event::unloop; |
| 88 |
} elsif ($ch eq KEY_DOWN or $ch eq "j") { |
| 89 |
$menu_idx++; |
| 90 |
} elsif ($ch eq KEY_UP or $ch eq "k") { |
| 91 |
$menu_idx--; |
| 92 |
} else { |
| 93 |
eval { |
| 94 |
$menu[$menu_idx][2]->($ch); |
| 95 |
}; |
| 96 |
} |
| 97 |
$refresh->start; |
| 98 |
}); |
| 99 |
|
| 100 |
-r $STOREFILE and |
| 101 |
$db = Storable::retrieve $STOREFILE; |
| 102 |
|
| 103 |
END { |
| 104 |
$db->{version} = 0; |
| 105 |
Storable::store $db, $STOREFILE; |
| 106 |
end_win; |
| 107 |
config "Mode: adhoc\n"; |
| 108 |
} |
| 109 |
|
| 110 |
sub decode_tags { |
| 111 |
my $pkt = shift; |
| 112 |
my %tag; |
| 113 |
|
| 114 |
while ($pkt) { |
| 115 |
my ($id, $len) = unpack "C C", $pkt; |
| 116 |
my $data = substr $pkt, 2, $len; |
| 117 |
$pkt = substr $pkt, 2 + $len; |
| 118 |
|
| 119 |
$id = ({ |
| 120 |
0 => SSID, |
| 121 |
1 => rates, |
| 122 |
2 => FH, |
| 123 |
3 => DS, |
| 124 |
4 => CF, |
| 125 |
5 => TIM, |
| 126 |
6 => IBSS, |
| 127 |
16 => challenge, |
| 128 |
})->{$id}; |
| 129 |
$tag{$id} = $data; |
| 130 |
} |
| 131 |
|
| 132 |
%tag; |
| 133 |
} |
| 134 |
|
| 135 |
sub PF_PACKET (){ 17 } |
| 136 |
sub SOCK_RAW (){ 3 } |
| 137 |
sub ETH_P_ALL (){ 0x0003 } |
| 138 |
sub SIOCGIFINDEX (){ 0x000008933 } |
| 139 |
|
| 140 |
sub open_pcap_socket { |
| 141 |
socket my $cap, PF_PACKET, SOCK_RAW, (unpack "s", pack "n", ETH_P_ALL) |
| 142 |
or die "unable to open packet socket: $!"; |
| 143 |
|
| 144 |
my $ifidx = pack "a16 I", $RAWDEV; |
| 145 |
ioctl $cap, SIOCGIFINDEX, $ifidx |
| 146 |
or die "can't get index of interface $RAWDEV: $!"; |
| 147 |
$ifidx = unpack "x16 I", $ifidx; |
| 148 |
|
| 149 |
$::sockaddr = pack "S! S! I S! C C a8", PF_PACKET, (unpack "s", pack "n", ETH_P_ALL), $ifidx; |
| 150 |
bind $cap, $::sockaddr |
| 151 |
or die "unable to bind to interface: $!"; |
| 152 |
|
| 153 |
$cap; |
| 154 |
} |
| 155 |
|
| 156 |
sub escape($) { |
| 157 |
local $_ = $_[0]; |
| 158 |
s/([^\x20-\x7e])/sprintf "\\%03o", ord $1/ge; |
| 159 |
$_; |
| 160 |
} |
| 161 |
|
| 162 |
sub e2h($) { |
| 163 |
join ":", map unpack("H*", $_), split //, $_[0]; |
| 164 |
} |
| 165 |
|
| 166 |
sub add_menu { |
| 167 |
my ($title, $display, $select) = @_; |
| 168 |
push @menu, [$title, $display, $select]; |
| 169 |
} |
| 170 |
|
| 171 |
$curmenu = \&menu_bss_list; |
| 172 |
|
| 173 |
sub bss2string { |
| 174 |
my $bss = shift; |
| 175 |
|
| 176 |
my $s = "AGE(" . int(time - $bss->{ts}) . ") " |
| 177 |
. "IP($bss->{ip}) " |
| 178 |
. "ARP($bss->{arp}) "; |
| 179 |
while (my ($k, $v) = each %{$bss->{ap}}) { |
| 180 |
$s .= "(" . e2h($k) . " $v->{mode} '".escape($v->{essid})."') "; |
| 181 |
} |
| 182 |
$s; |
| 183 |
} |
| 184 |
|
| 185 |
sub show_map { |
| 186 |
my ($map, $w, $h) = @_; |
| 187 |
|
| 188 |
my $data = $GPS->data; |
| 189 |
local $map->{"$data->{lat};$data->{long}"} = 0; |
| 190 |
|
| 191 |
my @data = (("-" x $w) x $h); |
| 192 |
|
| 193 |
my ($x1, $y1, $x2, $y2) = (1e6, 1e6, 1e-6, 1e-6); |
| 194 |
my $level = 1; |
| 195 |
|
| 196 |
while (my ($k, $v) = each %$map) { |
| 197 |
my ($y, $x) = split /;/, $k; |
| 198 |
$x1 = $x if $x1 > $x; |
| 199 |
$x2 = $x if $x2 < $x; |
| 200 |
$y1 = $y if $y1 > $y; |
| 201 |
$y2 = $y if $y2 < $y; |
| 202 |
$level = $v if $level < $v; |
| 203 |
} |
| 204 |
|
| 205 |
$x2 = ($w - 1) / (($x2 - $x1) || 1e-6); |
| 206 |
$y2 = ($h - 1) / (($y2 - $y1) || 1e-6); |
| 207 |
|
| 208 |
while (my ($k, $v) = each %$map) { |
| 209 |
my ($y, $x) = split /;/, $k; |
| 210 |
|
| 211 |
$y = ($y - $y1) * $y2; |
| 212 |
$x = ($x - $x1) * $x2; |
| 213 |
|
| 214 |
substr $data[$h-1-int $y], $x, 1, int($map->{$k} * 9 / $level); |
| 215 |
} |
| 216 |
|
| 217 |
for (@data) { |
| 218 |
addstr $_ . "\n"; |
| 219 |
} |
| 220 |
} |
| 221 |
|
| 222 |
sub display_bss { |
| 223 |
my $bss = shift; |
| 224 |
|
| 225 |
addstr "TS: $bss->{ts}\n"; |
| 226 |
addstr "WEP/Weak packets received: $bss->{wep}/$bss->{weak}\n"; |
| 227 |
addstr "ARP/IP packets received: $bss->{arp}/$bss->{ip}\n"; |
| 228 |
|
| 229 |
addstr "\naccess points\n"; |
| 230 |
|
| 231 |
while (my ($k, $v) = each %{$bss->{ap}}) { |
| 232 |
addstr " " . e2h($k) . "\n"; |
| 233 |
addstr " mode $v->{mode}; channel $v->{channel}; essid '".escape($v->{essid})."'; beacon frames $v->{beacon}\n"; |
| 234 |
} |
| 235 |
|
| 236 |
addstr "\nstations\n"; |
| 237 |
|
| 238 |
while (my ($k, $v) = each %{$bss->{station}}) { |
| 239 |
addstr " " . e2h($k); |
| 240 |
while (my ($k, $v) = each %$v) { |
| 241 |
addstr " " . (inet_ntoa $k) . "($v)"; |
| 242 |
} |
| 243 |
addstr "\n"; |
| 244 |
} |
| 245 |
|
| 246 |
if ($bss->{gps}) { |
| 247 |
my ($x, $y); getyx $y, $x; |
| 248 |
addstr "\nmap\n"; |
| 249 |
my $h = (&getmaxy - $y) - 3; |
| 250 |
show_map $bss->{gps}, int($h*1.8), $h; |
| 251 |
} |
| 252 |
} |
| 253 |
|
| 254 |
sub menu_bss_list { |
| 255 |
my @menu; |
| 256 |
#for my $k (sort { int $db->{$b}{ts} <=> int $db->{$a}{ts} } keys %$db) { |
| 257 |
for my $k (keys %$db) { |
| 258 |
my $v = $db->{$k}; |
| 259 |
add_menu e2h($k) . " " . bss2string($v), |
| 260 |
sub { display_bss $v }, |
| 261 |
sub { |
| 262 |
if ($_[0] eq KEY_LEFT or $_[0] eq "h") { |
| 263 |
Event::unloop; |
| 264 |
} elsif ($_[0] eq KEY_RIGHT or $_[0] eq "l") { |
| 265 |
#$curmenu = sub { display_bss $k }; |
| 266 |
} |
| 267 |
}; |
| 268 |
} |
| 269 |
} |
| 270 |
|
| 271 |
sub activity { |
| 272 |
if ($GPS) { |
| 273 |
my $data = $GPS->data; |
| 274 |
$db->{$_[0]}{gps}{"$data->{lat};$data->{long}"} = signal_quality; |
| 275 |
} |
| 276 |
$db->{$_[0]}->{ts} = time; |
| 277 |
$refresh->start; |
| 278 |
} |
| 279 |
|
| 280 |
sub reg_ip { |
| 281 |
my ($bssid, $ip, $ether) = @_; |
| 282 |
$db->{$bssid}{station}{$ether}{$ip}++; |
| 283 |
activity $bssid; |
| 284 |
} |
| 285 |
|
| 286 |
{ |
| 287 |
my $cap = open_pcap_socket; |
| 288 |
my $pkt; |
| 289 |
open LOG, ">/tmp/log";#d# |
| 290 |
|
| 291 |
Event->io(fd => $cap, poll => 'r', cb => sub { |
| 292 |
sysread $cap, $pkt, 120; |
| 293 |
|
| 294 |
printf LOG "%s\n", unpack "H*", $pkt; |
| 295 |
|
| 296 |
my ($fc1, $fc2, $sid, $a1, $a2, $a3, $sc, $pkt) |
| 297 |
= unpack "C C n a6 a6 a6 S a*", $pkt; |
| 298 |
|
| 299 |
send $cap, (pack "C C n a6 a6 a6 S a*", $fc1, $fc2, $sid, $a2, $a1, $a3, $sc, $pkt), 0, $::sockaddr; |
| 300 |
|
| 301 |
my $a4; |
| 302 |
|
| 303 |
if ($fc2 & 0x03 == 0x03) { |
| 304 |
$a4 = substr $pkt, 0, 6; |
| 305 |
$pkt = substr $pkt, 6; |
| 306 |
} |
| 307 |
|
| 308 |
if ($fc1 == 0x80 or $fc1 == 0x50) { |
| 309 |
my ($ts1, $ts2, $bi, $cf, $pkt) |
| 310 |
= unpack "L L S S a*", $pkt; |
| 311 |
|
| 312 |
my %tag = decode_tags $pkt; |
| 313 |
|
| 314 |
my $ap = $db->{$a3}{ap}{$a2} ||= {}; |
| 315 |
$ap->{mode} = ($cf & 3) == 1 ? "AP" : "adhoc"; |
| 316 |
|
| 317 |
# ESSID ' ' workaround for "closed-network feature" |
| 318 |
$ap->{essid} = $tag{SSID} unless $ap->{essid} && $tag{SSID} eq " "; |
| 319 |
$ap->{channel} = ord $tag{DS}; |
| 320 |
$ap->{beacon}++; |
| 321 |
|
| 322 |
activity $a3; |
| 323 |
} elsif ($fc1 == 0x08) { |
| 324 |
my $bssid = ($a3, $a1, $a2, $a2)[$fc2 & 3]; |
| 325 |
|
| 326 |
if ($fc2 & 0x40) { |
| 327 |
my ($iv1, $iv2, $iv3, $ivopt, $byte) = unpack "C C C C C", $pkt; |
| 328 |
$db->{$bssid}{wep}++; |
| 329 |
if ($iv1 > 2 and $iv1 < 14 and $iv2 == 255) { |
| 330 |
$db->{$bssid}{weak}++; |
| 331 |
} |
| 332 |
} else { |
| 333 |
my ($llc, $et, $pkt) = unpack "a6 n a*", $pkt; |
| 334 |
if ($llc eq "\xaa\xaa\x03\x00\x00\x00" |
| 335 |
or $llc eq "\xaa\xaa\x03\x00\x00\xf8") { # SNAP/UI/{ENCAP_ETHER,CISCO} |
| 336 |
if ($et == 0x0800) { |
| 337 |
my ($vhl, $tos, $len, $id, $off, $ttl, $prot, $sum, $src, $dst, $pkt) |
| 338 |
= unpack "C C n n n C C n a4 a4 a*", $pkt; |
| 339 |
if ($vhl & 0x40 == 0x40) { |
| 340 |
$db->{$bssid}{ip}++; |
| 341 |
reg_ip $bssid, $src, ($a2, $a2, $a3, $a4)[$fc2 & 3]; |
| 342 |
reg_ip $bssid, $dst, ($a1, $a3, $a1, $a3)[$fc2 & 3]; |
| 343 |
} |
| 344 |
} elsif ($et == 0x0806) { |
| 345 |
my ($hrd, $pro, $hln, $pln, $op, $src_hw, $src, $dst_hw, $dst) |
| 346 |
= unpack "n n C C n a6 a4 a6 a4", $pkt; |
| 347 |
if ($hrd == 1 and $hln == 6 and $pln == 4) { |
| 348 |
reg_ip $bssid, $src, $src_hw; |
| 349 |
reg_ip $bssid, $dst, $dst_hw if $op != 1; |
| 350 |
$db->{$bssid}{arp}++; |
| 351 |
} |
| 352 |
} |
| 353 |
} else { |
| 354 |
0 && |
| 355 |
printf "?? %02x %02x %04x %04x: %s.%s \n", $fc1, $fc2, $sid, $sc, (unpack "H*", $llc), unpack "H*", $pkt; |
| 356 |
} |
| 357 |
} |
| 358 |
} elsif ($fc1 != 0x40 and $fc1 != 0x10 and $fc1 != 0x00 and $fc1 != 0xb0) { |
| 359 |
0 && |
| 360 |
printf "%02x %02x %04x %04x: %s \n", $fc1, $fc2, $sid, $sc, unpack "H*", $pkt; |
| 361 |
} |
| 362 |
}); |
| 363 |
} |
| 364 |
|
| 365 |
Event::loop; |
| 366 |
|
| 367 |
|