Skip to content

Commit bc7bf3b

Browse files
committed
Fixed several bugs, added find_nearby_strangers plugin
1 parent c9a7722 commit bc7bf3b

8 files changed

Lines changed: 117 additions & 20 deletions

File tree

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
"""Finds nearby strangers. Basically a low-pass filter that alerts on infrequently seen (or unseen) nearby devices."""
2+
import os
3+
import time
4+
import pickle
5+
import datetime
6+
7+
__author__ = 'Caleb Madrigal'
8+
__email__ = '[email protected]'
9+
__version__ = '0.0.1'
10+
__apiversion__ = 1
11+
__config__ = {'power': -100, 'trigger_cooldown': 60, 'channel_switch_scheme': 'round_robin', 'time_per_channel': 0.1}
12+
13+
DEVS_TO_IGNORE = {'ff:ff:ff:ff:ff:ff', '00:00:00:00:00:00'}
14+
POWER_THRESHOLD = -50
15+
TIME_THRESHOLD = 15 * 60 # 15 minutes
16+
SAVE_PERIOD = 30 # seconds
17+
SAVE_FILE = 'find_nearby_strangers.pkl'
18+
19+
20+
class Trigger:
21+
def __init__(self):
22+
self.mac_to_seen = {}
23+
self.last_save = time.time()
24+
if os.path.exists(SAVE_FILE):
25+
with open(SAVE_FILE, 'rb') as f:
26+
self.mac_to_seen = pickle.load(f)
27+
print('Loaded {} devices from disk: {}'.format(len(self.mac_to_seen), list(self.mac_to_seen.keys())))
28+
29+
def __call__(self, dev_id=None, dev_type=None, power=None, vendor=None, **kwargs):
30+
# Only look at individual devices (device and bssids), and only look when power is present
31+
if ((not power) or
32+
(not dev_id) or
33+
(dev_type == 'ssid') or
34+
(dev_id in DEVS_TO_IGNORE) or
35+
(power < POWER_THRESHOLD)):
36+
return
37+
38+
seen_first_time = False
39+
40+
if dev_id not in self.mac_to_seen:
41+
seen_first_time = True
42+
last_seen = time.time()
43+
self.mac_to_seen[dev_id] = [last_seen]
44+
else:
45+
last_seen = self.mac_to_seen[dev_id][-1]
46+
self.mac_to_seen[dev_id].append(time.time())
47+
48+
if time.time() - last_seen > TIME_THRESHOLD:
49+
self.alert_stranger(dev_id, vendor, last_seen, power)
50+
elif seen_first_time:
51+
self.alert_stranger(dev_id, vendor, last_seen, power, first_seen=True)
52+
53+
if time.time() - self.last_save > SAVE_PERIOD:
54+
self.save_to_disk()
55+
56+
def alert_stranger(self, dev_id, vendor, last_seen, power, first_seen=False):
57+
# visual indicator
58+
if first_seen:
59+
msg = '[!] '
60+
else:
61+
msg = '[*] '
62+
63+
# timestamp
64+
msg += '{} - '.format(str(datetime.datetime.now().replace(microsecond=0)))
65+
66+
# device id
67+
msg += '{} '.format(dev_id)
68+
if vendor:
69+
msg += '({}) '.format(vendor)
70+
71+
# spotted at
72+
msg += 'spotted at {:+d} '.format(power)
73+
74+
# last seen
75+
if first_seen:
76+
msg += '(never before seen)'
77+
else:
78+
msg += '(last seen {} seconds ago)'.format(int(time.time() - last_seen))
79+
80+
# actually display msg
81+
print(msg)
82+
83+
def save_to_disk(self):
84+
with open(SAVE_FILE, 'wb') as f:
85+
pickle.dump(self.mac_to_seen, f)

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def get_readme():
2424

2525
setup(
2626
name = 'trackerjacker',
27-
packages = ['trackerjacker'],
27+
packages = ['trackerjacker', 'trackerjacker/plugins'],
2828
url = 'https://github.com/calebmadrigal/trackerjacker',
2929
version = get_version(),
3030
description = 'Finds and tracks wifi devices through raw 802.11 monitoring',

trackerjacker/__main__.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import errno
99
import pprint
1010
import logging
11+
import inspect
1112
import traceback
1213

1314
from . import config_management
@@ -202,7 +203,11 @@ def start(self):
202203
self.iface_manager.start()
203204
while True:
204205
try:
205-
scapy.sniff(iface=self.iface_manager.iface, prn=self.process_packet, store=0, exceptions=True)
206+
if 'exceptions' in inspect.signature(scapy.sniff).parameters:
207+
scapy.sniff(iface=self.iface_manager.iface, prn=self.process_packet, store=0, exceptions=True)
208+
else:
209+
# For versions of scapy that don't provide the exceptions kwarg
210+
scapy.sniff(iface=self.iface_manager.iface, prn=self.process_packet, store=0)
206211
except KeyboardInterrupt:
207212
break
208213
except OSError:

trackerjacker/config_management.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,9 @@ def build_config(args):
238238
# Allow any plugins to override config
239239
if config['trigger_plugin']:
240240
trigger_plugin_path = get_real_plugin_path(config['trigger_plugin'])
241-
parsed_trigger_plugin = plugin_parser.parse_trigger_plugin(trigger_plugin_path, config['plugin_config'])
241+
parsed_trigger_plugin = plugin_parser.parse_trigger_plugin(trigger_plugin_path,
242+
config['plugin_config'],
243+
parse_only=True)
242244

243245
# Allow plugin to override any config parameters
244246
if 'config' in parsed_trigger_plugin:
@@ -283,6 +285,7 @@ def get_real_plugin_path(trigger_plugin):
283285
possible_builtin_path = os.path.join(os.path.dirname(__file__),
284286
'plugins',
285287
'{}.py'.format(trigger_plugin))
288+
print('Possible builtin path: {}'.format(possible_builtin_path))
286289
if os.path.exists(possible_builtin_path):
287290
trigger_plugin = possible_builtin_path
288291
return trigger_plugin

trackerjacker/dot11_tracker.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import re
55
import time
6+
import traceback
67
import threading
78
import subprocess
89
from functools import reduce
@@ -281,8 +282,8 @@ def do_trigger_alert(self,
281282
channel=frame.channel,
282283
frame_type=frame.frame_type_name(),
283284
frame=raw_frame)
284-
except Exception as e:
285-
raise TJException('Error occurred in trigger plugin: {}'.format(e))
285+
except Exception:
286+
raise TJException('Error occurred in trigger plugin: {}'.format(traceback.format_exc()))
286287

287288
elif self.trigger_command:
288289
# Start trigger_command in background process - fire and forget

trackerjacker/plugin_parser.py

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
CURRENT_TRIGGER_API_VERSION = 1
3030

3131

32-
def parse_trigger_plugin(trigger_path, plugin_config):
32+
def parse_trigger_plugin(trigger_path, plugin_config, parse_only=False):
3333
"""Parse plugin file and return the trigger config."""
3434

3535
# Open and exec plugin definitions
@@ -44,19 +44,22 @@ def parse_trigger_plugin(trigger_path, plugin_config):
4444
trigger = trigger_vars.get('trigger', None)
4545
trigger_class = trigger_vars.get('Trigger', None)
4646

47-
if trigger_class:
48-
# Pass optional plugin_config to trigger class
49-
plugin_config = parse_plugin_config(plugin_config)
50-
51-
# Instantiate class. Note that only a trigger function or class can be defined (and class takes priority)
52-
# Assume the class is called 'Trigger'
53-
try:
54-
trigger = trigger_class(**plugin_config)
55-
except Exception as e:
56-
raise TJException('Error loading plugin ({}): {}'.format(trigger_path, e))
57-
58-
if not trigger:
59-
raise TJException('Plugin file must specify a "trigger" function or a "Trigger" class')
47+
if parse_only:
48+
trigger = None
49+
else:
50+
if trigger_class:
51+
# Pass optional plugin_config to trigger class
52+
plugin_config = parse_plugin_config(plugin_config)
53+
54+
# Instantiate class. Note that only a trigger function or class can be defined (and class takes priority)
55+
# Assume the class is called 'Trigger'
56+
try:
57+
trigger = trigger_class(**plugin_config)
58+
except Exception as e:
59+
raise TJException('Error loading plugin ({}): {}'.format(trigger_path, e))
60+
61+
if not trigger:
62+
raise TJException('Plugin file must specify a "trigger" function or a "Trigger" class')
6063

6164
return {'trigger': trigger, 'api_version': api_version, 'config': config}
6265

trackerjacker/plugins/__init__.py

Whitespace-only changes.

trackerjacker/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "1.7.6"
1+
__version__ = "1.7.7"

0 commit comments

Comments
 (0)