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