Moving Tshark logic into /tools/tshark.py
Added tests for analyzing handshakes
This commit is contained in:
@@ -3,9 +3,9 @@
|
|||||||
|
|
||||||
from ..util.process import Process
|
from ..util.process import Process
|
||||||
from ..util.color import Color
|
from ..util.color import Color
|
||||||
|
from ..tools.tshark import Tshark
|
||||||
|
|
||||||
import re
|
import re, os
|
||||||
import os
|
|
||||||
|
|
||||||
class Handshake(object):
|
class Handshake(object):
|
||||||
def __init__(self, capfile, bssid=None, essid=None):
|
def __init__(self, capfile, bssid=None, essid=None):
|
||||||
@@ -18,15 +18,15 @@ class Handshake(object):
|
|||||||
Tries to find BSSID and ESSID from cap file.
|
Tries to find BSSID and ESSID from cap file.
|
||||||
Sets this instances 'bssid' and 'essid' instance fields.
|
Sets this instances 'bssid' and 'essid' instance fields.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
# Get list of bssid/essid pairs from cap file
|
# Get list of bssid/essid pairs from cap file
|
||||||
pairs = self.tshark_bssid_essid_pairs()
|
pairs = Tshark.bssid_essid_pairs(self.capfile, bssid=self.bssid)
|
||||||
|
|
||||||
if len(pairs) == 0:
|
if len(pairs) == 0:
|
||||||
# Find bssid/essid pairs that have handshakes in Pyrit
|
pairs = self.pyrit_handshakes() # Find bssid/essid pairs that have handshakes in Pyrit
|
||||||
pairs = self.pyrit_handshakes()
|
|
||||||
|
|
||||||
if len(pairs) == 0 and not self.bssid and not self.essid:
|
if len(pairs) == 0 and not self.bssid and not self.essid:
|
||||||
# Tshark and Pyrit failed us, nothing else we can do.
|
raise Exception("Cannot find BSSID or ESSID in cap file") # Tshark and Pyrit failed us, nothing else we can do.
|
||||||
raise Exception("Cannot find BSSID or ESSID in cap file")
|
|
||||||
|
|
||||||
if not self.essid and not self.bssid:
|
if not self.essid and not self.bssid:
|
||||||
# We do not know the bssid nor the essid
|
# We do not know the bssid nor the essid
|
||||||
@@ -34,10 +34,8 @@ class Handshake(object):
|
|||||||
# HACK: Just use the first one we see
|
# HACK: Just use the first one we see
|
||||||
self.bssid = pairs[0][0]
|
self.bssid = pairs[0][0]
|
||||||
self.essid = pairs[0][1]
|
self.essid = pairs[0][1]
|
||||||
Color.pl('{!} {O}Warning{W}:' +
|
Color.pl('{!} {O}Warning{W}: {O}Arbitrarily selected ' +
|
||||||
' {O}Arbitrarily selected' +
|
'{R}bssid{O} {C}%s{O} and {R}essid{O} "{C}%s{O}"{W}' % (self.bssid, self.essid))
|
||||||
' {R}bssid{O} {C}%s{O} and {R}essid{O} "{C}%s{O}"{W}'
|
|
||||||
% (self.bssid, self.essid))
|
|
||||||
|
|
||||||
elif not self.bssid:
|
elif not self.bssid:
|
||||||
# We already know essid
|
# We already know essid
|
||||||
@@ -59,140 +57,23 @@ class Handshake(object):
|
|||||||
if not self.bssid or not self.essid:
|
if not self.bssid or not self.essid:
|
||||||
self.divine_bssid_and_essid()
|
self.divine_bssid_and_essid()
|
||||||
|
|
||||||
if len(self.tshark_handshakes()) > 0:
|
if len(self.tshark_handshakes()) > 0: return True
|
||||||
return True
|
if len(self.pyrit_handshakes()) > 0: return True
|
||||||
|
|
||||||
if len(self.pyrit_handshakes()) > 0:
|
# TODO: Can we trust cowpatty & aircrack?
|
||||||
return True
|
#if len(self.cowpatty_handshakes()) > 0: return True
|
||||||
|
#if len(self.aircrack_handshakes()) > 0: return True
|
||||||
if len(self.cowpatty_handshakes()) > 0:
|
|
||||||
return True
|
|
||||||
|
|
||||||
if len(self.aircrack_handshakes()) > 0:
|
|
||||||
return True
|
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def tshark_bssid_essid_pairs(self):
|
def tshark_bssid_essid_pairs(self):
|
||||||
'''
|
'''Returns list of tuples: (bssid,essid) found in capfile'''
|
||||||
Scrapes capfile for beacon frames indicating the ESSID.
|
|
||||||
Returns list of tuples: (bssid,essid)
|
|
||||||
'''
|
|
||||||
if not Process.exists('tshark'):
|
|
||||||
return []
|
|
||||||
|
|
||||||
essids = set()
|
|
||||||
|
|
||||||
# Extract beacon frames from cap file
|
|
||||||
cmd = [
|
|
||||||
'tshark',
|
|
||||||
'-r', self.capfile,
|
|
||||||
'-R', 'wlan.fc.type_subtype == 0x08 || wlan.fc.type_subtype == 0x05',
|
|
||||||
'-2', # tshark: -R without -2 is deprecated.
|
|
||||||
'-n'
|
|
||||||
]
|
|
||||||
proc = Process(cmd, devnull=False)
|
|
||||||
for line in proc.stdout().split('\n'):
|
|
||||||
# Extract src, dst, and essid
|
|
||||||
mac_regex = ('[a-zA-Z0-9]{2}:' * 6)[:-1]
|
|
||||||
match = re.search('(%s) [^ ]* (%s).*.*SSID=(.*)$'
|
|
||||||
% (mac_regex, mac_regex), line)
|
|
||||||
if match is None:
|
|
||||||
# Line doesn't contain src, dst, ssid
|
|
||||||
continue
|
|
||||||
(src, dst, essid) = match.groups()
|
|
||||||
if dst.lower() == "ff:ff:ff:ff:ff:ff": continue
|
|
||||||
if self.bssid:
|
|
||||||
# We know the BSSID, only return the ESSID for this BSSID.
|
|
||||||
if self.bssid.lower() == src.lower() or self.bssid.lower() == dst.lower():
|
|
||||||
essids.add((src, essid))
|
|
||||||
else:
|
|
||||||
# We do not know BSSID, add it.
|
|
||||||
essids.add((src, essid))
|
|
||||||
# Return list of tuples
|
|
||||||
return [x for x in essids]
|
|
||||||
|
|
||||||
|
|
||||||
def tshark_command(self):
|
|
||||||
return [
|
|
||||||
'tshark',
|
|
||||||
'-r', self.capfile,
|
|
||||||
'-R', 'eapol',
|
|
||||||
'-n',
|
|
||||||
'-2' # 2-pass filtering, required when using -R in newer versions of tshark
|
|
||||||
]
|
|
||||||
|
|
||||||
def tshark_handshakes(self):
|
def tshark_handshakes(self):
|
||||||
''' Returns True if tshark identifies a handshake, False otherwise '''
|
''' Returns True if tshark identifies a handshake, False otherwise '''
|
||||||
if not Process.exists('tshark'):
|
tshark_bssids = Tshark.bssids_with_handshakes(self.capfile, bssid=self.bssid)
|
||||||
return []
|
return [(bssid, None) for bssid in tshark_bssids]
|
||||||
|
|
||||||
target_client_msg_nums = {}
|
|
||||||
|
|
||||||
# Dump EAPOL packets
|
|
||||||
proc = Process(self.tshark_command(), devnull=False)
|
|
||||||
for line in proc.stdout().split('\n'):
|
|
||||||
# Extract source mac, destination mac, and message numbers
|
|
||||||
mac_regex = ('[a-zA-Z0-9]{2}:' * 6)[:-1]
|
|
||||||
match = re.search('(%s) (?:->|→) (%s).*Message.*(\d).*(\d)'
|
|
||||||
% (mac_regex, mac_regex), line)
|
|
||||||
if match is None:
|
|
||||||
# Line doesn't contain src, dst, Message numbers
|
|
||||||
continue
|
|
||||||
(src, dst, index, ttl) = match.groups()
|
|
||||||
# "Message (index) of (ttl)"
|
|
||||||
index = int(index)
|
|
||||||
ttl = int(ttl)
|
|
||||||
|
|
||||||
if ttl != 4:
|
|
||||||
# Must be a 4-way handshake
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Identify the client and target MAC addresses
|
|
||||||
if index % 2 == 1:
|
|
||||||
# First and Third messages
|
|
||||||
target = src
|
|
||||||
client = dst
|
|
||||||
else:
|
|
||||||
# Second and Fourth messages
|
|
||||||
client = src
|
|
||||||
target = dst
|
|
||||||
|
|
||||||
if self.bssid and self.bssid.lower() != target.lower():
|
|
||||||
# We know the BSSID and this msg was not for the target
|
|
||||||
continue
|
|
||||||
|
|
||||||
target_client_key = '%s,%s' % (target, client)
|
|
||||||
|
|
||||||
# Ensure all 4 messages are:
|
|
||||||
# Between the same client and target
|
|
||||||
# In numeric & chronological order (1,2,3,4)
|
|
||||||
if index == 1:
|
|
||||||
# First message, add to dict
|
|
||||||
target_client_msg_nums[target_client_key] = 1
|
|
||||||
|
|
||||||
elif target_client_key not in target_client_msg_nums:
|
|
||||||
# Not first message, we haven't gotten the first message yet
|
|
||||||
continue
|
|
||||||
|
|
||||||
elif index - 1 != target_client_msg_nums[target_client_key]:
|
|
||||||
# Message is not in sequence
|
|
||||||
continue
|
|
||||||
|
|
||||||
else:
|
|
||||||
# Message is > 1 and is received in-order
|
|
||||||
target_client_msg_nums[target_client_key] = index
|
|
||||||
|
|
||||||
bssids = set()
|
|
||||||
# Check if we have all 4 messages for the handshake between the same MACs
|
|
||||||
for (client_target, num) in target_client_msg_nums.items():
|
|
||||||
if num == 4:
|
|
||||||
# We got a handshake!
|
|
||||||
bssid = client_target.split(',')[0]
|
|
||||||
bssids.add(bssid)
|
|
||||||
|
|
||||||
return [(bssid, None) for bssid in bssids]
|
|
||||||
|
|
||||||
|
|
||||||
def cowpatty_command(self):
|
def cowpatty_command(self):
|
||||||
@@ -259,8 +140,7 @@ class Handshake(object):
|
|||||||
current_essid = essid
|
current_essid = essid
|
||||||
hit_target = True
|
hit_target = True
|
||||||
else:
|
else:
|
||||||
# This AccessPoint is not what we're looking for
|
hit_Target = False # This AccessPoint is not what we're looking for
|
||||||
hit_Target = False
|
|
||||||
else:
|
else:
|
||||||
# Line does not contain AccessPoint
|
# Line does not contain AccessPoint
|
||||||
if hit_target and ', good' in line:
|
if hit_target and ', good' in line:
|
||||||
@@ -268,19 +148,22 @@ class Handshake(object):
|
|||||||
return [x for x in bssid_essid_pairs]
|
return [x for x in bssid_essid_pairs]
|
||||||
|
|
||||||
|
|
||||||
def aircrack_command(self):
|
|
||||||
return 'echo "" | aircrack-ng -a 2 -w - -b %s "%s"' % (self.bssid, self.capfile)
|
|
||||||
|
|
||||||
def aircrack_handshakes(self):
|
def aircrack_handshakes(self):
|
||||||
|
'''Returns tuple (BSSID,None) if aircrack thinks self.capfile contains a handshake / can be cracked'''
|
||||||
if not self.bssid:
|
if not self.bssid:
|
||||||
return []
|
return [] # Aircrack requires BSSID
|
||||||
(stdout, stderr) = Process.call(self.aircrack_command())
|
|
||||||
|
command = 'echo "" | aircrack-ng -a 2 -w - -b %s "%s"' % (self.bssid, self.capfile)
|
||||||
|
(stdout, stderr) = Process.call(command)
|
||||||
|
|
||||||
if 'passphrase not in dictionary' in stdout.lower():
|
if 'passphrase not in dictionary' in stdout.lower():
|
||||||
return [(self.bssid, None)]
|
return [(self.bssid, None)]
|
||||||
else:
|
else:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
def analyze(self):
|
def analyze(self):
|
||||||
|
'''Prints analysis of handshake capfile'''
|
||||||
self.divine_bssid_and_essid()
|
self.divine_bssid_and_essid()
|
||||||
|
|
||||||
pairs = self.tshark_handshakes()
|
pairs = self.tshark_handshakes()
|
||||||
@@ -314,8 +197,7 @@ class Handshake(object):
|
|||||||
cmd = [
|
cmd = [
|
||||||
'tshark',
|
'tshark',
|
||||||
'-r', self.capfile, # input file
|
'-r', self.capfile, # input file
|
||||||
'-R', 'wlan.fc.type_subtype == 0x08 || wlan.fc.type_subtype == 0x05 || eapol', # filter
|
'-Y', 'wlan.fc.type_subtype == 0x08 || wlan.fc.type_subtype == 0x05 || eapol', # filter
|
||||||
'-2', # tshark: -R without -2 is deprecated.
|
|
||||||
'-w', outfile # output file
|
'-w', outfile # output file
|
||||||
]
|
]
|
||||||
proc = Process(cmd)
|
proc = Process(cmd)
|
||||||
@@ -357,7 +239,23 @@ class Handshake(object):
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
hs = Handshake('./tests/files/handshake_exists.cap', bssid='A4:2B:8C:16:6B:3A')
|
print('With BSSID & ESSID specified:')
|
||||||
|
hs = Handshake('./tests/files/handshake_has_1234.cap', bssid='18:d6:c7:6d:6b:18', essid='YZWifi')
|
||||||
|
hs.analyze()
|
||||||
|
print("has_hanshake() =", hs.has_handshake())
|
||||||
|
|
||||||
|
print('\nWith BSSID, but no ESSID specified:')
|
||||||
|
hs = Handshake('./tests/files/handshake_has_1234.cap', bssid='18:d6:c7:6d:6b:18')
|
||||||
|
hs.analyze()
|
||||||
|
print("has_hanshake() =", hs.has_handshake())
|
||||||
|
|
||||||
|
print('\nWith ESSID, but no BSSID specified:')
|
||||||
|
hs = Handshake('./tests/files/handshake_has_1234.cap', essid='YZWifi')
|
||||||
|
hs.analyze()
|
||||||
|
print("has_hanshake() =", hs.has_handshake())
|
||||||
|
|
||||||
|
print('\nWith neither BSSID nor ESSID specified:')
|
||||||
|
hs = Handshake('./tests/files/handshake_has_1234.cap')
|
||||||
hs.analyze()
|
hs.analyze()
|
||||||
print("has_hanshake() =", hs.has_handshake())
|
print("has_hanshake() =", hs.has_handshake())
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,136 @@ class Tshark(object):
|
|||||||
def exists():
|
def exists():
|
||||||
return Process.exists('tshark')
|
return Process.exists('tshark')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _extract_src_dst_index_total(line):
|
||||||
|
# Extract BSSIDs, handshake # (1-4) and handshake "total" (4)
|
||||||
|
mac_regex = ('[a-zA-Z0-9]{2}:' * 6)[:-1]
|
||||||
|
match = re.search('(%s)\s*.*\s*(%s).*Message.*(\d).*of.*(\d)' % (mac_regex, mac_regex), line)
|
||||||
|
if match is None:
|
||||||
|
# Line doesn't contain src, dst, Message numbers
|
||||||
|
return None, None, None, None
|
||||||
|
(src, dst, index, total) = match.groups()
|
||||||
|
return src, dst, index, total
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _build_target_client_handshake_map(output, bssid=None):
|
||||||
|
# Map of target_ssid,client_ssid -> handshake #s
|
||||||
|
# E.g. 12:34:56,21:43:65 -> 3
|
||||||
|
target_client_msg_nums = {}
|
||||||
|
|
||||||
|
for line in output.split('\n'):
|
||||||
|
src, dst, index, total = Tshark._extract_src_dst_index_total(line)
|
||||||
|
|
||||||
|
if src is None: continue # Skip
|
||||||
|
|
||||||
|
index = int(index)
|
||||||
|
total = int(total)
|
||||||
|
|
||||||
|
if total != 4: continue # Handshake X of 5? X of 3? Skip it.
|
||||||
|
|
||||||
|
# Identify the client and target MAC addresses
|
||||||
|
if index % 2 == 1:
|
||||||
|
# First and Third messages
|
||||||
|
target = src
|
||||||
|
client = dst
|
||||||
|
else:
|
||||||
|
# Second and Fourth messages
|
||||||
|
client = src
|
||||||
|
target = dst
|
||||||
|
|
||||||
|
if bssid is not None and bssid.lower() != target.lower():
|
||||||
|
# We know the BSSID and this msg was not for the target
|
||||||
|
continue
|
||||||
|
|
||||||
|
target_client_key = '%s,%s' % (target, client)
|
||||||
|
|
||||||
|
# Ensure all 4 messages are:
|
||||||
|
# Between the same client and target (not different clients connecting).
|
||||||
|
# In numeric & chronological order (Message 1, then 2, then 3, then 4)
|
||||||
|
if index == 1:
|
||||||
|
target_client_msg_nums[target_client_key] = 1 # First message
|
||||||
|
|
||||||
|
elif target_client_key not in target_client_msg_nums:
|
||||||
|
continue # Not first message. We haven't gotten the first message yet. Skip.
|
||||||
|
|
||||||
|
elif index - 1 != target_client_msg_nums[target_client_key]:
|
||||||
|
continue # Message is not in sequence. Skip
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Happy case: Message is > 1 and is received in-order
|
||||||
|
target_client_msg_nums[target_client_key] = index
|
||||||
|
|
||||||
|
return target_client_msg_nums
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def bssids_with_handshakes(capfile, bssid=None):
|
||||||
|
if not Tshark.exists():
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Returns list of BSSIDs for which we have valid handshakes in the capfile.
|
||||||
|
command = [
|
||||||
|
'tshark',
|
||||||
|
'-r', capfile,
|
||||||
|
'-n', # Don't resolve addresses
|
||||||
|
'-Y', 'eapol' # Filter for only handshakes
|
||||||
|
]
|
||||||
|
tshark = Process(command, devnull=False)
|
||||||
|
|
||||||
|
target_client_msg_nums = Tshark._build_target_client_handshake_map(tshark.stdout(), bssid=bssid)
|
||||||
|
|
||||||
|
bssids = set()
|
||||||
|
# Check if we have all 4 messages for the handshake between the same MACs
|
||||||
|
for (target_client, num) in target_client_msg_nums.items():
|
||||||
|
if num == 4:
|
||||||
|
# We got a handshake!
|
||||||
|
this_bssid = target_client.split(',')[0]
|
||||||
|
bssids.add(this_bssid)
|
||||||
|
|
||||||
|
return list(bssids)
|
||||||
|
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def bssid_essid_pairs(capfile, bssid):
|
||||||
|
# Finds all BSSIDs (with corresponding ESSIDs) from cap file.
|
||||||
|
# Returns list of tuples(BSSID, ESSID)
|
||||||
|
|
||||||
|
if not Tshark.exists():
|
||||||
|
return []
|
||||||
|
|
||||||
|
ssid_pairs = set()
|
||||||
|
|
||||||
|
command = [
|
||||||
|
'tshark',
|
||||||
|
'-r', capfile, # Path to cap file
|
||||||
|
# Extract beacon frames
|
||||||
|
'-Y', 'wlan.fc.type_subtype == 0x08 || wlan.fc.type_subtype == 0x05',
|
||||||
|
'-n', # Don't resolve addresses
|
||||||
|
]
|
||||||
|
tshark = Process(command, devnull=False)
|
||||||
|
|
||||||
|
for line in tshark.stdout().split('\n'):
|
||||||
|
# Extract src, dst, and essid
|
||||||
|
mac_regex = ('[a-zA-Z0-9]{2}:' * 6)[:-1]
|
||||||
|
match = re.search('(%s) [^ ]* (%s).*.*SSID=(.*)$' % (mac_regex, mac_regex), line)
|
||||||
|
if match is None:
|
||||||
|
continue # Line doesn't contain src, dst, ssid
|
||||||
|
|
||||||
|
(src, dst, essid) = match.groups()
|
||||||
|
|
||||||
|
if dst.lower() == "ff:ff:ff:ff:ff:ff":
|
||||||
|
continue # Skip broadcast packets
|
||||||
|
|
||||||
|
if bssid is not None:
|
||||||
|
# We know the BSSID, only return the ESSID for this BSSID.
|
||||||
|
if bssid.lower() == src.lower():
|
||||||
|
ssid_pairs.add((src, essid)) # This is our BSSID, add it
|
||||||
|
else:
|
||||||
|
ssid_pairs.add((src, essid)) # We do not know BSSID, add it.
|
||||||
|
|
||||||
|
return list(ssid_pairs)
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_for_wps_and_update_targets(capfile, targets):
|
def check_for_wps_and_update_targets(capfile, targets):
|
||||||
'''
|
'''
|
||||||
@@ -66,6 +196,7 @@ if __name__ == '__main__':
|
|||||||
test_file = './tests/files/contains_wps_network.cap'
|
test_file = './tests/files/contains_wps_network.cap'
|
||||||
|
|
||||||
target_bssid = 'A4:2B:8C:16:6B:3A'
|
target_bssid = 'A4:2B:8C:16:6B:3A'
|
||||||
|
'''
|
||||||
from ..model.target import Target
|
from ..model.target import Target
|
||||||
fields = [
|
fields = [
|
||||||
'A4:2B:8C:16:6B:3A', # BSSID
|
'A4:2B:8C:16:6B:3A', # BSSID
|
||||||
@@ -84,4 +215,6 @@ if __name__ == '__main__':
|
|||||||
|
|
||||||
print('Target(BSSID={}).wps = {} (Expected: True)'.format(targets[0].bssid, targets[0].wps))
|
print('Target(BSSID={}).wps = {} (Expected: True)'.format(targets[0].bssid, targets[0].wps))
|
||||||
assert targets[0].wps == True
|
assert targets[0].wps == True
|
||||||
|
'''
|
||||||
|
|
||||||
|
print(Tshark.bssids_with_handshakes(test_file, bssid=target_bssid))
|
||||||
|
|||||||
Reference in New Issue
Block a user