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 |
|